CMS 和G1 的 区别
CMS收集器
-
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于并发“标记清理”实现,在标记清理过程中不会导致用户线程无法定位引用对象。仅作用于老年代收集。它的步骤如下:
- 初始标记(CMS initial mark):独占CPU,stop-the-world, 仅标记GCroots能直接关联的对象,速度比较快;
- 并发标记(CMS concurrent mark):可以和用户线程并发执行,通过GCRoots Tracing 标记所有可达对象;
- 重新标记(CMS remark):独占CPU,stop-the-world, 对并发标记阶段用户线程运行产生的垃圾对象进行标记修正,以及更新逃逸对象;
- 并发清理(CMS concurrent sweep):可以和用户线程并发执行,清理在重复标记中被标记为可回收的对象
优缺点
CMS的优点:
- 支持并发收集.
- 低停顿,因为CMS可以控制将耗时的两个stop-the-world操作保持与用户线程恰当的时机并发执行,并且能保证在短时间执行完成,这样就达到了近似并发的目的.
CMS的缺点:
- CMS收集器对CPU资源非常敏感,在并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分CPU资源,如果在CPU资源不足的情况下应用会有明显的卡顿。
- 无法处理浮动垃圾:在执行‘并发清理’步骤时,用户线程也会同时产生一部分可回收对象,但是这部分可回收对象只能在下次执行清理是才会被回收。如果在清理过程中预留给用户线程的内存不足就会出现‘Concurrent Mode Failure’,一旦出现此错误时便会切换到SerialOld收集方式。
- CMS清理后会产生大量的内存碎片,当有不足以提供整块连续的空间给新对象/晋升为老年代对象时又会触发FullGC。且在1.9后将其废除。
使用场景
- 它关注的是垃圾回收最短的停顿时间(低停顿),在老年代并不频繁GC的场景下,是比较适用的。
G1收集器
-
G1收集器的内存结构完全区别去CMS,弱化了CMS原有的分代模型(分代可以是不连续的空间),将堆内存划分成一个个Region(1MB~32MB, 默认2048个分区),这么做的目的是在进行收集时不必在全堆范围内进行。它主要特点在于达到可控的停顿时间,用户可以指定收集操作在多长时间内完成,即G1提供了接近实时的收集特性。它的步骤如下:
- 初始标记(Initial Marking):标记一下GC Roots能直接关联到的对象,伴随着一次普通的Young GC发生,并修改NTAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,此阶段是stop-the-world操作。
- 根区间扫描,标记所有幸存者区间的对象引用,扫描 Survivor到老年代的引用,该阶段必须在下一次Young GC 发生前结束。
- 并发标记(Concurrent Marking):是从GC Roots开始堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行,该阶段可以被Young GC中断。
- 最终标记(Final Marking):是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,此阶段是stop-the-world操作,使用snapshot-at-the-beginning (SATB) 算法。
- 筛选回收(Live Data Counting and Evacuation):首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,回收没有存活对象的Region并加入可用Region队列。这个阶段也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率
G1的特点
- 并行与并发:G1充分发挥多核性能,使用多CPU来缩短Stop-The-world的时间,
- 分代收集:G1能够自己管理不同分代内已创建对象和新对象的收集。
- 空间整合:G1从整体上来看是基于‘标记-整理’算法实现,从局部(相关的两块Region)上来看是基于‘复制’算法实现,这两种算法都不会产生内存空间碎片。
- 可预测的停顿:它可以自定义停顿时间模型,可以指定一段时间内消耗在垃圾回收商的时间不大于预期设定值。
使用场景
- G1 GC切分堆内存为多个区间(Region),从而避免很多GC操作在整个Java堆或者整个年轻代进行。G1 GC只关注你有没有存货对象,都会被回收并放入可用的Region队列。G1 GC是基于Region的GC,适用于大内存机器。即使内存很大,Region扫描,性能还是很高的。
G1 什么时候引发的FullGc?
-
G1 提供了两种 GC 模式,Young GC 和 Mixed GC,两种都是完全 Stop The World 的。
- 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的。
说出一个最熟悉的垃圾回收算法
- 这道题是个开放性问题,可以通过自己熟悉的垃圾回收算法引申一下该算法相应的垃圾收集器实现,比如,标记–复制算法,这是 HotSpot 虚拟机新生代垃圾收集器常用的回收算法,对应的实现有 Serial、Parallel Scavenge,在比如标记–清除算法,对应的垃圾收集器实现有 CMS 等等。
- 一般来说,通过上面的问题,肯定继续引申你所熟悉的 HotSpot 垃圾收集器了,就目前的情况下,我理解主要讲讲 CMS 和 G1 吧;因为在往前说,Serial 和 Parallel 种类的收集器比较老了,在往后说 Shenandoah 和 ZGC 收集器又太新了,工作中使用都不多,目前应该就是 CMS 和 G1,使用比较多,而且实现原理上也比较复杂,和面试官比较有的聊。
吞吐量和响应时间 优先的回收器有哪些
- 对于吞吐量优先的场景,就只有一种选择,就是使用 PS 组合(Parallel Scavenge+Parallel Old )。
- 对于响应时间优先的场景,在 JDK1.8 的话优先 G1,其次是 CMS 垃圾回收器,另外还有PN、ZGC、Shenandoah。
- jvm垃圾回收器(串行、吞吐量优先、响应时间优先、G1)
怎么判断内存泄露
- 内存溢出:你申请了10个字节的空间,但是你在这个空间写入11或以上字节的数据,出现溢出。
- 内存泄漏:你用new申请了一块内存,后来很长时间都不再使用了(按理应该释放),但是因为一直被某个或某些实例所持有导致 GC 不能回收,也就是该被释放的对象没有释放。
- Java内存泄漏的排查总结
讲一下CMS 的流程
- InitialMarking(初始化标记,整个过程STW)
- 标记GC Roots可达的老年代对象;
- 遍历新生代对象,标记可达的老年代对象。
- Marking(并发标记)
- Precleaning(预清理)
- AbortablePreclean(可中断的预清理)
- FinalMarking(并发重新标记,STW过程)
- 图解CMS垃圾回收机制
为啥压缩指针超过32G失效
- 在64位平台的HotSpot中使用32位指针,内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据,占用较大宽带,同时GC也会承受较大压力
- 为了减少64位平台下内存的消耗,启用指针压缩功能
- 在jvm中,32位地址表示4G个对象的指针,在4G-32G堆内存范围内,可以通过编码、解码方式进行优化,使得jvm可以支持更大的内存配置
- 堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间
- 堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,这就会出现1的问题,所以堆内存不要大于32G为好
什么是内存泄露?GC调优有经验吗?一般出现GC问题怎么解决?
-
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。 如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存
-
从实际案例聊聊Java应用的GC优化
ThreadLocal 有没有内存泄露问题
- 由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,我觉得是这种数据结构导致,会产生内存溢出的问题
- Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。所以最怕的情况就是,threadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄露。
- 深入分析 ThreadLocal 内存泄漏问题
G1 两个Region 不是连续的,而且之间还有可达的引用,现在要回收一个,另一个怎么处理?
暂无题解,大家有合适的,可以留言
讲一下JVM 堆内存管理(对象分配过程)
要理解JVM的内存管理策略,首先就要熟悉Java的运行时数据区,如上图所示,在执行Java程序的时候,虚拟机会把它所管理的内存划分为多个不同的数据区,称为运行时数据区。在程序执行过程中对内存的分配、垃圾的回收都在运行时数据区中进行。对于Java程序员来说,其中最重要的就是堆区和JVM栈区了。注意图中的图形面积比例并不代表实际的内存比例。
- 绿色的区域代表被线程所共享
- 黄色的区域代表被线程所独享
- JVM之内存管理
听说过CMS的并发预处理和并发可中断预处理吗?
- 首先,CMS是一个关注停顿时间,以回收停顿时间最短为目标的垃圾回收器。并发预处理阶段做的工作是标记,重标记需要STW(Stop The World),因此重标记的工作尽可能多的在并发阶段完成来减少STW的时间。此阶段标记从新生代晋升的对象、新分配到老年代的对象以及在并发阶段被修改了的对象。
- 并发可中断预清理(Concurrent precleaning)是标记在并发标记阶段引用发生变化的对象,如果发现对象的引用发生变化,则JVM会标记堆的这个区域为Dirty Card。那些能够从Dirty Card到达的对象也被标记(标记为存活),当标记做完后,这个Dirty Card区域就会消失。CMS有两个参数:CMSScheduleRemarkEdenSizeThreshold、CMSScheduleRemarkEdenPenetration,默认值分别是2M、50%。两个参数组合起来的意思是预清理后,eden空间使用超过2M时启动可中断的并发预清理(CMS-concurrent-abortable-preclean),直到eden空间使用率达到50%时中断,进入重新标记阶段。
到底多大的对象会被之间扔到老年代里(Old)?
- HotSpot 虚拟机提供了-XX:PretenureSizeThreshold 参数,指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在 Eden 区及两个 Survivor区之间来回复制,产生大量的内存复制操作。
- 这样做的目的:
- 1.避免大量内存复制,
- 2.避免提前进行垃圾回收,明明内存有空间进行分配。PretenureSizeThreshold 参数只对 Serial 和 ParNew 两款收集器有效。-XX:PretenureSizeThreshold=4m。
用一句话说明你的JVM水平很牛?
emm…… 精通 HotSpot 虚拟机 10 中垃圾收集器实现原理。