本节常见面试题(推荐带着问题阅读,问题答案在文中都有提到):
如何判断对象是否死亡(两种方法)。
简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。
垃圾收集有哪些算法,各自的特点?
HotSpot为什么要分为新生代和老年代?
常见的垃圾回收器有那些?
介绍一下CMS,G1收集器。
Minor Gc和Full GC 有什么不同呢?
7.两种垃圾回收,实际用哪种?
8.标记清扫,为嘛要标记那些标记的东西?怎么找那些存活的标记?
-----------------------------------------------------------------------
1.首先所需要考虑
2 对象已经死亡?
2.1引用计数法
2.2可达性分析算法
2.3 再谈引用
2.4 生存还是死亡
2.5 回收方法区
3 垃圾收集算法
3.1 标记-清除算法
3.2 复制算法
3.4分代收集算法
4 垃圾收集器
4.1 Serial收集器
4.2 ParNew收集器
4.3 Parallel Scavenge收集器
4.4.Serial Old收集器
4.5 Parallel Old收集器
4.6 CMS收集器
4.7 G1收集器
5 内存分配与回收策略
5.1对象优先在Eden区分配
5.2 大对象直接进入老年代
5.3长期存活的对象将进入老年代
5.4 动态对象年龄判定
总结
一、概述
首先所需要考虑:什么时候回收?那些垃圾需要回收?如何回收?
排查各种 内存溢出问题、当垃圾收集称为系统达到更高并发的瓶颈时,对这些“自动化”的技术监控和调节。
二、对象已经死亡
堆中几乎放着所有对象实例,回收前判断是否死亡(不能再用的对象)
2.1引用计数法
引用,计数器加1;引用失效减1;计数器为0对象就是不可能再被使用。
简单,效率高,虚拟机没用这个管理内存,因难解决对象之间相互循环引用问题。
打印垃圾回收简易信息的参数:-verbose:gc
打印详细:-verbose:gc -XX:+PrintGCDetails
输出:[GC (System.gc()) [PSYoungGen: 22476K->680K(38400K)] 42956K->21168K(125952K), 0.0008355 secs]可以看出对象被回收,因此Java不使用引用计数算法。
2.2可达性分析算法
“GC Roots”的对象作为起点,向下搜索,节点所走过路径为引用链,对象到GC Roots没有任何引用链相连,此对象不可用。
可作为GC Roots的对象:
虚拟机栈(方法中的引用对象)、本地方法栈(Java方法)中引用对象
方法区的类(static类)、常量属性(final)所引用对象
2.3 再谈引用
1.强引用
大部分都是强引用,生活必需品,垃圾回收器绝不会回收它。内存空间不足OutOfMemoryError,程序异常终止,也不回收强引用。
2.软引用(SoftReference)
内存不足了,回收。实现内存敏感高速缓存。OOM时二次回收
和引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。
3.弱引用(WeakReference)
发现就回收。和一个引用队列(ReferenceQueue)联合使用,同上
4.虚引用(PhantomReference)
任何时候都可能被垃圾回收。跟踪对象被垃圾回收活动。
与软、弱引用区别:必须和引用队列(ReferenceQueue)联合使用。发现有虚引用,回收之前,把虚引用加入引用队列。判断引用队列中是 否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。
很少使用弱引用与虚引用,软引用较多,可加速回收速度,维护运行安全,防止内存溢出(OutOfMemory)
2.4 生存还是死亡
不可达的对象,b是“非死不可”的,暂时处于“缓刑阶段”,真正死亡,两次标记:
(1)可达性分析,筛选条件:标记且筛选,是否有必要执行finalize方法。也是作为第二次标记的条件。对象没有覆盖finalize方法,或finalize方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
(2)finalize方法(只能做一次):被放在队列中第二次标记,除非与引用链上对象关联,否则就会被真的回收。
2.5 回收方法区
Hotspot虚拟中永久代,回收(1)废弃常量和(2)无用的类。回收性价比低,一般新生代能回收70-85%
(1)如程序中没有String对象是"abc",这时候GC,"abc"常量被清理
(2)“无用的类”同时满足下面:
1.所有实例被回收,堆中不存在
2.加载该类ClassLoader被回收。
3.类对应java.lang.Class对象没有被引用
3 垃圾收集算法
3.1 标记-清除算法
统一回收所有被标记对象。基础收集算法,效率和空间问题(大量不连续碎片) 空间不足提前触发GC
3.2 复制算法
解决效率问题,内存分为大小相同两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。每次内存回收都是对内存区间的一半进行回收。
改良版
新:老年代对象 = 8:1,不用1:1,Eden和2个Survivor区比例为8:1,新生代占到9/10,老年代不存在复制算法
3.4分代收集算法
新生代复制,老年代存活几率高的“标记-清理
延伸面试问题: HotSpot为什么要分为新生代和老年代?
4 垃圾收集器
内存回收具体实现,根据具体场景选择。
4.1 Serial收集器
“单线程” “Stop The World” ,Client模式下的虚拟机来说是个不错的选择。
主要参数:
-XX:SurvivoRatio:新生代Eden和Survivor区(单个Survivor)容量比值,默认8
-XX:PretenureSizeThreshold:直接晋升到老年代对象大小,大于该值对象直接在老年代分配。
-XX:HandlePromotionFailure:允许老年代分配担保失败,开启后可冒险YGC。
4.2 ParNew收集器
Serial多线程版本,运行Server模式下首要选择,除Serial外,只它能与CMS收集器(真正意义上的并发收集器)配合工作。
并行和并发概念补充:
并行(Parallel) :多线程并行工作,用户线程等待状态。
并发(Concurrent):用户线程与垃圾收集线程同时执行(不一定并行,可能交替执行),用户程序在运行,垃圾收集器运行另一个CPU上。
4.3 Parallel Scavenge收集器
Parallel(平行) Scavenge(清扫)新生代收集器,复制算法
吞吐量 = (执行用户代码时间)/(执行用户代码时间+垃圾回收占用时间)
关注点是吞吐量(高效率用CPU)。提供参数找到最大吞吐量
-XX:MacGCPauseMillis:最大垃圾收集停顿时间
-XX:GCTimeRatio:吞吐量大小
GCTimeRatio=99,最大垃圾收集时间占比为1/(1+99)=1%,GCTimeRatio=用户代码运行时间/GC时间。
-XX:+UseAdaptiveSizePolicy:动态自适应调整JVM参数(-Xmn、SurvivorRatio等)
4.4.Serial Old收集器 :Serial老年代版本,单线程收集器
4.5 Parallel Old收集器:Parallel Scavenge收集器的老年代版本
4.6 CMS收集器
CMS(Concurrent Mark Sweep)关注点用户线程停顿时间。
(1)初始标记:找GC Roots,暂停其他线程
(2)并发标记:找引用链
(3)重新标记:找变更
(4)并发清除:开启用户线程,GC线程清扫标记
(1)(3):stop the world
(2)(4):与用户线程并发
缺陷:
(1)占用大量cpu资源
(2)无法处理“浮动垃圾” :并发清除阶段,用户线程产生对象。
-XX:CMSInitiatingOccupancyFranction:老年代被使用的百分比,达到时触发GC(老年代占满了再GC,GC并发产生对象可能获取不到存储空间)
CMSInitiatingOccupancyFranction过高会导致大量Concurrent Mode Failure,即老年代预留内存无法满足需要。
(3)内存空间碎片
-XX:+UseCMSCompactAtFullCollection:FullGC时启动内存碎片的合并整理
-XX:CMSFullGCsBeforeCompaction:执行多少次不压缩的FullGC后压缩一次,默认为0。即每次FullGC时都合并整理内存碎片。
4.7 G1收集器
削弱新生代与老年代概念,将堆划分不同Region,根据回收价值,确定优先列表。
整体“标记-整理”; 局部(两个Region之间)“复制”算法
多个CPU缩短stop-The-World时间。原本停顿GC动作,并发继续执行。
面向服务器,对配备多颗处理器及大容量内存机器. 高概率满足GC停顿时间,备高吞吐量性能
(串行serial, 并行parallel, CMS)把堆内存划分: 年轻代(young generation), 年老代(old generation), 持久代(permanent generation)
和CMS有很多相似之处。
1.初始标记:仅标记GC Roots直接关联对象,并改TAMS值,让下阶段用户程序并发运行时,能用Region中创建新对象,这段要停顿线程,但很短
2.并发标记:从GC Root开始对堆中对象可达性分析,找存活对象,耗时长,但可与用户程序并发执行。
3.最终标记:修正上阶段落掉,虚拟机将这段对象变化记录在线程Remenbered Set Logs里,Remembered Set Logs的数据合并到Remembered Set中,要停顿线程,可并行执行。目的:
4.筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望GC停顿时间来制定回收计划。
5 内存分配与回收策略
5.1对象优先在Eden区分配
Eden区没空间,虚拟机将发起Minor GC
1)Minor Gc和Full GC 有什么不同呢?
新生代GC(Minor GC): 1)在Eden和Survivor区,2)无法为新生对象分配内存时,触发Minor GC。3)频繁,回收速度快。新生代中大多数对象的生命周期都短
老年代GC(Full GC): 1)对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)全局GC。2)用了什么垃圾收集器,才能解释是什么样gc ,不等于Major GC,也不等于Minor GC+Major GC,3)
Major GC :清理Tenured区,回收老年代,伴随至少一次Minor GC(并非绝对),速度慢10倍
2)如果老年代对象引用年轻代对象,年轻代对象是否被GC?
写屏障,确保新被老引不被gc:
老年代存活对象较多时,每次Minor GC查询老年代所有对象影响回收效率(因为GC会 stop-the-world),老年代有write barrier(写屏障)管理card table(卡表),card table存放了所有老对新生代对象引用
3)如何判断老引用新?
年轻代之间对象引用消除,计数器>1则被引用
5.2 大对象直接进入老年代
大对象需要大量连续内存空间(如:字符串、数组)。
5.3长期存活的对象将进入老年代
为识别哪些应放新、老年代中。虚拟机给每个对象,年龄(Age)计数器。
Survivor空间中,并且对象年龄设为1。一次Minor GC,年龄加1
-XX:MaxTenuringThreshold最大年龄,默认为15;
5.4 动态对象年龄判定
Survivor中相同年龄所有对象大小总和超过Survivor一半,大于或等于该年龄直接进入老年代。