【垃圾回收】常见面试题解析

GC是JVM中极高频会被问到的问题,一方面是因为GC这块儿的确具有较多的理论基础,另一方面在于其具有极高的实践场景,在业务中经常遇到棘手的fullGC需要处理。
 

3.1 请谈谈在老年代和新生代层面的GC

3.1.1 Minor GC

在新生代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。这一定义既清晰又易于理解。但是,当发生Minor GC事件的时候,有一些有趣的地方需要注意到:

  • 当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor GC
  • 内存池被填满的时候,其中的内容全部会被复制,指针会从 0 开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以 Eden 和 Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部
  • 执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉
  • 质疑常规的认知,所有的 Minor GC 都会触发"全世界的暂停(stop-the-world)",停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。
     

3.1.2 Full GC

Full GC 的触发条件:

  • 调用 System.gc()
  • 老年代空间不足,常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等
  • 空间分配担保失败,使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC
  • JDK 1.7 及以前的永久代空间不足

这里补充一下与 System.gc() 相关的内容:
当调用System.gc()的时候,其实并不会马上进行垃圾回收,甚至不一定会执行垃圾回收。

public static void gc() {
    boolean shouldRunGC;
    synchronized(lock) {
        shouldRunGC = justRanFinalization;
        if (shouldRunGC) {
            justRanFinalization = false;
        } else {
            runGC = true;
        }
    }
    if (shouldRunGC) {
        Runtime.getRuntime().gc();
    }
}

 
也就是 justRanFinalization=true 的时候才会执行。查找发现当调用 runFinalization()的时候 justRanFinalization 变为true。

public static void runFinalization() {
    boolean shouldRunGC;
    synchronized(lock) {
        shouldRunGC = runGC;
        runGC = false;
    }
    if (shouldRunGC) {
        Runtime.getRuntime().gc();
    }
    Runtime.getRuntime().runFinalization();
    synchronized(lock) {
        justRanFinalization = true;
    }
}

 
直接调用 System.gc() 只会把这次 gc 请求记录下来,等到 runFinalization=true 的时候才会先去执行 GC,runFinalization=true 之后会在允许一次 system.gc()。之后在 call System.gc() 还会重复上面的行为。所以 System.gc() 要跟 System.runFinalization() 一起搭配使用:

static void gcAndFinalize() {
    final VMRuntime runtime = VMRuntime.getRuntime();
    System.gc();
    runtime.runFinalizationSync();
    System.gc();
}

 

java.lang.System.gc()只是java.lang.Runtime.getRuntime().gc()的简写,两者的行为没有任何不同。唯一的区别就是System.gc()写起来比Runtime.getRuntime().gc()简单点。GC本身是会周期性的自动运行的,由JVM决定运行的时机,而且现在的版本有多种更智能的模式可以选择,还会根据运行的机器自动去做选择,就算真的有性能上的需求,也应该去对GC的运行机制进行微调,而不是通过使用这个命令来实现性能的优化。

3.1.3 Major GC 与 Full GC 的区别

  • Major GC 是清理老年代
  • Full GC 是清理整个堆空间—包括年轻代和老年代
  • 执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足

很不幸,实际上它还有点复杂且令人困惑。首先,许多 Major GC 是由 Minor GC 触发的,所以很多情况下将这两种 GC 分离是不太可能的。另一方面,许多现代垃圾收集机制会清理部分永久代空间,所以使用“cleaning”一词只是部分正确。

3.2 请谈谈你对GC分类标准的理解

针对HotSpot VM的实现,它里面的GC其实准确分类只有两大种:

  • Partial GC:并不收集整个GC堆的模式
    • Young GC:只收集新生代的GC
    • Old GC:只收集老年代的GC。只有CMS的concurrent collection是这个模式
    • Mixed GC:收集整个新生代以及部分老年代的GC。只有G1有这个模式
  • Full GC:收集整个堆,包括新生代、老年代、perm gen(如果存在的话)等所有部分的模式

Major GC 通常是跟 Full GC 是等价的,收集整个 GC 堆。但因为 HotSpot VM 发展了这么多年,外界对各种名词的解读已经完全混乱了,当有人说“major GC”的时候一定要问清楚他想要指的是上面的 Full GC 还是 Old GC。

最简单的分代式 GC 策略,按 HotSpot VM 的 serial GC 的实现来看,触发条件是:

  • young GC:当新生代 中的eden区分配满的时候触发。注意 young GC 中有部分存活对象会晋升到老年代 ,所以 young GC 后 老年代 的占用量通常会有所升高。
  • full GC:当准备要触发一次 young GC 时,如果发现之前 young GC 的平均晋升大小比目前 老

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java面试必问JVM考点精讲 文章被收录于专栏

“挨踢”行业行情日益严峻,企业招聘的门槛也随之越来越高,大厂hc少之又少。 庞大的知识体系下,不知道学什么、怎么学? 面试高频考点是什么、怎么回答才能得到面试官的青睐? 作为后端求职者,在Java的道路上越走越宽。 本专刊则针对Java面试考点上,精讲JVM知识点,为大家的大厂求职路保驾护航! 针对如今校招痛点,深入详解JVM知识考点,列出高频真题并详细解答!探索JVM精髓!

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务