文章目录
垃圾回收算法与垃圾回收器
- 学习垃圾回收的意义
1 面试需要;
2 GC对应用的性能是有影响的;
3 写代码有好处
栈:栈中的生命周期是跟随线程,所以一般不需要关注;
堆:堆中的对象是垃圾回收的重点
方法区:这一块也会发生垃圾回收,不过这块的效率比较低
判断对象的存活
- 引用计数法
给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。(Python在用,但主流虚拟机没有使用)
优点:快,方便,实现方便
缺点:对象互相引用时,很难判断对象是否该回收
- 可达性分析(使用)
来判定对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
GC Roots | |
---|---|
1 | 当前虚拟机栈中局部变量表中的引用的对象 |
2 | 当前本地方法栈中局部变量表中的引用的对象 |
3 | 方法区中类静态属性引用的对象 |
4 | 方法区中的常量引用的对象 |
- finalize
finalize可以完成对象的拯救,但是JVM不保证一定能执行
各种引用
传统定义:Reference中存储的数据代表的是另一块内存的起始地址。
名称 | 发生GC条件 | 运用 |
---|---|---|
强引用 | 不会 | |
软引用 | 垃圾回收器在内存充足的时候不会回收它,而在内存不足时会回收它 | 创建缓存 |
弱引用 | 垃圾回收器在扫描到该对象时,无论内存充足与否,都会回收该对象的内存。 | WeakHashMap、ThreadLocal |
虚引用 | 任何时候都有可能 | 虚引用主要用来跟踪对象被垃圾回收器回收的活动 |
强引用
一般的Object obj = new Object() ,就属于强引用。
代码片段:
运行结果:
jvm参数:
软引用(SoftReference)
代码片段:
运行结果:
jvm参数:
弱引用(WeakReference)
代码片段:
运行结果:
虚引用(PhantomReference)
幽灵引用,最弱,被垃圾回收的时候收到一个通知
如果一个对象只具有虚引用,那么它和没有任何引用一样,任何时候都可能被回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动
GC
- 部分VM参数:
-Xms 堆区内存初始内存分配的大小
-Xmx 堆区内存可被分配的最大上限
-XX:+PrintGCDetails 打印GC详情
-XX:+HeapDumpOnOutOfMemoryError 当堆内存空间溢出时输出堆的内存快照
-Xmn 新生代大小配置参数的优先级
-XX:SurvivorRatio 2个Survivor区和Eden区的比值
- Minor GC
特点: 发生在新生代上,发生的较频繁,执行速度较快
触发条件: Eden区空间不足\空间分配担保
- Full GC
特点: 主要发生在老年代上(新生代也会回收),较少发生,执行速度较慢
触发条件:
1.调用 System.gc()
2.老年代区域空间不足
3.空间分配担保失败
4.JDK 1.7 及以前的永久代(方法区)空间不足
5.CMS GC处理浮动垃圾时,如果新生代空间不足,则采用空间分配担保机制,如果老年代空间不足,则触发Full GC
垃圾回收算法
- 复制算法(Copying)
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要按顺序分配内存即可。
注意:内存移动是必须实打实的移动(复制),不能使用指针
优点:实现简单,运行高效
缺点:只是这种算法的代价是将内存缩小为了原来的一半
- 标记-清除算法(Mark-Sweep)
过程:
1.首先标记所有需要回收的对象
2.统一回收被标记的对象
优点:利用率百分之百
缺点:
1.效率问题,标记和清除效率都不高
2.标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
- 标记-整理算法(Mark-Compact)
首先标记出所有需要回收的对象,在标记完成后,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
优点:利用率百分之百
缺点
标记和清除的效率都不高
效率相对标记-清除要低
效率:
复制算法>标记-清除算发>标记-整理算法
垃圾回收器
分代收集
根据各个年代的特点选取不通的垃圾回收算法
新生代:使用复制算法
老年代:使用标记-整理或者标记清除算法
- jps -v 显示当前使用的垃圾回收器
表示使用的是CMS
各种垃圾回收器
并行:垃圾收集的多线程的同时进行。
并发:垃圾收集的多线程和应用的多线程同时进行。
注:吞吐量=运行用户代码时间/(运行用户代码时间+ 垃圾收集时间)
垃圾收集时间= 垃圾回收频率 * 单次垃圾回收时间
单线程 | 多线程 | 采用算法 | |
---|---|---|---|
新生代 | Serial | ParNew、Parallel Scavenge | 复制回收算法 |
老年代 | Serial Old | CMS、Parallel Old | 标记-整理、标记-清除(CMS特有) |
收集器类型 | 单线程 | 并行、并发(CMS特有) |
-
Serial/Serial Old
最古老的,单线程,独占式,成熟,适合单CPU 服务器 -
ParNew
和Serial基本没区别,唯一的区别:多线程,多CPU的,停顿时间比Serial少
除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作。 -
Parallel Scavenge(ParallerGC)/Parallel Old
关注吞吐量的垃圾收集器,高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
- Concurrent Mark Sweep (CMS)
收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
整个过程分为4个步骤,包括:
- 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿(STW -Stop the world)。
- 并发标记:从GC Root 开始对堆中对象进行可达性分析,找到存活对象,它在整个回收过程中耗时最长,不需要停顿。
- 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿(STW)。这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。
- 并发清除:不需要停顿。
优点:
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
缺点:
CPU资源敏感:因为并发阶段多线程占据CPU资源,如果CPU资源不足,效率会明显降低。
会产生空间碎片:标记 - 清除算法会导致产生不连续的空间碎片
浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为“浮动垃圾”。
由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。
在1.6的版本中老年代空间使用率阈值(92%)
如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
- G1垃圾回收器
内部布局改变
G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。
-
特点
空间整合: 不会产生内存碎片
算法:标记—整理 (humongous) 和复制回收算法(survivor)。可预测的停顿
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
G1把内存“化整为零”的思路,理解起来似 -
G1 GC模式
Young GC
选定所有年轻代里的Region。通过控制年轻代的region个数,即年轻代内存大小,来控制young GC的时间开销。(复制回收算法)
Mixed GC
选定所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。
Mixed GC不是full GC,它只能回收部分老年代的Region。如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap。所以我们可以知道,G1是不提供full GC的。 -
全局并发标记
初始标记:仅仅只是标记一下GC Roots 能直接关联到的对象,并且修改TAMS(Nest Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可以的Region中创建对象,此阶段需要停顿线程(STW),但耗时很短。
并发标记:从GC Root 开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行。
最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程(STW),但是可并行执行。
筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。
G1 GC主要的参数
参数 | 含义 |
---|---|
-XX:G1HeapRegionSize=n | 设置Region大小,并非最终值 |
-XX:MaxGCPauseMillis | 设置G1收集过程目标时间,默认值200ms,不是硬性条件 |
-XX:G1NewSizePercent | 新生代最小值,默认值5% |
-XX:G1MaxNewSizePercent | 新生代最大值,默认值60% |
-XX:ParallelGCThreads | STW期间,并行GC线程数 |
-XX:ConcGCThreads=n | 并发标记阶段,并行执行的线程数 |
-XX:InitiatingHeapOccupancyPercent | 设置触发标记周期的 Java 堆占用率阈值。默认值是45%。这里的java堆占比指的是non_young_capacity_bytes,包括old+humongous |
垃圾回收器的重要参数(使用-XX:)
参数 | 描述 |
---|---|
UseSerialGC | 虚拟机运行在Client模式下的默认值,打开此开关后,使用 Serial+Serial Old 的收集器组合进行内存回收 |
UseParNewGC | 打开此开关后,使用 ParNew + Serial Old 的收集器组合进行内存回收 |
UseConcMarkSweepGC | 打开此开关后,使用 ParNew + CMS + Serial Old 的收集器组合进行内存回收。Serial Old 收集器将作为 CMS 收集器出现 Concurrent Mode Failure 失败后的后备收集器使用 |
UseParallelGC | 虚拟机运行在 Server 模式下的默认值,打开此开关后,使用 Parallel Scavenge + Serial Old(PS MarkSweep) 的收集器组合进行内存回收 |
UseParallelOldGC | 打开此开关后,使用 Parallel Scavenge + Parallel Old 的收集器组合进行内存回收 |
SurvivorRatio | 新生代中 Eden 区域与 Survivor 区域的容量比值,默认为8,代表 Eden : Survivor = 8 : 1 |
PretenureSizeThreshold | 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 |
MaxTenuringThreshold | 晋升到老年代的对象年龄,每个对象在坚持过一次 Minor GC 之后,年龄就增加1,当超过这个参数值时就进入老年代 |
UseAdaptiveSizePolicy | 动态调整 Java 堆中各个区域的大小以及进入老年代的年龄 |
HandlePromotionFailure | 是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个 Eden 和 Survivor 区的所有对象都存活的极端情况 |
ParallelGCThreads | 设置并行GC时进行内存回收的线程数 |
GCTimeRatio | GC 时间占总时间的比率,默认值为99,即允许 1% 的GC时间,仅在使用 Parallel Scavenge 收集器生效 |
MaxGCPauseMillis | 设置 GC 的最大停顿时间,仅在使用 Parallel Scavenge 收集器时生效 |
CMSInitiatingOccupancyFraction | 设置 CMS 收集器在老年代空间被使用多少后触发垃圾收集,默认值为 68%,仅在使用 CMS 收集器时生效 |
UseCMSCompactAtFullCollection | 设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理,仅在使用 CMS 收集器时生效 |
CMSFullGCsBeforeCompaction | 设置 CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理,仅在使用 CMS 收集器时生效 |