16

我是 javapub,一名 Markdown 程序员从👨‍💻,八股文种子选手。

面试官: 小伙子,跟我聊聊垃圾回收机制吧。什么是垃圾?怎么回收?

候选人: 好的面试官,来吧!垃圾就是那些不再被程序使用的对象。Java 通过自动的垃圾回收机制回收这些垃圾对象所占的内存。

面试官: 那Java垃圾收集器都有哪些?各有什么优缺点?

候选人: Java 提供了几种垃圾收集器:

  • Serial 收集器:最基本的收集器,对内存进行复制然后清理,效率low。只使用一个线程,会停顿其他线程,不适用服务器环境。
  • Parallel 收集器:多个线程并行回收,效率高于Serial,适用于微服务等。
  • CMS 收集器:并发标记清除,效率高,并发回收,但会产生碎片。适用于对响应时间有要求的场景。
  • G1 收集器:JDK9默认,基于region分代回收,效率高且不产生碎片。适用于大内存的机器。

面试官: 讲讲G1垃圾收集器的工作流程。

候选人: G1垃圾收集器的工作流程如下:

  1. 初始标记:标记GC Roots能直接关联的对象,速度快,主要为了第2步做准备。
// 代码示例
private void markFromRoots() {
    // classify objects and put them into correct lists
    for (Object obj : strongRefs) {
        G1CollectedHeap.addToMarked(obj);
    }
}
  1. 并发标记:从GC Roots开始对堆中对象进行并发标记,jia部分STW(stop-the-world)
  2. 最终标记:修正并发标记期间并发修改导致的错误标记,需要STW。
  3. 筛选回收:根据标记结果筛选回收区域,回收垃圾对象,需要STW。
  4. 并发清理:与用户线程一起工作,对标记和筛选阶段差异化技术产生的垃圾链进行清理。

以上就是G1收集器的整个工作流程,相比CMS无碎片和高效,适用于大内存服务器。

面试官: 那说说 Java 对象如何判断为“垃圾”?

候选人: Java 对象通过引用计数算法判断是否为垃圾:

  1. 如果一个对象仅被强引用变量引用,并且这个强引用变量为 null,则该对象为垃圾。
// 对象example引用为null,则该对象为垃圾
Example example = new Example();  
example = null;  
  1. 如果一个对象被强引用的变量引用,并且这个强引用变量所在的方法已经弹出栈,则该对象为垃圾。
// 方法退出后,obj为垃圾
public void func() {
    Example obj = new Example();
}  
  1. 如果一个对象仅被软引用、弱引用或虚引用变量引用,则在垃圾回收时这些变量会被清除,该对象为垃圾。
// 使用WeakReference例子 
WeakReference<Example> weakExample = new WeakReference<>(new Example());
// 如果没有其他引用指向Example对象,则该对象可被回收
  1. 如果两个对象彼此引用,但没有任何一个对象被外部强引用,则这两个对象之间形成的循环引用链为垃圾。
// A和B相互引用,构成循环,都为垃圾
A a = new A(b);
B b = new B(a);

面试官: 帮我总结下 Java 垃圾回收的机制?

候选人: 可以这么总结 Java 垃圾回收机制:

  1. 垃圾的判断:通过引用计数算法判断对象是否可达。如被引用变量置null、超出作用域、软引用被清理等,则判断为垃圾。
  2. 垃圾的回收:通过垃圾收集器进行自动回收,如Serial、Parallel、CMS、G1等收集器。使用分代回收、标记-清除、复制算法等进行回收。
  3. 垃圾回收的时机:当堆中垃圾对象达到一定比例或内存不足时,会触发垃圾回收。也可以手动触发System.gc()
  4. 垃圾回收的步骤:1标记阶段标记垃圾 2清除阶段删除垃圾 3整理阶段压缩空间
  5. 如何优化:尽量减少垃圾产生,使用软引用或弱引用存放易变对象,及时回收资源等方式优化。

以上就是 Java 垃圾回收机制的主要内容,希望能对你有所帮助!有任何问题都可以继续问我。

面试官: 那谈谈你了解的JVM内存结构和垃圾回收之间的关系?

候选人: JVM内存结构与垃圾回收有密切关系:

  1. JVM内存结构分为:堆内存、虚拟机栈、方法区、本地方法栈、程序计数器等。堆内存存储对象实例,垃圾回收的主要区域就是堆内存。
// JVM内存结构图示
   +--------------------+
   |      方法区        |  
   +--------------------+
   |     程序计数器      |
   +--------------------+  
   |        JVM栈        |      
   +--------------------+  
   |                     |  
   |     堆内存           |    
   |                     |  
   +--------------------+  

  1. 堆内存中又分为几个区域,主要有两个:新生代和老年代。新生代用于存储新创建的对象,老年代中存放老化对象。
   +--------------------+
   |     方法区        |  
   +--------------------+
   |     程序计数器      |
   +--------------------+  
   |        JVM栈        |      
   +--------------------+  
   |  新生代            |  
   |                     |  
   |     堆内存           | 
   |                     |  
   |   老年代             |    
   +--------------------+  
  1. 垃圾收集器会根据这些内存区域中的对象进行回收,比如新生代使用Copying算法,老年代使用Mark-Sweep算法。

  2. 情景举例:

  • 对象在Eden出生,经过第一次Minor GC后未死亡进入Survivor,多次MinorGC后仍存活进入老年代。
  • 老年代空间不足触发Major GC,回收部分垃圾对象。
  • 老年代的对象通过晋升至永久代,如果永久代填满,会抛出OOM异常。

面试官: 嗯!啊。

最近我在更新《面试1v1》系列文章,主要以场景化的方式,讲解我们在面试中遇到的问题,致力于让每一位工程师拿到自己心仪的offer,感兴趣可以关注公众号JavaPub追更!

🎁目录合集:

Gitee:https://gitee.com/rodert/JavaPub

GitHub:https://github.com/Rodert/JavaPub

http://javapub.net.cn

posted @ 2023-07-28 08:20  JavaPub  阅读(139)  评论(0编辑  收藏  举报