小菜鸟之java内存结构

JVM启动流程:html

wps1

JVM基本结构图:wps2java

《深刻理解Java虚拟机(第二版)》中的描述是下面这个样子的:面试

wps3

wps4

wps5

Java中的内存分配:算法

Java程序在运行时,须要在内存中的分配空间。为了提升运算效率,就对数据进行了不一样空间的划分,由于每一片区域都有特定的处理数据方式和内存管理方式。编程

具体划分为以下5个内存空间:(很是重要)数组

栈:存放局部变量 堆:存放全部new出来的东西 方法区:被虚拟机加载的类信息、常量、静态常量等。 程序计数器(和系统相关) 本地方法栈缓存

一、程序计数器:安全

每一个线程拥有一个PC寄存器多线程

在线程建立时建立并发

指向下一条指令的地址

执行本地方法时,PC的值为undefined

二、方法区:

保存装载的类信息

  类型的常量池

  字段,方法信息

  方法字节码

一般和永久区(Perm)关联在一块儿

三、堆内存:

程序开发密切相关

应用系统对象都保存在Java堆中

全部线程共享Java堆

对分代GC来讲,堆也是分代的

GC管理的主要区域

如今的GC基本都采用分代收集算法,若是是分代的,那么堆也是分代的。若是堆是分代的,那堆空间应该是下面这个样子:

wps6

上图是堆的基本结构,在以后的文章中再进行详解。

四、栈内存:

线程私有,生命周期和线程相同 栈由一系列帧组成(所以Java栈也叫作帧栈) 帧保存一个方法的局部变量、操做数栈、常量池指针 每一次方法调用建立一个帧,并压栈

解释:

Java虚拟机栈描述的是Java方法执行的内存模型:每一个方法被调用的时候都会建立一个栈帧,用于存储局部变量表、操做栈、动态连接、方法出口等信息。每个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机中从入栈到出栈的过程。

在Java虚拟机规范中,对这个区域规定了两种异常状况:

(1)若是线程请求的栈深度太深,超出了虚拟机所容许的深度,就会出现StackOverFlowError(好比无限递归。由于每一层栈帧都占用必定空间,而 Xss 规定了栈的最大空间,超出这个值就会报错)

(2)虚拟机栈能够动态扩展,若是扩展到没法申请足够的内存空间,会出现OOM

4.1 Java栈之局部变量表:包含参数和局部变量

局部变量表存放了基本数据类型、对象引用和returnAddress类型(指向一条字节码指令的地址)。其中64位长度的long和double类型的数据会占用2个局部变量空间(slot),其他数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配。

例如,我写出下面这段代码:

wps7

?

package test03;

/**

* Created by smyhvae on 2015/8/15.

*/

public class StackDemo {

//静态方法

public static int runStatic(int i, long l, float f, Object o, byte b) {

return 0;

}

//实例方法

public int runInstance(char c, short s, boolean b) {

return 0;

}

}

wps8

上方代码中,静态方法有6个形参,实例方法有3个形参。其对应的局部变量表以下:

wps9

上方表格中,静态方法和实例方法对应的局部变量表基本相似。但有如下区别:实例方法的表中,第一个位置存放的是当前对象的引用。

四、2 Java栈之函数调用组成栈帧:

方法每次被调用的时候都会建立一个栈帧,例以下面这个方法:

?

public static int runStatic(int i,long l,float f,Object o ,byte b){

       return runStatic(i,l,f,o,b);

}

当它每次被调用的时候,都会建立一个帧,方法调用结束后,帧出栈。以下图所示:

wps10

4.3 Java栈之操做数栈

Java没有寄存器,全部参数传递都是使用操做数栈

例以下面这段代码:

?

1

2

3

4

5

public static int add(int a,int b){

    int c=0;

    c=a+b;

    return c;

}

压栈的步骤以下:

0: iconst_0 // 0压栈

1: istore_2 // 弹出int,存放于局部变量2

2: iload_0 // 把局部变量0压栈

3: iload_1 // 局部变量1压栈

4: iadd //弹出2个变量,求和,结果压栈

5: istore_2 //弹出结果,放于局部变量2

6: iload_2 //局部变量2压栈

7: ireturn //返回

若是计算100+98的值,那么操做数栈的变化以下图所示:

wps11

4.4 Java栈之栈上分配:

小对象(通常几十个bytes),在没有逃逸的状况下,能够直接分配在栈上

直接分配在栈上,能够自动回收,减轻GC压力

大对象或者逃逸对象没法栈上分配

栈、堆、方法区交互:

wps12wps13

3、内存模型:

每个线程有一个工做内存。工做内存和主存独立。工做内存存放主存中变量的值的拷贝。

wps14

当数据从主内存复制到工做存储时,必须出现两个动做:第一,由主内存执行的读(read)操做;第二,由工做内存执行的相应的load操做;当数据从工做内存拷贝到主内存时,也出现两个操做:第一个,由工做内存执行的存储(store)操做;第二,由主内存执行的相应的写(write)操做。

每个操做都是原子的,即执行期间不会被中断

对于普通变量,一个线程中更新的值,不能立刻反应在其余变量中。若是须要在其余线程中当即可见,须要使用volatile关键字做为标识。

wps15

一、可见性:

  一个线程修改了变量,其余线程能够当即知道

保证可见性的方法:

volatile

synchronized (unlock以前,写变量值回主存)

final(一旦初始化完成,其余线程就可见)

二、有序性:

  在本线程内,操做都是有序的

  在线程外观察,操做都是无序的。(指令重排 或 主内存同步延时)

三、指令重排:

wps16

指令重排:破坏了线程间的有序性:

wps17

指令重排:保证有序性的方法:

wps18

指令重排的基本原则:

程序顺序原则:一个线程内保证语义的串行性

volatile规则:volatile变量的写,先发生于读

锁规则:解锁(unlock)必然发生在随后的加锁(lock)前

传递性:A先于B,B先于C 那么A必然先于C

线程的start方法先于它的每个动做

线程的全部操做先于线程的终结(Thread.join())

线程的中断(interrupt())先于被中断线程的代码

对象的构造函数执行结束先于finalize()方法

4、解释运行和编译运行的概念:

解释运行:

解释执行以解释方式运行字节码

解释执行的意思是:读一句执行一句

编译运行(JIT):

将字节码编译成机器码

直接执行机器码

运行时编译

编译后性能有数量级的提高

编译运行的性能优于解释运行

Q:简单说说 Java 的 JVM 内存结构分为哪几个部分

A:JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈,运行时常量池(六个部分,分别解释以下)

· 虚拟机栈:

线程私有的,每一个方法在执行时会建立一个栈帧,用来存储局部变量表、操做数栈、动态链接、方法返回地址等;其中局部变量表用于存放 8 种基本数据类型(boolean、byte、char、short、int、float、long、double)和 reference 类型。每一个方法从调用到执行完毕对应一个栈帧在虚拟机栈中的入栈和出栈。

· 堆:

线程共享的,在虚拟机启动时建立,用于存放对象实例。

· 方法区:

线程共享的,用于存储已被虚拟机加载的类信息、常量、静态变量等。

· 程序计数器:

线程私有的,是当前线程所执行的字节码行号指示器,每一个线程都有一个独立的程序计数器,字节码解释器工做时经过改变它的值来选取下一条须要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复都依赖于它。

· 本地方法栈:

线程私有的,主要为虚拟机用到的 native 方法服务,与虚拟机栈相似。

· 运行时常量池(Runtime Constant Pool)

,存放的为类中的固定的常量信息、方法和Field的引用信息等,其空间从方法区域中分配。

Q: 堆和栈的特色

当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的做用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间能够当即被另做他用。

堆内存用来存放由new建立的对象和数组。

java中变量在内存中的分配

一、类变量(static修饰的变量):在程序加载时系统就为它在堆中开辟了内存,堆中的内存地址存放于栈以便于高速访问。静态变量的生命周期--一直持续到整个'系统'关闭

二、实例变量:当你使用java关键字new的时候,系统在堆中开辟并不必定是连续的空间分配给变量(好比说类实例),而后根据零散的堆内存地址,经过哈希算法换算为一长串数字以表征这个变量在堆中的'物理位置'。 实例变量的生命周期--当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并非立刻就释放堆中内存

三、局部变量:局部变量,由声明在某方法,或某代码段里(好比for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离做用域,内存当即释放

Q:你能不能谈谈,java GC是在何时,对什么东西,作了什么事情?

A:

Q:简单谈谈你对 Java 虚拟机内存模型 JMM 的认识和理解及并发中的原子性、可见性、有序性的理解?

这是一个很泛很大且颇有水准的面试题,也算是对并发基础原理实质的一个深度问题,想要在面试中简短的回答好不是特别容易,本解析也仅供参考,具体理解可本身查阅其余资料。

Java 内存模型主要目标是定义程序中变量(此处变量特指实例字段、静态字段等,但不包括局部变量和函数参数,由于这两种是线程私有没法共享)在虚拟机中存储到内存与从内存读取出来的规则细节,Java 内存模型规定全部变量都存储在主内存中,每条线程还有本身的工做内存,工做内存保存了该线程使用到的变量到主内存副本拷贝,线程对变量的全部操做(读取、赋值)都必须在工做内存中进行而不能直接读写主内存中的变量,不一样线程之间没法相互直接访问对方工做内存中的变量,线程间变量值的传递均须要在主内存来完成(具体以下图)。

wps19

Java 内存模型对主内存与工做内存之间的具体交互协议定义了八种操做,具体以下:

· lock(锁定):做用于主内存变量,把一个变量标识为一条线程独占状态。

· unlock(解锁):做用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才能够被其余线程锁定。

· read(读取):做用于主内存变量,把一个变量从主内存传输到线程的工做内存中,以便随后的 load 动做使用。

· load(载入):做用于工做内存变量,把 read 操做从主内存中获得的变量值放入工做内存的变量副本中。

· use(使用):做用于工做内存变量,把工做内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个须要使用变量值的字节码指令时执行此操做。

· assign(赋值):做用于工做内存变量,把一个从执行引擎接收的值赋值给工做内存的变量,每当虚拟机遇到一个须要给变量进行赋值的字节码指令时执行此操做。

· store(存储):做用于工做内存变量,把工做内存中一个变量的值传递到主内存中,以便后续 write 操做。

· write(写入):做用于主内存变量,把 store 操做从工做内存中获得的值放入主内存变量中。

若是要把一个变量从主内存复制到工做内存就必须按顺序执行 read 和 load 操做,从工做内存同步回主内存就必须顺序执行 store 和 write 操做,可是 JVM 只要求了操做的顺序而没有要求上述操做必须保证连续性,因此实质执行中 read 和 load 间及 store 和 write 间是能够插入其余指令的。

Java 内存模型还会对指令进行重排序操做,在执行程序时为了提升性能编译器和处理器常常会对指令进行重排序操做,重排序主要分下面几类:

· 编译器优化重排序:编译器在不改变单线程程序语义的前提下能够从新安排语句的执行顺序。

· 指令级并行重排序:现代处理器采用了指令级并行技术来将多条指令重叠执行,若是不存在数据依赖性处理器能够改变语句对应机器指令的执行顺序。

· 内存系统重排序:因为处理器使用缓存和读写缓冲区使得加载和存储操做看上去多是在乱序执行。

其实 Java JMM 内存模型是围绕并发编程中原子性、可见性、有序性三个特征来创建的,关于原子性、可见性、有序性的理解以下:

· 原子性:就是说一个操做不能被打断,要么执行完要么不执行,相似事务操做,Java 基本类型数据的访问大都是原子操做,long 和 double 类型是 64 位,在 32 位 JVM 中会将 64 位数据的读写操做分红两次 32 位来处理,因此 long 和 double 在 32 位 JVM 中是非原子操做,也就是说在并发访问时是线程非安全的,要想保证原子性就得对访问该数据的地方进行同步操做,譬如 synchronized 等。

· 可见性:就是说当一个线程对共享变量作了修改后其余线程能够当即感知到该共享变量的改变,从 Java 内存模型咱们就能看出来多线程访问共享变量都要通过线程工做内存到主存的复制和主存到线程工做内存的复制操做,因此普通共享变量就没法保证可见性了;Java 提供了 volatile 修饰符来保证变量的可见性,每次使用 volatile 变量都会主动从主存中刷新,除此以外 synchronized、Lock、final 均可以保证变量的可见性。

· 有序性:就是说 Java 内存模型中的指令重排不会影响单线程的执行顺序,可是会影响多线程并发执行的正确性,因此在并发中咱们必需要想办法保证并发代码的有序性;在 Java 里能够经过 volatile 关键字保证必定的有序性,还能够经过 synchronized、Lock 来保证有序性,由于 synchronized、Lock 保证了每一时刻只有一个线程执行同步代码至关于单线程执行,因此天然不会有有序性的问题;除此以外 Java 内存模型经过 happens-before 原则若是能推导出来两个操做的执行顺序就能先天保证有序性,不然没法保证,关于 happens-before 原则能够查阅相关资料

相关文章
相关标签/搜索