《深入理解Java虚拟机:JVM高级特性与最佳实践》笔记(一)

Java 内存区域与内存溢出异常

  • 运行时数据区
    • 所有线程共享的数据区
      • 方法区(Method Area)
      • 堆(Heap)
      • 执行引擎
      • 本地库接口
    • 线程隔离的数据区
      • 程序计数器(Program Counter Register)
      • 本地方法栈(Native Method Stack)
      • 虚拟机栈(VM Stack)

程序计数器

当前线程所执行的字节码的行号指示器。如果执行Java方法,记录正在执行的虚拟机字节码指令的地址;如果执行Native方法,值为Undefined。唯一一个在Java虚拟机规范中没有规定任何OOM情况的区域。

Java虚拟机栈

每个Java方法执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表所需内存在编译期间完成分配,运行期间不会改其大小。

StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度时

OutOfMemoryError:虚拟机栈可以动态扩展,扩展时无法申请到足够的内存时

本地方法栈

类似虚拟机栈,区别是执行虚拟机用到的Native方法。方法使用的语言、使用方式与数据结构没有强制规定,可自由实现。

Java堆

存放对象实例,虚拟机启动时创建。随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术使得有些对象不会分配到堆上。

垃圾收集器管理的主要区域,也称“GC堆”(Garbage Collected Heap)。

可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。如果没有内存完成实例分配也无法扩展时将抛出OutOfMemoryError。

方法区

用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码数据。内存回收目标主要是针对常量池的回收和对类型的卸载。

当内存无法满足分配需求时将抛出OutOfMemoryError。

运行时常量池

方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

无法再申请到内存的时候会抛出OutOfMemoryError。

直接内存(Direct Memory)

JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,避免在Java堆和Native堆中来回复制数据。

受机器总内存大小以及处理器寻址空间、操作系统的限制,超出时将导致动态扩展出现OutOfMemoryError。

HotSpot虚拟机对象探秘

对象的创建

  1. 遇到new指令
  2. 检查是否能在常量池中定位到一个类的符号引用
  3. 检查类是否已被加载、解析和初始化过
  4. 分配内存,内存大小在类加载完成后便完全确定
  5. 设置对象头(Object Header)
  6. 由字节码中是否跟随Invokeespecial指令决定,执行new指令之后会接着执行方法。

分配堆内存,如果Java堆中内存是绝对规整的,使用“指针碰撞(Bump the Pointer)”分配方式;否则使用“空闲列表(Free List)”方式。

并发分配两种方案,一种对分配内存空间的动作进行同步处理——实际采用CAS配上失败重试的方式保证更新操作的原子性;另一种是本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。TLAB用完并分配新的TLAB时才需要同步锁定。

对象的内存布局

三块区域:对象头(Object Header)、实例数据(Instance Data)和对齐填充(Padding)。

对象头包括两部分,第一部分存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,官方称它为“Mark Word”。另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

实例数据部分是对象真正存储的有效信息。

对齐填充并不是必要的,只是占位符,使对象的大小必是8字节的整数倍。

对象的访问定位

主流的访问方式有两种,使用句柄和直接指针。

  1. 使用句柄则Java堆专门划分一块内存作为句柄池,reference中存储对象的句柄地址,句柄中包含对象实例数据与类型数据各自的具体地址信息。好处:对象移动时,reference不需要修改。
  2. 直接指针则Java堆对象的布局就需要放置访问对象类型数据的指针。优点:访问速度快。