本文为《深入理解Java虚拟机_第三版 周志明》学习笔记
概念:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一,当引用失效时,计数值就减一;任何时刻计数器为零的对象就是不可能再被使用的。但当遇到循环引用时就无法正确处理;
SoftReference
有用但非必须的对象。只要存在着关系,在发生内存溢出异常前,会把这些对象列入回收范围之中进行二次回收,如果回收没有足够的内存,才会抛出内存溢出异常。WeakReference
非必须对象,强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。无论当前内存是否足够,都会回收掉只被弱引用关联的对象。PhantomReference
幽灵引用,最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,为对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到的一个系统通知。思路:通过一系列称为GC Roots
的跟对象作为起始节点集,从这些结点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到根节点间没有任何引用链相连,则证明此对象可再被使用。
对象的若判定为不可达对象,不会立即销毁,至少要经历两次标记过程。如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那将第一次标记,随后再次根据是否有必要执行finalize()方法进行筛选。若对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,则其为“没有必要执行finalize()。当有必要执行的时候,对象将会放置在F-Queue的队列中,并由虚拟机自动建立的低调度低优先级的Finalizer线程去执行他们的finalize()方法。若在finalize()方法中重新与引用链建立关系,则移除即将回收的集合,否则就被销毁了。
由于JDK8以后,方法区中的字符串常量池和静态变量放入堆中,而方法区置于本地内存元空间中。书上说判定一个类型是否被废弃的条件有:
标记所有需要回收的对象,标记完成后,统一回收掉所有被标记的对象。
缺点:1. 执行效率低,遇到大量对象需要回收时,需要进行大量的标记清除动作。2.产生碎片化的内存;
将内存空间分为相等两部分,每次使用其中一块,当内存耗尽,就将存活的对象复制到另一块上面,然后把使用过的空间清理掉。
缺点:当大量的对象均存活,则需要大量拷贝对象,还有就是内存空间缩小了一倍。
针对老年代对象特征提出的回收算法。当内存耗尽时,将活着的对象向内存空间一端移动,然后直接清理掉边界以外的内存。
缺点:老年代回收时都有大量存活的对象。移动对象必须全程暂停用户应用程序才能进行。
构造GC Roots,GC Roots中一般为全局性引用(常量或类静态属性)和执行上下文(栈帧中的本地变量表)中;
一组数据结构,一旦类加载动作完成时,HotSpot会把对象内什么偏移量上是什么类型的数据计算出来。
通过OopMap,JVM可以准确迅速的完成Gc Roots枚举。安全点指的是在某个特定的指令流位置暂停下来开始垃圾收集。
是否具有让程序长时间执行的特征为标准进行选定。
仅停留在理论上
若程序未获得处理器,处于Sleep或者Blocked状态,程序无法响应JVM的中断请求,就需要引入安全区域解决。在这个区域中任意地方开始垃圾收集都是安全的。
当用户线程执行到安全区域里面的代码时,先标记自己进入安全区域,当JVM需要发起垃圾收集时,就不去关注在安全区域的代码。当线程要离开安全区域时,检查虚拟机是否已完成了根节点枚举。否则,用户线程一直等到JVM允许离开的信号时才继续执行。
记忆集就是用于从非收集区域指向收集区域的指针集合的抽象数据结构。
记忆集的一种实现,他定义了记忆集的记录精度,与堆内存的映射关系等。最简单的实现形式可以只是一个字节数组。
CARD_TABLE [this.address >>9] = 0;
每一个元素都对应着其标识的内存区域中一块特定大小的内存块(卡页)。一个卡页的内存通常包含不止一个对象,只要卡页内有一个以上的对象的字段存在着跨代指针,就将数组值变为1,称这个元素变脏。垃圾收集时,将为1的区域加入GC Roots中一并扫描。
写屏障是维护卡表状态的,可看作是虚拟机层面,对卡表写入时的AOP环形通知。在赋值前叫写前屏障,之后叫写后屏障。先检查卡表标记,当卡表元素未被标记时,才将其变脏。
引入三色标记法
白色:表示对象未被垃圾收集器访问过;若扫描完成仍是白色,则表示对象不可达。
黑色:表示对象已被垃圾收集器访问过,且这个对象的所有引用都已扫描。它是安全存活的,如果有其他对象引用指向了黑色对象,无需重新扫描一遍,黑色对象不可能直接指向某个白色对象。
灰色:表示对象已被垃圾收集器访问过,但至少存在一个引用还没被扫描过。
并发可能出现的问题:
赋值器插入了一条或多条从黑色到白色对象的新引用;
增量更新破坏其条件,当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色为跟对象,重新的扫描一次。也就是,黑色对象一旦插入白色对象引用后,就变成了灰色。
赋值器删除了全部从灰色对象到该白色对象的直接引用或间接引用;
原始快照破坏其条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,并发结束后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。也就是,无论引用关系删除与否,都会按照刚刚开始扫描的那一刻的对象图快照来进行搜索。
相比增量更新算法,原始快照搜索能减少并发标记和重新标记阶段的消耗,避免在最终标记阶段停顿时间过长的缺点,但会带来跟踪引用带来的额外负担。
衡量垃圾收集器的三项重要指标是,内存占用,吞吐量,延迟。
单线程的新生代收集器,需要暂停其他线程。推荐在客户端模式下选用,由于没有线程交互的开销,可以获得最高的单线程收集效率。
Serial收集器的多线程并发版本。
新生代并行收集器,与ParNew非常相似,目标是达到可控制的吞吐量;
吞
吐
量
=
运
行
用
户
代
码
时
间
运
行
用
户
代
码
时
间
+
运
行
垃
圾
收
集
时
间
吞吐量=\frac{运行用户代码时间}{运行用户代码时间+运行垃圾收集时间}
吞吐量=运行用户代码时间+运行垃圾收集时间运行用户代码时间
Serial老年代版本,单线程收集器。
是Parallel Scavenge老年代版本。支持多线程并发收集。
以获得最短回收停顿时间为目的的收集器。基于标记-清除出算法。
整个过程的四个步骤:
优势:并发收集,低停顿;
不足:
对处理器资源非常敏感。会占用一部分线程降低总吞吐量。默认启动的回收线程数是 (处理器核心数+3)/4;
无法处理浮动垃圾。在并发标记和并发清理过程中,会产生新的垃圾对象,CMS无法在当次收集中处理掉他们,只好在下一次清理。这部分垃圾称为浮动垃圾。
CMS使用标记清除算法,会产生大量的碎片化空间。
一款主要面向服务端应用的垃圾收集器;使用Mixed GC模式,面向堆内存任何部分来组成回收集进行回收。标准就是哪块内存存放的垃圾数量最多,回收收益最大。整体看是标记整理算法实现,,局部看,两个Region之间是标记复制算法实现。不会产生内存空间碎片。
G1把连续的JAVA堆内存划分为多个大小相等的独立区域(Region);每一个Region可以充当Eden,Survivor,老年代空间。还有Humongous区域,专门用来存储大对象。只要大小超过了一个Region的一半的对象即为大对象。
建立一个可预测的停顿时间模型,他将Region作为单词最小回收单元,每次回收都是Region的整数倍.G1跟踪每个Region中的垃圾堆积的价值(回收获得的空间大小和回收所需时间的经验值),然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间优先处理回收价值大的Rgion。保证了G1收集器在有限时间内获得尽可能高的收集效率。
收集过程的步骤:
对象优先在Eden分配
大对象直接进入老年代
大对象:大量连续内存空间的Java对象。避免在Eden和Survivor间来回复制
长期存活的对象将进入老年代
对象通中有一个年龄计数器,当经过第一次MinorGC后仍存活,并且能被Survivor容纳,就移动到Survivor空格键,将其设置为1岁,每次在Survivor区域中熬过Minor GC 年龄就增加一岁。当15岁时就进入老年代。
动态对象年龄判断
如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可直接进入老年代。
空间担保分配
在MinorGC之前,JVM检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果不成立,继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小。如果大于进行Minor GC,否则,就进行Full GC。