jvm笔记-02-垃圾收集器与内存分配策略

面向工资编程 2018-01-20

[TOC]

对象的生存与死亡

如何判定一个对象的生与死

  1. 引用计数法 Reference Counting

    给对象中添加一个引用计数器,被引用加1,引用失效减1,计数器为0则对象可以被回收。
    缺点:很难解决对象之间互相循环引用的问题。

  2. 可达性分析法 Reachability Analysis

    通过一系列 GC Roots 的对象作为起点,向下搜索,遍历路径称为引用链。当一个对象到 GC Roots 不可达时,证明此对象是不可用的。
    GC Roots对象包括:
    1. 虚拟机栈中引用的对象
    2. 方法区中静态常量引用对象
    3. 方法区中常量引用对象
    4. 本地方法栈中引用的对象

死亡面前,各种引用不平等

这样的一类对象:当内存空间足够是,保存在内存之中。若内存空间在垃圾回收之后仍不足,则可以抛弃这些对象。应用场景如系统的缓存功能。

  1. 强引用

    普遍存在的引用,只要引用关系存在,就不会被回收。

  2. 软引用

    还有用但非必须的对象,系统将要发生内存溢出之前,将会把这些对象列入回收范围之内。 SoftReference 类实现。

  3. 弱引用

    描述非必须对象,比软引用更弱一些,被弱引用关联对象只能生存到下一次垃圾收集之前。 WeakReference 类实现。

  4. 虚引用

    幽灵引用或幻影引用,不会对对象生存时间造成影响,也无法通过虚引用获取对象实例。
    为对象设置虚引用关联唯一目的就是能在这个对象被回收时收到一个系统通知。 PhantomReference 类实现。

死亡,不过如此 finalize()方法

可达性分析算法中不可达的对象,到被回收之间至少经历两次标记过程:

  1. 对象在进行可达性分析之后发现没有与 GC Roots的引用链,会被第一次标记并筛选。
    筛选的条件是对象是否有必要去执行 finalize() 方法。
    当对象没有覆盖该方法,或该方法已经被虚拟机调用过,则没必要执行。

  2. 有必要执行 finalize() 方法的对象,被放入 F-Queue 队列中触发该方法。
    这是对象自我拯救的最后一次机会:将自己赋值给某个类变量或成员变量。
    稍后 GC 将对 F-Queue 中对象进行第二次标记,正式宣告对象死亡。

有点像寻梦环游记里的,如果活着的人没人记得你,你就会在亡灵世界里终极死亡。

回收方法区(永久代)

永久代垃圾回收主要是两部分内容:废弃常量与无用的类。

判断常量废弃的条件:

没有任何对象引用常量池中的常量,如字面量、接口、方法、字段的符号引用,垃圾回收时必要时会被回收。

判断类无用的条件:

1. 该类的所有实例已被回收
2. 加载该类的 ClassLoader 已被回收
3. 该类对应的 java.lang.Class对象没有被引用,即无法通过反射访问该类方法。

回收无用的类型,使用 -Xnoclassgc 控制。
在大量使用反射、动态代理、CGLib等字节码框架、动态生成JSP以及OSGI频繁自定义ClassLoader的场景,需要虚拟机具备类卸载功能。

垃圾收集算法

标记-清除算法 Mark-Sweep

原理:

  1. 标记阶段:对于不可达的对象进行标记。
  2. 清除阶段:对两次标记的对象进行回收。

缺点:

  1. 效率问题:标记和清除的效率都不高
  2. 空间问题:会产生大亮不连续的空间碎片,须分配较大对象时,会因为无法找到足够大的内存而提前触发垃圾收集动作。

复制算法 Copying

原理:

将内存划分为两块,每次使用其中一块,使用完时将其中存活对象复制到另一块,将已经使用的内测空间清理掉。

优缺点:

实现简单,运行高效。
可用内存空间变小,且只适用于年轻代。

内存分配:

新生代分为较大的 Eden 空间和两块较小的 Survivor 空间。每次使用 Eden 和 其中一块 Survivor空间。
HotSpot虚拟机默认 EdenSurvivor = 8:1
当回收时 Survivor 空间不足,需要老年代进行分配担保。

标记整理算法 Mark-Compact

原理:

  1. 标记同 标记-清除算法 中的标记阶段。
  2. 整理:让所有存活对象向一端移动,然后清理掉边界以外的内存。

优点:

  1. 解决 复制算法 在对象存活率较高时复制操作效率低问题。适用于老年代。
  2. 不会浪费内存空间。

分代收集算法 Generational Collection

根据对象存活周期不同将内存划分为新生代和老年代。

新生代对象存活少,选用 复制算法
老年代对象存活率高、没有额外空间分配担保,选用 标记-清除算法标记整理算法

hotspot的算法实现

枚举根节点 GC Roots

原理:

作为GC Roots的节点主要在全局性的引用(常量和静态属性)和执行上下文(栈帧的本地变量表)中。

难点:

  1. 方法区可能较大,逐个检查引用效率低。
  2. GC停顿,整个执行系统中引用关系不断变化会影响分析结果准确性。Stop The Word

解决:

  1. 使用一组称为 OopMap 的数据结构,直接得知执行上下文和全局引用的位置。
  2. 使用 OopMap 可快速完成 GC Roots枚举,但前提是枚举时引用关系不会变化。这个使用安全点 Safepoint 解决。

安全点 Safepoint

问题:

GC Roots枚举时可能引用关系变化,或者说,导致 OopMap 内容变化的指令非常多,若为每条指令生成 OopMap,将会需要大量额外空间。

解决:

只在特定位置记录 OopMap 信息,这些位置即安全点。
程序执行到安全点才可以GC。

安全点的选定:

是以程序是否具有让程序长时间执行的特征为标准选定的,特征是指令序列复用,如方法调用、循环跳转、异常跳转等。
意思是在你经常路过的地方堵着你。

如何在GC发生时让所有线程都泡到安全点上停顿下来?

  1. 抢先式中断

    先把所有线程全部中断,如果有线程中断的地方不在安全点上,就恢复线程让它跑到安全点上。

  2. 主动式中断

    当GC需要中断线程时,见设置一个标识,个线程执行时主动轮询该标识,发现为真时就自己中断挂起。轮询标志的地方与安全点重合。

安全区域 SafeRegion

Safepoint 的问题:

若程序不执行如处于 SleepBlocked 状态的线程,无法响应JVM的中断请求。

解决:

安全区域指在一端代码片段中,引用关系不会发生变化。可以看作扩展的 Safepoint
线程执行到 SafeRegion 时标识自己已经进入到 SafeRegion ,若这段时间JVM发起GC,就不用管这样状态的线程了。
在线程离开 SafeRegion 时检查系统是否已经完成根节点枚举,如果没有完成,必须等待收到可以离开 SafeRegion 的信号为止。

垃圾收集器

Serial 串行收集器

原理:

进行垃圾收集工作时,必须暂停其他所有工作线程,即GC线程需要和用户线程串行。

优点:

简单高效,Clent 模式下默认新生代收集器。

缺点:

Stop The Word 停顿时间长。

ParNew(颇牛) 多线程串行收集器

原理:

Serial收集器的多线程版本,GC线程并发执行,暂停所有用户线程。

特点:

ParNew作为新生代收集器 可以和 CMS老年代收集器搭配使用。
是使用 -XX:+UserConcMarkSweepGC选项后的默认新生代收集器,也可以使用 -XX:+UserParNewGC 选项使用它。

Parallel Scavenge 并行的多线程收集器

Parallel Scavenge 收集器目的是达到一个可控制的吞吐量,吞吐量优先收集器。

吞吐量就是CPU运行用户代码时间与CPU总消耗时间的比值。  
吞吐量 = 用户代码时间 / ( 用户代码时间 + 垃圾收集时间 )

CMS等收集器关注点则在缩短停顿时间。

追求短停顿时间和追求可控吞吐量的区别?
GC停顿时间短是牺牲吞吐量和新生代空间来换取的。  
停顿时间短,则可能GC停顿频繁,整体上用户代码时间缩短,吞吐量不高。  
停顿时间短适合用户交互程序;提高吞吐量则高效率利用CPU时间,适合后台计算任务。

Parallel Scavenge 收集器提供了两个参数用于精确控制吞吐量。

  1. -XX:MaxGCPauseMillis 最大垃圾收集停顿时间,大于0。
  2. -XX:GCTimeRatio 直接设置吞吐量大小,大于0,小于100,默认99,允许最大1%的垃圾收集时间。
  3. -XX:+UserAdaptiveSizePoliy 自适应调节策略开关,使用最大停顿时间和吞吐量参数给虚拟机设立一个优化目标。

gc日志

垃圾收集器参数

相关推荐