深入理解Java虚拟机 - 内存 读书笔记

Java虚拟机管理的内存包括以下几个区域:

  • 程序计数器 Program Counter Register,比较小,可以认为是当前线程执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取需要执行的下一条指令,分支、循环、跳转、异常处理和线程恢复都依赖这个计数器完成。每个线程都有独立的程序计数器。如果正在执行native方法,该计数器为Undefined。唯一不会OutOfMemoryError的区域。
  • 虚拟机栈,线程私有。Java方法执行的内存模型。每个方法执行的时候会创建一个栈帧。用来存储局部变量表、操作数栈、动态链接、方法出口等信息。方法调用到完成,就是栈帧入站到出站的过程。局部变量表存放原始类型、引用类型和returnAddress。方法运行期间,不会改变局部变量表的大小。如果线程请求的栈深度大于虚拟机允许的深度,抛StackOverflowError。如果可动态扩展的虚拟机栈无法申请足够的内存,抛OutOfMemoryError。
  • 本地方法栈,虚拟机使用的native方法服务。HotSpot的本地方法栈和虚拟机栈合二为一。
  • Java堆,所有线程共享。存放对象实例。在物理上可以不连续。
  • 方法区,各线程共享。存储虚拟机加载的类信息、常量、静态变量、即时编译器编译的代码等数据。
  • 运行时常量池,Runtime Constant Pool,是方法区的一部分。存放编译器生成的字面量和符号引用。
  • 直接内存。用native函数库直接分配堆外内存,通过存储在堆内的DirectByteBuffer作为这块内存的引用进行操作。

对象创建

  • 指针碰撞,Bump the Pointer。如果内存规整。Serial、ParNew等
  • 空闲列表,Free List。如果内存不规整,可以压缩。CMS这种基于Mark-Sweep的

修改指针要考虑并发问题。可以使用CAS保证修改的原子性。也可以使用Thread Local Allocation Buffer,TLAB,在线程的TLAB上分配内存。分配新的TLAB才需要同步锁定。
新分配的内存,除了对象头都是0值。

对象头

包括两部分信息。
1、对象自身的运行时数据。HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。官方叫Mark Word。占一个字。
2、类型指针。即指向它的类元数据的指针,用来确定是哪个类的实例。对于数组,头中要记录数组长度。

访问定位:

  • 句柄-对象移动时,只改变句柄中实例数据的指针。
  • 直接指针

Reference Counting很难解决循环引用的问题。

java、c#和Lisp采用Reachability Analysis。GC Roots作为起始点,向下搜索,走过的路径是Reference Chain。不可达的对象需要回收。

可以作为GC Roots的对象包括:

  • 虚拟机栈中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • native方法引用的对象

引用的分类:

  • Strong Reference,普遍存在,类似Object obj=new Object()
  • Soft Reference,有用但是非必须的对象。系统将要内存溢出时,软应用的对象会被放进回收范围
  • Weak Reference,也是非必须对象。强度比软引用更弱。被弱引用关联的对象只能生存到下次回收之前。
  • Phantom Reference,虚引用,也叫幻影引用。一个对象是否有许引用存在,对其生存周期没影响。也无法通过虚引用去的对象引用。虚引用关联在对象被回收时会收到系统通知。

可达性分析后不可达的对象。被标记,并筛选是否要执行finalize()方法。需要执行的被放进F-Queue。由虚拟机维护的Finalizer线程执行-会触发,但不会等待运行结束。

GC算法

  • 标记-清除,Mark-Sweep。基础算法,后续算法对其有改进。两个不足:效率不高,空间碎片。
  • 复制算法。内存分两块,用完一块,就将活着的拷贝到另一块,此块清空。现在一般把内存分成一个较大的Eden,两个较小的Survivor。每次使用Eden和其中一个Survivor。回收时,将Eden和Survivor中活着的拷贝到没用的Survivor。
  • 标记-整理,Mark-Compact。标记后不直接清理,而是让存活的对象都向一端移动,然后清理掉边界外的内存。
  • 分代收集,Generational Collection。比如分成新生代和老年代。新生代一般用复制算法,老年代一般用标记-清除或者标记-整理。

HotSpot

GC Roots主要在全局性的引用和执行上下文中。
可达性分析要在确保一致性的快照中进行。即使CMS,此阶段也要停顿。
使用OopMap,可以知道哪儿有对象引用。
没有为每条指令生成OopMap,只有执行到特定位置-Safepoint-才能停下来GC。
方法调用、循环跳转、异常跳转这些地方才会产生Safepoint。
抢先式中断:GC时,所有用户线程都中断,如果线程没停在安全点,就继续执行,到安全点再停下来。

如果线程处于Sleep或者Blocked状态,就无法响应JVM的中断请求。这就需要Safe Region解决,安全区域是指一段代码中,引用关系不会发生变化。

CMS:

  • 初始标记(CMS initial mark)-GC Roots能直接关联到的对象
  • 并发标记(CMS concurrent mark)
  • 重新标记(CMS remark)-修正
  • 并发清除(CMS concurrent sweep)

其中,初始标记和重新标记需要Stop The World。
CMS对CPU敏感。
无法处理浮动垃圾(并发清理阶段新产生的垃圾)。可能出现Concurrent Mode Failure,导致Full GC。
容易导致空间碎片,可能导致Full GC。

G1(分region,化整为零。不过,region也不是孤立的):

  • 初始标记(Initial Marking)-停顿
  • 并发标记(Concurrent Marking)
  • 最终标记(Final Marking)-停顿
  • 筛选回收(Live Data Counting and Evacuation)

分配和回收策略

优先在Eden分配。空间不够就Minor GC。
大对象直接进老年代。
长期存活的进老年代。
动态对象年龄判断。如果Survivor中相同年龄对象总和大于Survivor空间的一半,直接进老年代。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值