16
我是 javapub,一名 Markdown
程序员从👨💻,八股文种子选手。
面试官: 小伙子,跟我聊聊垃圾回收机制吧。什么是垃圾?怎么回收?
候选人: 好的面试官,来吧!垃圾就是那些不再被程序使用的对象。Java 通过自动的垃圾回收机制回收这些垃圾对象所占的内存。
面试官: 那Java垃圾收集器都有哪些?各有什么优缺点?
候选人: Java 提供了几种垃圾收集器:
- Serial 收集器:最基本的收集器,对内存进行复制然后清理,效率low。只使用一个线程,会停顿其他线程,不适用服务器环境。
- Parallel 收集器:多个线程并行回收,效率高于Serial,适用于微服务等。
- CMS 收集器:并发标记清除,效率高,并发回收,但会产生碎片。适用于对响应时间有要求的场景。
- G1 收集器:JDK9默认,基于region分代回收,效率高且不产生碎片。适用于大内存的机器。
面试官: 讲讲G1垃圾收集器的工作流程。
候选人: G1垃圾收集器的工作流程如下:
- 初始标记:标记GC Roots能直接关联的对象,速度快,主要为了第2步做准备。
// 代码示例
private void markFromRoots() {
// classify objects and put them into correct lists
for (Object obj : strongRefs) {
G1CollectedHeap.addToMarked(obj);
}
}
- 并发标记:从GC Roots开始对堆中对象进行并发标记,jia部分STW(stop-the-world)
- 最终标记:修正并发标记期间并发修改导致的错误标记,需要STW。
- 筛选回收:根据标记结果筛选回收区域,回收垃圾对象,需要STW。
- 并发清理:与用户线程一起工作,对标记和筛选阶段差异化技术产生的垃圾链进行清理。
以上就是G1收集器的整个工作流程,相比CMS无碎片和高效,适用于大内存服务器。
面试官: 那说说 Java 对象如何判断为“垃圾”?
候选人: Java 对象通过引用计数算法判断是否为垃圾:
- 如果一个对象仅被强引用变量引用,并且这个强引用变量为 null,则该对象为垃圾。
// 对象example引用为null,则该对象为垃圾
Example example = new Example();
example = null;
- 如果一个对象被强引用的变量引用,并且这个强引用变量所在的方法已经弹出栈,则该对象为垃圾。
// 方法退出后,obj为垃圾
public void func() {
Example obj = new Example();
}
- 如果一个对象仅被软引用、弱引用或虚引用变量引用,则在垃圾回收时这些变量会被清除,该对象为垃圾。
// 使用WeakReference例子
WeakReference<Example> weakExample = new WeakReference<>(new Example());
// 如果没有其他引用指向Example对象,则该对象可被回收
- 如果两个对象彼此引用,但没有任何一个对象被外部强引用,则这两个对象之间形成的循环引用链为垃圾。
// A和B相互引用,构成循环,都为垃圾
A a = new A(b);
B b = new B(a);
面试官: 帮我总结下 Java 垃圾回收的机制?
候选人: 可以这么总结 Java 垃圾回收机制:
- 垃圾的判断:通过引用计数算法判断对象是否可达。如被引用变量置null、超出作用域、软引用被清理等,则判断为垃圾。
- 垃圾的回收:通过垃圾收集器进行自动回收,如Serial、Parallel、CMS、G1等收集器。使用分代回收、标记-清除、复制算法等进行回收。
- 垃圾回收的时机:当堆中垃圾对象达到一定比例或内存不足时,会触发垃圾回收。也可以手动触发
System.gc()
。 - 垃圾回收的步骤:1标记阶段标记垃圾 2清除阶段删除垃圾 3整理阶段压缩空间
- 如何优化:尽量减少垃圾产生,使用软引用或弱引用存放易变对象,及时回收资源等方式优化。
以上就是 Java 垃圾回收机制的主要内容,希望能对你有所帮助!有任何问题都可以继续问我。
面试官: 那谈谈你了解的JVM内存结构和垃圾回收之间的关系?
候选人: JVM内存结构与垃圾回收有密切关系:
- JVM内存结构分为:堆内存、虚拟机栈、方法区、本地方法栈、程序计数器等。堆内存存储对象实例,垃圾回收的主要区域就是堆内存。
// JVM内存结构图示
+--------------------+
| 方法区 |
+--------------------+
| 程序计数器 |
+--------------------+
| JVM栈 |
+--------------------+
| |
| 堆内存 |
| |
+--------------------+
- 堆内存中又分为几个区域,主要有两个:新生代和老年代。新生代用于存储新创建的对象,老年代中存放老化对象。
+--------------------+
| 方法区 |
+--------------------+
| 程序计数器 |
+--------------------+
| JVM栈 |
+--------------------+
| 新生代 |
| |
| 堆内存 |
| |
| 老年代 |
+--------------------+
-
垃圾收集器会根据这些内存区域中的对象进行回收,比如新生代使用Copying算法,老年代使用Mark-Sweep算法。
-
情景举例:
- 对象在Eden出生,经过第一次Minor GC后未死亡进入Survivor,多次MinorGC后仍存活进入老年代。
- 老年代空间不足触发Major GC,回收部分垃圾对象。
- 老年代的对象通过晋升至永久代,如果永久代填满,会抛出
OOM
异常。
面试官: 嗯!啊。
最近我在更新《面试1v1》系列文章,主要以场景化的方式,讲解我们在面试中遇到的问题,致力于让每一位工程师拿到自己心仪的offer,感兴趣可以关注公众号JavaPub追更!
🎁目录合集:
Gitee:https://gitee.com/rodert/JavaPub
GitHub:https://github.com/Rodert/JavaPub