Hunter的博客

读《深入理解Java虚拟机》-Java内存区域

读书笔记

运行时数据区域

运行时数据区域

  • 程序计数器

    • 一块较小的内存空间,是当前线程所执行的字节码的行号指示器,字节码解释器工作室就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成
    • 这块内存区域为线程私有,每条线程都有一个独立的程序计数器,各线程之间计数器互不影响
    • 此内存区域是Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
  • Java虚拟机栈

    • 线程私有,生命周期同线程相同
    • 每个方法从调用到执行完成的过程,对应着一个栈帧在虚拟机栈中入栈到出栈的过程
    • 栈帧包含局部变量表、操作数栈、动态链接、方法出口等信息
    • Java虚拟机规范中对这个区域规定了两种异常状况:
      • StackOverflowError:如果线程请求的栈深度大于虚拟机所允许的深度,抛出此异常
      • OutOfMemoryError:如果虚拟机栈扩展时无法申请到足够的内存,抛出此异常
  • 本地方法栈

    • 作用与Java虚拟机栈类似,只是本地方法栈为虚拟机使用到的Native方法服务
    • 同样也会抛出StackOverflowError和OutOfMemoryError异常
  • Java堆

    • 所有线程共享的区域
    • 所有对象实例和数组在堆上进行分配内存
    • 细分为:新生代、老年代;或Eden空间、From Survivor空间、To Survivor空间
    • 无法扩展堆大小时会抛出OutOfMemoryError异常
  • 方法区

    • 所有线程共享的区域
    • 存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
    • Not-Heap(非堆)、永久代
    • 对于HotSpot虚拟机,有放弃永久代并逐步改为采用Native Memory来实现方法区的规划,在目前已经发布的JDK1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出了。可以看测试结果
    • 无法满足内存分配需求时,将抛出OutOfMemoryError异常
  • 运行时常量池

    • JDK1.7以前是方法区的一部分,JDK1.7以后是Java堆的一部分
    • 存放字面量和符号引用
    • 可以通过String类的intern()方法动态生成新的常量并放入池中
    • 无法再申请到内存时会抛出OutOfMemoryError异常
  • 直接内存

    • 不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域
    • NIO引入了一种基于通道(Channel)和缓冲区(Buffer)的I/O方式,可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在java堆和Native堆中来回复制数据
    • 受本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限制,会抛出OutOfMemoryError异常

hotspot虚拟机对象探秘

  • 对象的创建

    在语言层面上,创建对象通常仅仅是一个new关键字而已,而在虚拟机中,对象(普通java对象)的创建会经过如下步骤:

    1. 虚拟机遇到一条new指令时,首先检查这个指令的参数能否在常量池中定位到一个类的符号引用
    2. 检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程
    3. 类加载检查通过后,虚拟机将为新生对象分配内存(对象所需内存的大小在类加载完成后便可完全确定)
    4. 修改内存指针,并发下会出现线程安全,虚拟机采用了CAS或TLAB(本地线程分配缓冲)来解决
    5. 虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例、如何找到类的元数据信息、对象的哈希码、对象的GC分代年龄、是否启用偏向锁等信息,这些信息都是存放在对象头中
    6. 完成上面的工作,从虚拟机的视觉来看,一个新的对象已经产生了,但是从Java程序的视觉来看,还需要执行方法来完成对象的初始化,这样一个真正可用的对象才算完全产生出来
  • 对象的内存布局

    • 对象头(Header)

      1. 存储自身的运行数据:如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
      2. 类型指针,即对象指向它的类元数据的指针
    • 实例数据(Instance Data)

      1. 对象真正存储的有效信息,也是在程序代码中定义的各种类型的字段内容
    • 对齐填充(Padding)

      1. 不是必然存在,也没有特别的含义,仅仅起着占位符的作用
      2. 由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍,而对象头部分刚好是8字节的整数倍,因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全
  • 对象的访问定位

    Java程序是通过栈上的reference数据来操作堆上的具体对象,由于reference类型在Java虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,所以对象访问方式也是取决于虚拟机实现而定的。

    • 句柄访问
      通过句柄访问对象

    • 直接指针
      通过直接内存访问对象

      两种对象访问各有优缺点,虚拟机Sun HotSpot使用的是第二种方式进行对象访问的