Java内存模型

1. java运行时数据区域

程序计数器

线程私有。
当前线程锁执行的字节码的行号指示器。

Java虚拟机栈

线程私有。
用于记录Java方法的执行,存储方法的局部变量表、操作数栈、动态链接、方法出口等信息。

本地方法栈

线程私有。
用于记录Native方法的执行。

Java堆

线程共享。
用于存放对象实例,细分为新生代(生命周期短)和老年代(生命周期长)。

方法区

线程共享。
存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。(可以认为是永久代 PermGen)
(字符串常量池 JDK 1.7 开始从方法区移除改放到堆中)
在HotSpot中方法区可以认为是永久代(PermGen)。

元空间(jdk 1.8)

从JDK1.7 开始,永久代开始慢慢被移除,部分存储在永久代的数据转移到了Heap或Native Heap。
从JDK1.8 开始,已经不存在永久代,而是用元空间(Metaspace)进行代替。元空间和永久代最大的区别在于元空间不在虚拟机中,而是使用本地内存。

运行时常量池

包含在方法区中。
存放编译期生成的各种字面量和符号引用。

直接内存

堆外内存。

2. HotSpot虚拟机对象简介

对象创建流程:
  1. 判断是否在常量池中,或者进行类加载
  2. 为对象分配内存
    • 策略:指针碰撞或空闲列表
    • 线程安全策略:
      • CAS配上失败重试保证操作的原子性
      • 本地线程分配缓冲(Thread Local Allocation Buffer),从TLAB中分配
  3. 对象分配到的内存空间初始化为零值
  4. 对对象进行必要设置(对象头信息填充)
  5. 执行init方法进行字段赋值
对象内存布局
  • 对象头
    • 对象自身运行时数据
    • 类型指针,指向类元数据
  • 实例数据
  • 对其填充
对象访问定位

通过栈中的reference数据来操作堆上的具体对象。访问方式分为以下两种:

  • 使用句柄(指向句柄池,句柄记录实例和类型数据)
  • 直接指针

3. JVM异常

  • OutOfMemoryError
    • Java堆溢出(内存泄露或内存溢出)
    • 方法区和运行时常量池溢出
    • 本机直接内存溢出
  • StackOverFlowError
    • 虚拟机栈或本地方法栈溢出(这类错误在Heap Dump中不会发现异常,OOM之后Dump文件比较小,如果直接或间接使用过NIO可能是这方面的问题。)