第二章 java内存区域与内存溢出异常

2.2 运行时数据区域

  1. 程序计数器线程私有,没有OOM,当前执行的java方法则是虚拟机字节码指令的地址,如果是native,则为空(undefined)。
  2. 虚拟机栈线程私有,生命周期与线程相同,每个方法对应一个栈帧(局部变量表、操作数栈、动态链接、方法出口等),方法的执行就是栈帧的出栈和入栈。抛出栈溢出异常(线程申请的栈深度过大)和内存溢出(无法申请到足够的内存来扩展)异常。
  3. 本地方法栈:与虚拟机栈的区分是,为native方法服务。
  4. 线程共享,存放对象的实例、数组,可以划分出多个线程私有的分配缓冲区。存储空间在逻辑上是连续的,但是对于大对象类似于数组对象,可能要求存储空间在物理上连续以方便管理。可以扩展(-Xmx、-Xms),不能扩展则抛出OOM。
  5. 方法区线程共享,存放虚拟机加载的类型信息、常量、静态变量、即时编译产生的代码缓存等。"永久代":实现方法区的方式不同,HotSpot使用永久代来实现方法区,物理上还是存在于堆空间,方便使用HotSpot的垃圾管理器来管理方法区。在java8中,废弃了永久代的概念,在本地内存中实现方法区,称为元空间。这部分的内存回收主要指常量池的回收和类型的卸载,会抛出OOM。
  6. 运行时常量池:属于方法区的一部分。在Class文件中,有一项信息是常量池表,用于存放编译器生成的字面量符号引用(一般还包括翻译出来的直接引用),这部分信息在类加载之后放到方法区的运行时常量池中。除了编译器确定的常量外,运行时产生的常量(String.intern())也会放到这里,会抛出OOM。

2.3 HotSpot虚拟机对象探秘

  介绍HotSpot虚拟机在java堆中管理对象的方法。

  2.3.1 对象的创建(不包括数组和Class对象)

  new SomeObject();

  1. 类加载检查 在java虚拟机遇到new指令时,检查在常量池中能否找到一个类的符号引用,进而检查代表的类是否已经完成加载、解析和初始化,如果没有需要预先执行类的加载。
  2. 分配内存
    1. 指针碰撞:内存划分规整,例如使用Serial、ParNew垃圾回收器回收的内存
    2. 空闲列表:使用CMS回收的内存
    3. 同步问题:解决并发分配内存问题,1.CAS+失败重试 2.预先给每个线程分配缓存(TLAB)
    4. 在分配完内存后,将内存空间(不包括对象头)初始化为零值,使不需要在程序里赋初始值就能访问
  3. 设置对象头 
  4. 执行<init>()构造器方法对对象初始化

   2.3.2 对象的内存布局

  1. 对象头
    1. 第一部分:存储对象运行时的数据(Mark Word):HashCode、GC分代年龄、锁状态标志位(01未锁定、00轻量级锁定、10重量级锁定、11GC标记)、持有锁的线程在栈帧中对当前Mark Word的拷贝的地址、偏向线程ID、偏向时间戳
    2. 第二部分:类型指针,指向该对象所属的类型数据。
    3. 第三部分:如果对象是数组,记录数组的长度
  2. 实例数据
  3. 对齐填充 HotSpot虚拟机规定对象的大小必须是8字节的整数倍

  2.3.3 对象的访问定位

  通过栈的存储的对象引用操作堆上面的具体对象。具体访问的实现方式不在《java虚拟机规范》中限制。

  1. 句柄访问:Java堆中划分一部分内存作为句柄池。对象引用中存储的是句柄地址,句柄中存储了对象实例的地址(堆)和对象类型信息的地址(方法区)。
  2. 直接指针:Java堆中存储的是对象实例地址。在对象实例中,有对象类型信息的指针。

  这两种方式各有优势,使用句柄访问,当对象实例移动时(垃圾回收时),不需要改变对象引用中的句柄地址,而直接指针访问则省去了一次访问。HotSpot主要使用第二种方式来进行对象访问。

 

    

  

posted @ 2020-05-14 20:29  walker993  阅读(99)  评论(0编辑  收藏  举报