目录
1.程序计数器(Program Counter Register)
前言
参考周志明的《深入理解Java虚拟机》,进行简单的总结记录。本小结总结了《深入理解Java虚拟机》第二章内存区域。
一、运行时数据区域
1.程序计数器(Program Counter Register)
- 线程私有。
- 针对方法内部:字节码调用。
- 可以看作是当前线程执行的字节码的行号指示器。控制着程序运行的顺序、分支、循环、跳转等等。
- 执行Java方法:记录的是字节码指令的地址。
- 执行Native方法:记录的是空(undefined)。
- 内存中唯一没有OOM的区域。
2.虚拟机栈(VM Stack)
- 线程私有。
- 描述的是Java方法执行时线程的内存模型,直观上看就是debug调试的堆栈信息。
- 一个方法从调用到被执行完毕,就是对应一个栈帧从入栈到出栈的过程。
- 栈帧保存了局部变量表、操作数栈、动态链接、方法出口等信息。
- 异常:线程请求栈的深度超过虚拟机允许深度,发生StackOverFlowError;虚拟机栈动态扩展时无法申请到内存,发生OutOfMemoryError。
3.本地方法栈(Native Method Stack)
- 与虚拟机栈类似,只不过是为Native方法服务。
4.Java堆(Heap)
- 线程共享
- 虚拟机中很大的一块内存,保存对象的实例。
- 垃圾回收的主要区域。
- 异常:堆内无法实例分配,且堆无法扩展,发生OOM。
5.方法区(Method Area)
- 线程共享
- 堆的一个逻辑部分,别名:Non-Heap
- 存储内容:虚拟机加载的类、常量、静态变量、即时编译器编译后的代码。
- HotSpot虚拟机对方法区的实现叫做永久代,jdk1.8中,移到了元空间(MetaSpace)。
- 方法区内还有一块内存叫做运行时常量池,存储已被加载的class文件中的常量池表(Constant Pool Table)。当常量池无法申请到内存时,发生OOM。
二、直接内存
1.DirectByteBuffer
- 占用堆外内存,是Native直接调用的机器内存。JVM会在DirectBuffer上直接使用本地IO操作(Native I/O Operations)。
- 好处:避免额外的数据复制,提高IO效率。减低GC的工作量。
- 场景:需要容量较大且长期使用的缓存。需要测试确定是否能获得性能提升。
- MappedByteBuffer:继承DirectByteBuffer,可以将文件内容映射到内存。
2.操作
- 通过存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
- DirectBuffer一般在FullGC时被回收。
3.异常
- 不受参数-Xmx参数控制,设置大小:-XX:MaxDirectMemory=512M
- 受物理内存、SWAP分页限制。
- 如果堆内存Xmx满了,物理内存和分页也用完了,则无法继续申请直接内存(OOM)。
三、对象
1.创建对象
- 内存划分:指针碰撞和空闲列表。
- 指针碰撞:内存规整,只需要移动指针来分配空间。
- 空闲列表:内存不规整,虚拟机需要维护哪些空间是空闲的,找到足够的空间分配。
- 如何选择:取决于Java堆是否规整,Java堆是否规整又取决于GC是否带有空间压缩整理的能力。
2.内存布局
- 对象头Header:Mark Word+Class pointer+array length
- Mark Word:存储对象自身的运行时数据,如hashcode,gc分代年龄、锁标志等。
- Class pointer:类型指针,指向它的类元数据,JVM通过这个指针确定对象时哪个类的实例。
- array length:如果对象是数组,那么对象头还需要额外的空间存储数组的长度。
- 实例数据:对象真正存储的有效数据。
- 对其填充:非必须,占位功能,因为hotspot自动内存管理要求对象起始地址必须是8字节的整数倍,如果不是,则用它填充。
3.访问定位
- 句柄池:Java堆中划出一块内存存储句柄池,引用存储的句柄池的地址,而句柄中存储了对象实例数据和到对象类型数据的指针。
- 优点:稳定,当对象移动(因为gc等)时只需要修改句柄中到对象实例的指针即可。
- 缺点:两次寻址
- 直接指针:reference中存储的就是对象的地址,此时对象中就要存储到对象类型数据的指针了。(HotSpot主要使用)
- 优点:访问对象比较快。