1、GC是什么?为什么要有GC?
GC垃圾收集,Java提供的GC可以自动监测对象是否超过作用域从而达到自动回收内存的目的。
垃圾回收可有效使用内存和防止内存泄露。垃圾回收器通常是作为一个单独的低优先级线程运行,不可预知的情况下对内存堆中已死亡或长久无使用的对象进行清除和回收。
回收机制:分代复制垃圾回收、标记垃圾回收、增量垃圾回收等方式。
2、什么时候会导致垃圾回收?
1)当Eden区和Survivor区满时、老年代空间不足、方法区空间不足;
2)调用System.gc时,系统建议执行Full GC,但不是必然执行
3)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
4)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小;
3、GC是怎么样运行的?
1)自动性。Java提供了一个系统级的线程,即垃圾收集器线程,来跟踪分配的每一块内存空间,当JVM处于空闲循环时,垃圾收集器线程会自动检查内存空间,自动回收每一块可以回收的内存块。
2)不可预期性。一个对象成为了垃圾,不能断言该对象立刻被清除。就算调用System.gc(),只是给JVM起建议作用,是否立即执行回收并不可控。
4、新老以及永久区是什么?
分代收集法,根据对象存活的生命周期分为:新生代、老年代和永久代;
1)新生代:每次都回收大量对象—>Copying复制法
2)老年代:每次都回收少量对象—>Mark-Compact法
3)永久代:存储class类、常量、方法描述,回收废弃常量和无用类
5、GC有几种方式?怎么配置?
1)串行GC:client模式下默认GC方式,使用-XX:+UseSerialGC指定
2)并行GC:server模式下默认GC方式,可用-XX:+UseParallelGC指定
3)并发GC:CMS GC时默认采用,可用-XX:+UseParNewGC,-XX:+UseConcMarkSweepGC指定;
6、什么时候一个对象会被GC? 如何判断一个对象是否存活?
当一个对象到GC Roots不可达时,在下一个垃圾回收周期中尝试回收该对象,如果对象重写了finalize(),并在这个方法中成功自救(将自身赋予某个引用),那么这个对象不会被回收。但如果这个对象没有重写finalize()方法或已执行过这个方法,该对象将会被回收。
判断存活有两种算法:
1)引用计数算法
给对象添加一个引用计数器,当有地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;计数器为0的对象就是不被使用的。
2)可达性分析算法
通过GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
7、System.gc()、Runtime.gc()会做什么? 能保证GC执行吗?
通知虚拟机执行GC,虚拟机是否立即执行回收并不可控。
8、Minor GC、Major GC、Young GC与Full GC分别在什么时候发生?
Young GC:JVM在进行minorGC前会判断老年代最大可用连续空间是否大于新生代的所有对象总空间;
1)如果大于,直接执行minorGC
2)如果小于,判断是否开启HandlerPromotionFailure,没有开启直接FullGC
3)如果开启了HanlerPromotionFailure, JVM会判断老年代最大连续内存空间是否大于历次晋升的大小,如果小于直接执行FullGC
FullGC:
1)老年代空间不足
2)持久代空间不足
3)YGC出现promotion failure
4)统计YGC发生时晋升到老年代的平均总大小大于老年代的空闲空间
5)显示调用System.gc
Minor GC:Eden区满了,或新创建的对象大小 > Eden所剩空间
Major GC:是清理老年代,Full GC是清理整个堆空间,包括年轻代和老年代。许多Major GC是由Minor GC触发的。
9、垃圾回收算法的实现原理
1)标记-清除(Mark-Sweep)算法
首先标记出所有需要回收的对象,标记完成后回收所有被标记的对象。不足主要体现在效率和空间,从效率的角度讲,标记和清除效率都不高;从空间的角度讲,标记清除后会产生大量不连续的内存碎片, 内存碎片太多可能会导致需要分配较大对象时,无法找到足够的连续内存而提前触发一次垃圾收集动作。
2)复制(Copying)算法
将可用内存分为两块,每次只用其中一块,当一块内存用完了,就将还存活的对象复制到另外一块上,然后再把已经使用过的内存空间一次性清理掉。这样每次只需对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等复杂情况,只需要移动指针,按照顺序分配即可。
缺点:内存缩小为原来的一半,代价太高。现在商用虚拟机都采用这种算法来回收新生代,不过1:1的比例非常不科学,因此新生代的内存被划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。每次回收时,将Eden和Survivor中还存活着的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden区和Survivor区的比例为8:1,意思是每次新生代中可用内存空间为整个新生代容量的90%。当然,我们无法保证每次回收都少于10%的对象存活,当Survivor空间不够用时,需要依赖老年代进行分配担保(Handle Promotion)。
3)标记-整理(Mark-Compact)算法
过程与标记-清除算法一样,不过不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。
4)分代收集算法
根据对象的生命周期不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法。
大批对象死去、少量对象存活的(新生代),使用复制算法,成本低;
对象存活率高、没有额外空间进行分配担保的(老年代),采用标记-清理算法或者标记-整理算法。
10、垃圾回收器的基本原理是什么?
对于GC来说,当程序创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。
通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。
11、串行(serial)收集器和吞吐量(throughput)收集器的区别?
吞吐量收集器使用并行版本的新生代垃圾收集器,用于中大规模数据的应用程序。
串行收集器对大多数的小应用(在处理器上需要大概100M左右的内存)就足够了。
12、Serial与Parallel GC之间的不同之处?
Serial与Parallel GC严格来说是2种不同的GC。
1、串行GC在垃圾回收的时候,执行线程要停止等待GC回收,回收完毕,执行线程继续工作。
2、并行GC的好处是提升垃圾回收的性能,减少串行回收带来的问题,也有停顿,但可以并行回收,一边标记对象一边执行线程,整体上提升了回收的性能。
13、CMS收集器与G1收集器的特点与区别?
不管选择哪种GC算法,stop-the world都是不可避免的。Stop-the-world意味着从应用中停下来并进入到GC执行过程中去。一旦Stop-the-world发生,除了GC所需的线程外,其他线程都将停止工作,中断了的线程直到GC任务结束才继续它们的任务。GC调优通常就是为了改善stop-the-world的时间。
在Java语言中,可作为GC Roots的对象包括4种情况:
a) 虚拟机栈中引用的对象(栈帧中的本地变量表);
b) 方法区中类静态属性引用的对象;
c) 方法区中常量引用的对象;
d) 本地方法栈中JNI(Native方法)引用的对象。
CMS:是以获取最短回收停顿时间为目标的收集器,基于”标记-清除”(Mark-Sweep)算法实现,整个过程分为四个步骤:
1)初始标记 (Stop the World事件CPU停顿很短) ,仅标记GC Roots能直接关联到的对象,速度快;
2)并发标记 (收集垃圾跟用户线程一起执行) ,初始标记和重新标记仍需要“stop the world”,并发标记过程就是进行GC Roots Tracing的过程;
3)重新标记 (Stop the World事件CPU停顿,比初始标记稍长,远比并发标记短),修正并发标记期因用户程序继续运作而导致标记产生变动的那部分对象的标记记录,这个阶段停顿时间比初始标记阶段稍长些,比并发标记时间短;
4)并发清理-清除算法
整个过程中最耗时的并发标记和并发清除过程,收集器线程都可与用户线程一起工作,总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
优点:并发收集,低停顿
缺点:
1)CMS收集器对CPU资源非常敏感
2)CMS处理器无法处理浮动垃圾
3)CMS基于“标记--清除”算法实现,会产生大量空间碎片,会在大对象分配时提前触发full gc。为解决这个问题,CMS提供了一个开关参数,用于在CMS要进行full gc时开启内存碎片的合并整理过程,内存整理的过程无法并发,停顿时间变长;
G1(Garbage First)是一款面向服务端应用的垃圾收集器。
G1运作步骤:
1)初始标记(stop the world事件CPU停顿只处理垃圾);
2)并发标记(与用户线程并发执行);
3)最终标记(stop the world事件CPU停顿处理垃圾);
4)筛选回收(stop the world事件根据用户期望的GC停顿时间回收)
G1具备如下特点:
1)并行于并发:充分使用多个CPU来缩短stop-The-World停顿时间。
2)分代收集:采用不同方式处理新创建的对象和已存活一段时间,熬过多次GC的旧对象,以获取更好的收集效果。
3)空间整合:与CMS的“标记--清理”算法不同,G1从整体看基于“标记整理”算法实现,从局部上看基于“复制”算法实现。
4)可预测的停顿:是G1相对于CMS的一大优势,G1除追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定一个长度为M毫秒的时间片段;
14、如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?
不会的,在对象不使用时应该将其设置为null,垃圾回收器会在下一个周期对这个对象进行回收,有些对象是可恢复的,如在finalize方法中恢复引用。
15、垃圾回收的最佳做法是什么?
用编程的方式,可以要求(只是请求,不是命令)JVM通过调用System.gc()来运行垃圾回收。
16、JVM 中一次完整的GC流程是怎样的?对象如何晋升到老年代?
Minor GC过程:
假设Heap内存大小为20M,其中年轻代为10M,老年代为10M,年轻代中Eden区6M,From区2M,To区2M。
新创建的对象首先往Eden区分配,当再次分配一个对象,假设大小为1M,此时Eden区已没有足够空间来给这个对象分配内存,这时触发一次Minor GC,把Eden区的存活对象转移到From区,非存活对象进行清理,然后给新创建的对象分配空间,存入Eden区;
随着分配对象增多,Eden区空间又不足,这时再触发一次Minor GC,清理Eden区和S1区的死亡对象,把存活对象转移到S2区,然后再给新对象分配内存;
From区和To区是相对的关系,哪个区中有对象,哪个区就是From区,比如,再进行一次Minor GC,会把存活对象转移到S1区,再为转移之前,S2区是From区,S1区是To区,转移后,S2区中没有存活对象,变为To区,而S1区变为From区;
对象进入老年代的4种情况:
1) 在进行Minor GC时发现,存活的对象在To区中存不下,那么把存活的对象存入老年代
2)大对象直接进入老年代:若新创建的对象很大,比如5M(可通过PretenureSizeThreshold参数设置,默认3M),即使Eden区有足够的空间,也不会放在Eden区,而是直接存入老年代。
3)长期存活的对象将进入老年代:如果对象在Eden出生且经过1次Minor GC后仍存活,并能被To区容纳,将被移动到To区,并把对象年龄设置为1,对象没"熬过"1次Minor GC,年龄就增加一岁,当它的年龄增加到一定程度(默认15岁,配置参数-XX:MaxTenuringThreshold),就会被晋升到老年代中。
4) 动态对象年龄判定:如果在From中,相同年龄所有对象的大小总和大于From和To空间总和的一半,那么年龄大于等于该年龄的对象就会被移动到老年代,而不用等到15岁(默认):
17、吞吐量优先和响应优先的垃圾收集器选择?
吞吐量优先:并行收集器
响应时间优先:并发收集器
18、JVM的永久代中会发生垃圾回收吗?
不会,如果永久代满了或超过临界值,会触发完全垃圾回收(Full GC)。正确设置永久代大小对避免Full GC非常重要。Java8中移除了永久代,新加了一个叫做元数据区的native内存区。
19、标记清除、标记整理、复制算法的原理与特点?分别用在什么地方?
先标记,标记完毕再清除,效率不高,会产生碎片
复制算法:分为8:1的Eden区和survivor区,就是上面谈到的YGC(
堆里面分为新生代和老生代,新生代包含Eden+Survivor区,survivor区分为from和to区,内存回收时使用复制算法,从from复制到to,当经过一次或多次GC后,存活对象会被移动到老年区,当JVM内存不够时会触发Full GC,清理JVM老年区。
当新生区满后会触发YGC,先把存活对象放到其中一个Survice区,然后进行垃圾清理。如果仅清理需要删除的对象,会导致内存碎片,一般会把Eden完全清理,然后整理内存。下次GC时就会使用下一个Survive,这样循环使用。如果有特别大的对象,新生代放不下,会直接放到老年代。)
标记整理:标记完毕,让所有存活的对象向一端移动;