[Java]《深入理解Java虚拟机》学习笔记-垃圾收集器与内存分配策略

众所周知,Java跟C/C++中有个区别,就是Java程序员可以不用特别对内存分配和垃圾回收进行一些代码上的操作, Java的内存动态分配和垃圾收集技术已经非常成熟,但在需要排查各种内存溢出、内存泄漏的问题时,以及为了让系统有更高的并发量,需要一些监控和调节。所以我们了解JVM的垃圾收集器与内存分配策略是非常有必要的。

一、概述

程序计数器、Java虚拟机栈、本地方法栈随线程而生,随线程而灭,所以我们主要考虑的是Java堆和方法区中的内存分配和垃圾回收。

二、对象已死吗?

对堆进行回收前,我们要确定哪些对象还“活着”,哪些对象已经“死了”。

1、引用计数算法(Reference Counting)

给对象添加一个计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

弊端:很难解决对象之间相互循环引用的问题–当两个不再访问,但是他们互相引用对方,导致计数器不为0,无法被GC回收。

2、可达性分析算法(Reachability Analysis)

主流商用程序语言Java、C#等都是用的这种算法。其通过一系列成为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。就像下图obj4、obj5 、obj6虽然互相有关联,但是他们到GC Roots是不可达的,所以可回收。

可作为GC Roots的对象包括:

-虚拟机栈(栈帧中的本地变量表)中引用的对象

-方法区中类静态属性引用的对象

-方法区中常量引用的对象

-本地方法中JNI(Native方法)引用的对象

3、再谈引用

强引用(Strong Reference):类似“Object obj = new Object();”,只要强引用还在,垃圾收集器则永远不会回收被引用的对象。

软引用(Soft Reference):描述一些有用但并非必须存在的对象。在系统发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收,如果还是不够内存才报OOME。JDK1.2以后提供了SoftReference类来实现软引用。

弱引用(Weak Reference):也是用来描述非必须存在的对象的,强度比软引用弱,被弱引用关联的对象只能存活到下一次垃圾收集发生之前,当垃圾收集器工作的时候,无论内存够不够都会被回收。JDK1.2以后提供了WeakReference类来实现弱引用。

虚引用(Phantom Reference):别名幽灵引用或幻影引用,是最弱的引用关系。虚引用不会对其生存时间构成影响,也无法通过虚引用取得一个实例。设置虚引用关联的唯一一个作用就是能在这个对象被垃圾收集器回收时收到一个系统通知。JDK1.2之后提供了PhantomReference类来实现虚引用。

4、生存还是死亡

即使是可达性分析算法中不可达的对象,他也不一定是“非死不可的”,需要经过两次标记,来宣告一个对象死亡:第一次:对象到GC Roots不可达,第二次:没有覆盖finalize()方法或虚拟机已经执行finalize()方法时,虚拟机认为其“没必要执行”,而被判定有必要的话会被存到F-Queue队列,并由Finalize线程执行。

5、回收方法区

一般来说方法区是没有必要实现垃圾收集的,若要回收,永久代的垃圾收集主要收集两部分内容:废弃常量和无用的类。

废弃常量判定:其他地方没有用常量池中的常量

无用的类判定:同时满足1、该类所有实例都已被回收,2、加载该类的ClassLoader已被回收,3、该类对应的java.lang.Class对象没有在任何地方被引用,无法通过反射访问该类的方法。

三、垃圾回收算法

1、标记-清除算法(Mark-Sweep)

算法分标记和清除两个阶段,首先标记要回收的对象,标记完成后统一回收。

效率低,会有大量不连续内存碎片

2、复制算法(Copying)

可用内存分为大小相等的两块,当一块内存用完,将存活着的对象复制到另一块,然后再把已使用过的内存空间一次清理掉。

效率高,不用考虑空间问题,但把内存空间缩小到原来的一半代价太大。

3、标记-整理算法(Mark-Compact)

与标记-清除算法相似,不同在于其让所有存活的对象都向一端移动,然后直接清除端边界以外的内存。

4、分代收集算法(Generational Collection)

根据对象存货周期的不同将内存划分几块,如Java堆分成新生代和老年代,对划分的这几块的特点选择合适的收集算法。

四、HotSpot的算法实现

1、枚举根节点

GC在枚举根节点时所有Java执行线程都是停止的,HotSpot通过OopMap这个数据结构来检查对象引用的。

2、安全点(Safepoint)

HotSpot没有为每条指令都生成OopMap,只在特定的位置记录,这些位置被称为“安全点”,表示程序不是在所有地方都停顿下来开始GC,只有在安全点才开始。

安全点的选定不能太少让GC等待太久,也不能太多增大运行时负荷,基本上以程序“是否具有让程序长时间执行的特征”为标准进行选定的,程序不太可能因为指令太长而过长时间运行,“长时间执行”最明显特征是指令序列复用,例如方法调用、循环跳转、异常跳转等,这样才会产生安全点。

为了在GC发生时让所有线程(不包括执行jni用的线程)都跑到最近的安全点上来,有两种方案:

抢先式中断(Preemptive Suspension):当GC发生时首先把所有线程全部中断,然后如果发现有线程不在安全点上,就恢复线程,让它跑到安全点上。

主动式中断(Voluntary Suspension):不直接对线程操作,简单设置一个标志,各个线程主动轮询这个标志,发现中断标志为真时就自己中断挂起,轮询标志的地方与安全点是重合的。

3、安全区域(Safe Region)

线程处于sleep状态或blocked状态,这时候线程无法响应JVM的中断请求,无法到达安全点挂起,JVM也不可能等待线程重新分配CPU时间,这种情况就要安全区域来解决了。

安全区域是指在一段代码中,引用关系不会发生变化,在这个区域任意地方GC都是安全的。

在线程执行到安全区域中分代码时,首次标志自己已经进入安全区域了,当在这段时间要执行GC时,就不用管标志自己为安全区域状态的县城了。当线程要离开安全区域时,他要检查系统是否已经完成了根节点的枚举,如果完成则继续执行,否则就等到他获得可以安全离开安全区域的信号为止。

五、垃圾收集器

讨论基于JDK1.7Update14之后的HotSpot虚拟机

1、Serial收集器

单线程收集器,GC时必须暂停其他所有的工作线程(Stop the world)。它依然是虚拟机运行在Client模式下的默认新生代收集器。

2、ParNew收集器

Serial收集器的多线程版本,是许多运行在Server模式下的虚拟机中首选的新生代收集器,一个重要的原因就是目前只有它能够与CMS收集器配合工作。

3、Parallel Scavenge收集器

新生代收集器,使用复制算法,并行的多线程收集器。为了达到一个可控制的吞吐量。吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的壁纸。自适应调节是Parallel Scavenge收集器与ParNew收集器的一个重要区别

4、Serial Old收集器

Serial收集器的老年代版本,单线程收集器,使用“标记-整理”算法。为了给Client模式下的虚拟机使用。如果在Server模式下,在JDK1.5之前与Parallel Scavenge收集器搭配使用,或作为CMS收集器的后备预案,在并发手机发生Concurrent ModeFailure时使用。

5、Parallel Old收集器

Parallel Scavenge收集器老年代版本,多线程及“标记-整理”算法。

6、CMS收集器

是一种以获取最短回收停顿时间为目标的收集器,适合“重视服务响应速度,希望系统停顿时间最短,给用户带来较好体验的需求”,基于“标记-清除”算法,运作分为:初始标记(CMS initial)、并发标记(CMS concurrent mark)、重新标记(CMS remark)、并发清除(CMS concurrent sweep)。初始标记、重新标记任然需要Stop the world。

优点:并发收集,低停顿

缺点:

-CMS收集器对CPU资源非常敏感

-CMS收集器无法处理浮动垃圾

-CMS收集器基于“标记-清理”算法,会产生大量的空间碎片

7、G1收集器(Garbage-First)

G1收集器是当今收集器技术发展的最前沿成果之一,面向服务端应用的垃圾收集器,有以下特点:

-并行与并发:缩短stop the world时间

-分代收集

-空间整合:从整体看是“标记-清除”算法,从局部看是基于“复制”算法。

-可预测的停顿

运作大致可分为:

-初始标记(Initial Marking)

-并发标记(Concurrent Marking)

-最终标记(Final Marking)

-筛选回收(Live Data Counting and Evacuation)

六、内存分配与回收策略

1、对象优先在Eden分配

2、大对象直接进入老年代

3、长期存活的对象将进入老年代

4、动态对象年龄判定

5、空间分配担保

发表评论

电子邮件地址不会被公开。 必填项已用*标注