jvm主要知识点小结之垃圾回收及面试题

什么是垃圾?
在运行程序中没有任何指针指向的对象
一 垃圾回收算法
1.引用计数算法(在java中不使用,了解即可)。
对每个对象保存一整个引用计数器属性,用于记录对象被引用的情况。
优点:
1.实现简单,垃圾回收对象便于辨识
2.判定效率高,无回收延迟。
缺点
1.单独字段存储计算器,增加了存储空间开销
2.每次赋予值都要重新更新计数器,伴随加、减法操作,增加了时间开销。
*3.无法处理循环引用的情况。(最严重的问题,直接导致被弃用)
2.可达性分析算法(或根搜索算法、追踪性垃圾收集)
解决了循环引用问题,防止内存泄漏
根:GC Roots
判断方法:一个指针,保存了堆内存里的对象,自己又不存放在堆内存
包括:
1.虚拟机栈中的引用对象(线程中使用的参数、局部变量等)。
2.本地方法栈内JNI(本地方法)引用的对象。
3.静态变量。
4.常量引用对象(如字符串常量池中的引用)。
5.同步锁synchronized中持有的对象。
6.虚拟机内部引用(基本数据类型对应的class对象、异常、系统类加载器)
7.JMXBean、JVMTI注册的回调、本地代码缓存等。
3.标记-清除算法(非移动式)
标记:根节点遍历、标记所有被引用的对象,一般是在对象的Header中记录为可达对象。
清除:对堆中所有内存线性遍历,没有被标记的是垃圾对象
优点:简单。
缺点:效率低;STW(stop the world);清理后内存不连续,产生空间碎片,需要维护一个空闲列表。
什么是清除?把垃圾对象的地址放到空闲列表,下次再有对象时,进行覆盖,而不是直接删除。
4.复制算法(可结合一下新生代s0/s1的回收)
① 将活着的内存空间分为两块,每次只使用一块。
② 垃圾回收时将正在使用的内存中存活的对象复制到未被使用的内存块中,之后再清除正在使用的内存块中的所有对象。
③ 交换两个内存角色,最后完成垃圾回收。
优点:高效、连续空间。
缺点:占内存;G1收集器维护了大量的region GC,意味着GC需要维护引用关系,加大了内存占用和时间开销。
另外:如果存活的对象过多,则效率会很低。

5.标记-压缩(又叫标记-整理或标记-清除-整理)算法(移动式)
第一阶段:标记所有被引用的对象
第二阶段:将所有存活的对象压缩到内存的一端
第三阶段:回收边界外所有的空间
优点:1.消除了标记清除算法内存碎片化的问题;2.消除了复制算法内存减半的代价问题
缺点:1.效率较低;2.移动对象时,需要调整引用地址;3.移动过程中需要STW;
图示
jvm主要知识点小结之垃圾回收及面试题
文章图片


三种算法对比

Mark-Sweep 标记-清除 Mark-Compact 标记-压缩 Copying 复制
速度
空间开销 少(碎片化) 少(无碎片) 活对象2倍大小(无碎片)
移动对象


分代收集的算法思想
①.新生代代:
区域较小,生命周期短,存活率低,回收频繁——>复制算法
②.老年代:
区域大,生命周期长,存活率低,回收不如新生代频繁 ——>标记-清除 和 标记-压缩混合实现
mark标记阶段的开销与存活对象数量成正比。
sweep清楚阶段开销与管理区域大小成正比。
compact压缩整理阶段开销和存活对象数量成正比。
(CMS收集器为解决标记-清楚的碎片化问题,引入Serial Old补偿措施,执行Full GC)

6.增量收集算法:
解决STW问题,使系统稳定。
让垃圾收集线程和应用线程交替执行。
缺点:垃圾回收的总体成本上升,系统吞吐量下降。
7.分区算法:
解决GC停顿时间问题。
将大的内存分割成多个小块,根据目标的停顿时间,每次合理回收若干小区间,而不是整堆收集。
分区算法将按对象的生命周期的长短将整个堆空间划分成连续的不同小区间,每个小区间都独立使用,独立回收。
优点:可以控制一次回收多少个小区间。
8.关于System.gc()的理解
1.一般无需手动调用
2.通过System.gc()或Runtime.getRunTime().gc()的调用,会显式触发Full GC;
对老年代和新生代的回收,尝试释放被丢弃对象占用的空间。
(jvm的免责声明:无法保证对垃圾回收器的调用一定会执行)
System.runFinalization() 强制调用使用引用的对象的finalize()方法
finalize() :一个对象只能调用一次,将可复活的对象进行复活,是对象复活的最后机会。
可能复活的对象:带回收对象在finalize()方法中与引用链上的一个对象建立了联系。
10.内存泄露举例(面试)
①单例模式:
单例模式的生命周期和应用程序的一样长(static),所以单例程序中,如果持有对外部对象引用的话,这个外部对象不能被回收。
②.一些提供了close()的资源未关闭。如数据库、网络连接、io等。

11.再谈并发和并行
并发:多个事情在同一时间段内发生,多个任务互相抢占资源。
并行:多个事情在统一时间点上同时发生,多个任务不互相抢占资源。
只有在多个CPU或一个CPU多核的情况下,才会并行,否则看似同时发生的事情都是并发的。
安全点:
特定位置停顿下来开始GC,根据“是否1具有让程序长时间执行的特征”为标准,选一些执行时间较长的指令作为safe point,如方法的调用、循环跳转、异常跳转等,检查所有线程都跑到最近的安全点停顿下来。
主动式中断:
设置中断标志,各线程运行到safe point时,主动轮询这个标志,如标志为真,则将自己进行中断挂起。
安全区域:
一段代码片段中,对象的引用关系不会发生变化,在这个区域中任何位置开始GC都是安全的(防止sleep或Blocked导致长时间中断无法响应)

二 引用
按引用强度排名:
强引用 > 软引用 > 弱引用 > 虚引用强度递减
所有的引用父类为Reference
强引用:new 对象都是强引用,程序中绝大多数都是强引用,对象可被访问,即便OOM也不回收。
软引用:对象可被访问,当内存不足时,进行垃圾回收。
声明方式:
Object obj = new Object(); //声明强引用 SoftReference sf = new SoftReference(obj); obj = null; //销毁强引用
弱引用:对象可被访问,有垃圾回收就会被回收。
声明方式:
Object obj = new Object(); //声明强引用 WeakReference wr = new WeakReference(obj); obj = null; //销毁强引用
WeakHashMap底层就是软引用。
*MyBatis框架中的缓存,底层用到了很多软、弱引用。
虚引用:
几乎可以当作没有被引用,只是这个对象在被系统回收时,会收到一个系统通知。虚引用中的对象不能被获取。
Object obj = new Object(); //声明强引用 ReferenceQueue phantomQueue = new ReferenceQueue(); //需要维护一个引用队列 PhantomQueue pf = new PhantomReference(obj,phantomQueue); obj = null; //销毁强引用
*终结器引用:用以实现对象的finalize()方法。

三 CMS收集器
1.评估GC的性能指标
①吞吐量:运行用户代码的时间 占 运行时间的比例;在0~1范围内,越大越好。
②垃圾收集开销:吞吐量的补数,垃圾收集器用时与运行时间比。
【jvm主要知识点小结之垃圾回收及面试题】③暂停时间:执行垃圾回收时,程序工作线程被暂停的时间。
④收集频率:相对于应用程序线程的执行,收集操作发生的频率。
⑤快速:一个对象从诞生到被回收所经历的时间。
标准:最大化吞吐量的情况下,降低停顿时间。
2.回收器分类:
串行回收器:Serial,Serial Old
并行回收器:ParNew,Parallel Scavenge、Parallel Old
并发回收器:CMS、G1
新生代:Serial、ParNew、Parallel Scavenge
老年代:Serial Old、Parallel Old、CMS
整堆:G1
3.常用参数:
查看垃圾收集器
-XX:PrintCommandLineFlags
jinfo -flag 回收器参数 进程id
ParNew:
-XX:+UseParNewGC:手动指定那个并行回收(只新生代)。
-XX:ParallelGCThreads:限制线程数,默认和CPU相同线程。
CMS:(在jdk9中被标记过时,在jdk14中已完全被移除)
-XX:+UseConcMarkSweepGC:手动指定使用CMS。
-XX:CMSInitiatingOccupanyFraction:
设置内存使用率阈值。
默认jdk5及以前为68,jdk6及以后为92,有效降低了Full GC的执行次数。
-XX:+UseCMSCompactAtFullCollection:在Full GC结束后进行空间压缩整理,避免碎片化。
-XX:CMSFullGCsBeforeCompaction:设置在多次Full GC后,对内存空间进行压缩整理。
默认jdk5及以前为68,jdk6及以后为92,有效降低了Full GC的执行次数
-XX:ParallelCMSThreads:CMS的线程数,默认(ParallelGCThreads+3)/4
4.CMS收集器的收集过程:
1.初始标记阶段:仅对GC Roots能直接关联的对象进行标记,速度快,会STW。
2.并发标记阶段:从GC Roots的直接关联对象整个遍历,耗时长,但不需要停止用户线程。
3.重新标记阶段:修正并发标记阶段期间,因用户继续运作而导致标记变动的那一部分,会STW。
4.并发消除阶段:清理,与用户线程并发。
使用的是标记清除-算法,因为用户线程正在执行,对象的地址不能修改。
缺点:
1.内存碎片。
2.对CPU资源敏感,因占用了一部分线程,导致应用程序变慢。
3.无法处理浮动垃圾:并发标记阶段产生的新的垃圾对象无法标记,所以不能及时回收。
小结:最小化使用内存和并行开销:Serial GC + Serial Old
最大化应用吞吐量:Parallel GC + Parallel Old
最小化中断或停顿时间:CMS(仅老年代)+ParNew(仅新生代)

四 G1收集器
目标:在延迟可控的情况下,获取尽可能高的吞吐量。
1.G1 GC的特点:
①.并行:多个GC线程同时工作,用户线程STW;并发:交替执行,部分工作可以和应用程序同时执行,一般不会在整个回收阶段发生完全阻塞应用程序的情况。
②.分代收集:将堆空间分为若干区域(region),包含了从逻辑上的年轻代和老年代,兼顾了年轻代和老年代。
③.空间整合:region之间是复制算法,整体上是标记-压缩,解决了内存碎片化。
④.可预测的停顿时间模型(软实时):每次根据允许的收集时间,优先回收价值最大的Region,以在有限时间内获取尽可能高的垃圾收集效率。
缺点:额外的内存空间。(6~8GB是G1和CMS性能比较的对比平衡点,G1在大内存上更有优势)
2.G1回收器参数:
-XX:+UseG1GC:手动设置使用G1回收器(在jdk8及以前需要手动设置,在这之后默认使用G1)
-XX:G1HeapRegionSize:设置region大小,2的次幂,1~32MB之间,目标是根据最小的堆内存大小划分为2048个区域,默认是堆内存的1/2000
-XX:MaxGCPauseMillis:期望达到的最大GC停顿时间指标,尽力达到但不保证一定达到,默认200ms。
-XX:ParallelGCThread:STW工作线程的值,最多为8。
-XX:ConcGCThreads:并发标记线程数,设为并行回收线程数(parallelGCThreads)的1/4左右。
-XX:InitiatingHeapOccupanyPercent:Java堆占用阈值,默认45
调优只需要做三步:
① 开启G1垃圾收集器
② 设置堆最大内存
③ 设置最大停顿时间
适用于:
面向服务端的应用,针对具有大内存,多处理器的机器。
应用需要低GC延迟,为具有大堆的应用程序提供解决方案
3. H区 humongous(新增的内存区域),用于存储超过1.5个Region的最大对象。
如果一个H区装不下,则G1会寻找,连续的H区存储。为找连续的H区,有时不得不Full GC(大多数情况把H区当作老年代看待)
4.Remembered Set (RSet)记忆集
为了解决Region被引用的问题,避免垃圾回收时全局扫描(年轻代和老年代),每个Region对应一个RSet。
5.G1垃圾回收流程
jvm主要知识点小结之垃圾回收及面试题
文章图片

年轻代回收过程:
1.扫描跟2.更新RSet3.处理RSet4.复制对象5.处理引用
并发标记过程:
1.初始化标记阶段 2.跟区域扫描3.并发标记4.再次标记5.独占清理6.并发清理

G1垃圾回收的建议:
①避免使用-Xmn或-XX:NewRatio显示设置年轻代大小,固定大小会覆盖暂停时间目标。
② 对暂停时间目标不要太过于严苛,G1 GC吞吐量目标是90%应用,10%垃圾回收时间,要求过于严苛表明jvm将承受更多垃圾回收开销,直接影响吞吐量。

*GC日志的导出: -Xloggc:./logs/gc.log
GC分析工具:GCViewer、GCEasy

五 面试题
罗列几道近几年大场面试题,答案在上面都能找到,就不写出来了
1.你知道有几种垃圾回收器,各自有什么优缺点,重点说一下CMS和G1
2.JVM GC算法有哪些,目前版本采用什么算法?
3.G1回收器的回收过程。
4.GC的两种判定方法,CMS收集器与G1收集器的特点。
5.说一下分代回收。
6.JVM GC原理,JVM如何回收内存的。
7.CMS的特点,垃圾回收算法有哪些,各自优缺点有什么,共同的缺点什么。
8.垃圾回收器有哪写,说一下G1应用场景,如何搭配使用垃圾回收器。

    推荐阅读