一、内存区域、对象创建步骤、对象内存布局、对象访问定位

此笔记出自《深入理解java虚拟机 第二版》,仅做摘要整理,详细了解可购买相关书籍。

内存区域

一、内存区域、对象创建步骤、对象内存布局、对象访问定位

  • 程序计数器:线程私有当前线程所指向的字节码的行号指示器是唯一一个没有规定OOM(OutOfMemoryError)情况的区域;【PS:如果线程当前执行的是一个Java方法,则记录的是正在执行的虚拟机字节码指令地址,如果是native方法,则为空;】

  • 虚拟机栈:线程私有描述Java方法执行,方法执行时会创建栈帧—用于存储局部变量表、操作数栈、动态链接、方法出口等信息;会抛出OOM和*Error两种异常

    • 局部变量表:1、编译期可知的各种基本数据类型–boolean、byte、int等等;2、对象引用类型:引用指针;3、returnAddress地址局部变量表所需的内存空间在编译期完成,进入一个方法时所需要分配的局部变量空间是完全确定的,不会改变。
  • 本地方法栈:和虚拟机栈一样,只是虚拟机栈为Java方法服务,而本地方法栈为Native方法服务

  • 堆:线程共享;虚拟机启动时创建;存放对象实例;会抛出OOM;【PS:堆是垃圾回收的主要区域,所以也会叫做GC堆,从内存回收的角度来看,Java堆还可以细分为新生代和老年代,Eden空间、FromSurvivor空间、To Survivor空间。】

  • 方法区:线程共享;存储已被虚拟机加载的类信息、常量、静态变量即时编译器编译后的代码等数据;会抛出OOM异常

    • 运行时常量池:存放编译期生成的各种字面量和符号引用,类加载后进入方法区的运行时常量池。

对象

创建:4步

1、类加载检查:虚拟机遇到new指令时,先检查指令参数能否在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化,如果没有,则需要进行类加载(类加载过程见第七章)。

2、分配内存:类加载检查通过后,为新生对象分配内存;内存大小在类加载完成时便完全确定;【PS:分配内存的方法有指针碰撞和空闲列表两种,由Java堆是否规整决定,而是否规整又由采用的垃圾收集器是否由压缩整理功能决定。】

3、初始化:将分配的内存空间初始化为零(不包括对象头),保证了对象的实例字段不赋初始值就可以直接使用。

4、必要设置:动刀对象头:设置对象是哪个类的实例,哈希码,GC分代年龄等。

四步下来,在jvm里,对象已经产生,但在java程序里,对象创建才刚开始,因为init方法还没执行,对象还处于默认值状态,执行完init方法进行初始化,对象才成为程序员想象中的样子。

内存布局:3块

分为3块:对象头、实例数据和对齐填充

  • 对象头:存储两部分信息
    • 对象自身运行时数据:哈希码、GC分代年龄、锁状态标志、线程持有锁
    • 类型指针:对象指向它的类袁术的指针,通过这个来确定这个对象是哪个类的实例
  • 实例数据:对象真正存储的有效信息,即代码中定义的各类型字段
  • 对齐填充:并非必然存在,起占位符作用。

访问定位:2种

句柄访问和直接指针

  • 句柄访问:在堆中划出内存作为句柄池,引用类型中存储的就是对象的句柄地址,句柄中包含了对象实例数据和类型数据各自具体的地址信息。好处是引用类型存储的地址是稳定的,对象移动(例如垃圾回收)的时候只改变实例数据指针。

一、内存区域、对象创建步骤、对象内存布局、对象访问定位

  • 直接指针:直接指向对象实例数据,内含指向对象类型数据的指针。好处是速度更快,节省了一次指针定位的开销,看上去只有一丢丢,但是访问对象这个操作是非常频繁的,积少成多。

一、内存区域、对象创建步骤、对象内存布局、对象访问定位