1、运行时数据区
1.1 PC Register (程序计数器)
(1) 记录当前线程所执行字节码的行号,字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令
(2) 每个线程都有一个独立的程序计数器
(3) 如果是java方法,记录的是正在执行的字节码指令地址;如果是native方法,计数器的值为空
(4) 唯一没有规定OutOfMemoryError的区域
JVM Stack (java虚拟机栈)
(1) 线程私有,生命周期与线程相同
(2) 虚拟机栈描述的是java方法执行的内存模型,每个方法在执行的同时创建一个栈帧(Stack Frame),方法从调用到执行完成,对应栈帧从入栈到出栈
(3) 栈帧:包括局部变量表、操作数栈等
(4) 两种异常:StackOverflowError和OutOfMemoryError
1.3 Native Method Stack(本地方法栈)
(1) 与JVM Stack类似,主要区别是本地方法栈为native方法(c/c++)服务
1.4 Heap(堆)
(1) 所有线程共享的区域
(2) 存放对象实例和数组,是GC管理的主要区域
(3) 通过-Xmx和-Xms控制是否可扩展
(4) 如果堆中没有足够内存,且无法扩展,将抛出OutOfMemoryError异常
1.5 Method Area(方法区)
(1) 所有线程共享的区域
(2) 存放类信息、常量、静态变量、即时编译器编译后的代码等
(3) GC在该区域主要是常量池的回收和类型的卸载,回收效果难尽如人意
(4) 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
1.6 Runtime Constant Pool(运行时常量池)
(1) 属于方法区的一部分
(2) 存放编译期生成的各种字面量和符号引用
(3) 不同供应商可以按自己的需求实现这个区域
1.7 Direct Memory(直接内存)
(1) 不属于jvm运行时数据区
(2) 可以使用native函数库直接分配堆外内存,通过java堆中的DerectByteBuffer对象作为这块内存的引用进行操作
(3) 容易忽略直接内存导致的OutOfMemoryError异常
2、hotspot虚拟机对象探秘
2.1 对象的创建
(1) 类加载检查:虚拟机遇到new指令时,首先检查该指令的参数是否能在常量池中定位到一个类的符号引用,并检查该符号引用代表的类是否已被加载、解析和初始化过,如果没有,必须先进行类加载操作
(2) 为新生对象分配内存,指针碰撞(Serial、ParNew):内存绝对规整,用过的内存放一边,空闲的内存放另一边,分配内存将指针向空闲内存挪动对象大小的空间;空闲列表(CMS):内存不规整,虚拟机通过维护一个列表记录可用内存
(3) 虚拟机创建对象的线程安全问题:一种是CAS+失败重试,另一种是按线程划分到不同空间中,每个线程在堆中预先分配一小块内存TLAB(Thread Local Allocation Buffer),然后在线程的TLAB上分配
(4) 内存分配完成后,需要将分配到的内存空间初始化为零值
(5) 设置对象头,包括这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息
2.2 对象的内存布局
(1) 对象的内存布局分为3块区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)
(2) 对象头包括两部分:第一部分用于存储对象自身的运行时数据(Mark Word),包括哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;另一部分是类型指针(元数据不需要),通过该指针确定对象是哪个类的实例;如果对象是数组,对象头还有一部分需记录数组的长度
2.3 对象的定位访问
(1) 句柄访问:在堆中划分出一块内存作为句柄池,reference中存储的是对象的句柄地址,句柄中包含了对象实例数据和对象类型数据
(2) 直接指针访问:reference存储的是对象地址
(3) 句柄访问的优势是,reference存储的是稳定的句柄地址,当对象被移动(GC时移动对象)时只会改变句柄中的实例数据指针,reference本身不需要修改;直接指针访问的优势是,速度更快,它节省了一次指针定位的开销,hotspot使用的是直接指针访问