JVM——Java内存区域

运行时数据区

程序计数器

线程私有;
唯一不产生OOM的情况;
指定当前执行的字节码行号。

线程私有;
*Error:栈深度不够(比如递归调用);
OutOfMemoryError:扩展无法申请到足够内存。
注意逃逸分析时,有方法直接放到栈上,用来减少堆内存中发生GC的频率。

逃逸分析

分析对象的动态作用域, 当一个对象在方法里面被定义后,他可能被外部方法所引用,例如调用参数传递到其他方法中,这种称为方法逃逸;
如果被外部线程访问到,称为线程逃逸。
逃逸程度: 不逃逸---->方法逃逸---->线程逃逸。
如果能证明一个对象不会逃逸到方法之外,或者逃逸程度低,可以采用不同程度的优化。

  • 栈上分配
    如果对象方法的调用不逃逸出栈帧,或者只在方法层次上逃逸,但不逃逸出线程,那么此时对象不需要再堆上存储,只需要存储在栈就行。
    支持方法逃逸,不支持线程逃逸。
  • 标量替换
    如果把一个Java对象拆散, 根据访问期间的情况,将其用到的成员变量恢复为原始类型(int,long,reference)来访问,这个过程就是标量替换。
    不允许对象逃逸出方法。
  • 同步消除
    线程同步本身是消耗性能的过程,如果逃逸分析确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写就肯定不会有竞争,对这个变量的同步操作就可以安全的消除掉。

栈帧

要运行的方法是放入栈帧中的。

局部变量表

基本数据类型(8大基本数据类型),对象引用(reference类型)和returnAddress(指向一条字节码指令的地址)。

操作数栈

概念模型中,两个栈帧之间是严格分开的,但是在实际的JVM中,两个栈帧会出现一部分重叠,让下面栈帧的部分的操作数栈和上面栈帧的部分的局部变量表重叠在一起。
*好处:公用一部分数据,不用额外的参数传递。

动态链接

动态链接与静态链接的区别
静态链接:在类加载的解析阶段,将符号引用替换为直接引用,该阶段会把一些静态方法的符号引用,替换为指向数据存储在内存中的指针或句柄。
动态链接:是在程序运行期间,完成符号引用转化为直接引用。
举例:Object.method();当前代码执行到这一行,说明是在代码执行的过程中发生了方法的调用。此时该方法会加入到虚拟机栈中,形成自己的栈帧,Object对象本身是在堆中,但是Object对象的引用是在method()方法的栈帧当中。在方法被调用的时候,就会method方法的Object引用找起,找到Object对象所在堆中的地址,由堆中Object对象中存放的类元信息的指针,找到方法区中对应method的方法,方法区中的方法是在类加载时加载的(类加载在解析之前开始)。

方法出口

无论以何种方式返回,都要回到调用该方法的下一行代码。
正常调用完成
异常调用完成:无论以何种方式返回,都要回到调用该方法的下一行代码。

本地方法栈

存放native方法的地方。
出错方式与Java虚拟机栈一样。

线程共享区域(也要根据不同的虚拟机区分,比如HotSpot可以在堆中划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配内存空间的效率)
OutOfMemoryError:堆中没有内存完成实例分配,且无法扩展。

方法区

线程共享区域;
OutOfMemory:方法区无法满足新的内存分配需求时。
存储:类型信息,常量,静态变量,即时编译器编译后的代码缓存。

运行时常量池

Class类文件加载入JVM中,用来存放编译期生成的字面量与符号引用。
特列:运行期也可以将新的常量放入池中(String.intern()).
OutOfMemory:无法申请到内存。

直接内存

不属于JVM运行时数据区的一部分,一般都是指本机的物理内存。

HotSpot虚拟机对象探秘

对象创建

1.首先判断能否在常量池中定位到类。
2.检查该类是否被加载,解析,初始化过。(如果没有则要执行类加载过程)
3.对新生对象分配内存(分配内存的方式)
指针碰撞(规整),空闲列表(不规整)
4.对象空间初始化
5.分配对象头
6.执行init<>
//至此对象创建完成。
对于多线程,为了安全,在分配空间时采用:
1.CAS+失败重试——对分配内存空间的操作进行同步处理。
2.TLAB(Thread Local Allocation Buffer)——将内存分配的动作按照线程不同划分到不同的空间中进行。

对象内存布局

JVM——Java内存区域

对象头

MarkWork

对象头包括:HashCode,GC年龄,锁状态,线程持有的锁,偏向线程ID,偏向时间戳。

类型指针

对象指向他的类元数据的指针,虚拟机通过这个指针来确定该对象属于哪个类。
也就是说存在于堆上的对象存在一个指向对应类型的指针,指向方法区特定的类元信息。

数组长度——数组类型独有

实例数据

对象真正存储的有效信息。

对其填充

不是绝对存在的,仅仅起到占位符的作用。比如,HotSpot要求对象大小是8字节的整数倍。

对象的访问定位

HotSpot主要采用直接指针访问。

句柄

java堆中划分一部分空间,用来存放句柄。栈帧中的reference存储的就是对象的句柄地址,
句柄中包含:

  • 对象实例数据的地址(对象实例就是我们常说的对象Object)
  • 对象类型数据的地址(对象类型就是我们的Class类文件在类加载过程中放入方法区中的类文件信息)。

直接指针

栈帧中的reference存储的就是对象在Java堆中的地址,对象实例数据中存放对象类型数据的地址(也就是在对象实例数据中的指针,指向方法区中(与对象对应的类)Class类文件信息的地址)。

对比

句柄访问的好处:对象被移动时,只会改变句柄中实例数据的指针(也就是修改了对象在Java堆中的地址)。
直接指针访问的好处:速度快,节省了一次指针定位的开销。

JVM——Java内存区域