前言

作为一个有经验的开发人员,面试中难免会被问JVM相关的问题。

该被回收了吗

首先,我们需要弄清楚什么样的对象会被回收,总不能正在使用的对象突然就被JVM当作垃圾回收了吧,想必这样的结果广大的程序员都要崩溃了,因此JVM需要准确的判断出谁才是要被回收的垃圾。对于JVM来说,主要判断对象是否需要被回收的方法有两种,引用计数法和可达性分析法,当然,由于引用计数法存在循环引用的情况,所以主流的Java虚拟机中都没用使用它来进行垃圾回收。我们可以看下示例

public class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[2 * _1MB];

public static void main(String[] args) {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;

objA = null;
objB = null;
//手动gc
System.gc();
}
}

当我们执行完main方法时,objA和objB会被回收吗?答案时肯定的,我们看下堆日志的输出结果。首先我们开启IDEA的堆日志打印,Run–>Edit Configuration,在VM Options中设置: ​​-XX:+PrintGCDetails​​​ 猿友们,你的对象被回收了吗?_redis 接着我们运行下代码,查看打印出的日志

D:\tools\jdk\jdk1.8.0_151\bin\java.exe -XX:+PrintGCDetails "-javaagent:D:\IntelliJ IDEA 2020.3.1\lib\idea_rt.jar=63413:D:\IntelliJ IDEA 2020.3.1\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\tianyaa\AppData\Local\Temp\classpath1008509836.jar com.yuan.server.test.ReferenceCountingGC
[GC (System.gc()) [PSYoungGen: 23425K->2302K(74240K)] 23425K->2310K(243712K), 0.0071882 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [PSYoungGen: 2302K->0K(74240K)] [ParOldGen: 8K->2077K(169472K)] 2310K->2077K(243712K), [Metaspace: 3547K->3547K(1056768K)], 0.0098817 secs] [Times: user=0.19 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 74240K, used 427K [0x000000076dc00000, 0x0000000772e80000, 0x00000007c0000000)
eden space 64000K, 0% used [0x000000076dc00000,0x000000076dc6ac20,0x0000000771a80000)
from space 10240K, 0% used [0x0000000771a80000,0x0000000771a80000,0x0000000772480000)
to space 10240K, 0% used [0x0000000772480000,0x0000000772480000,0x0000000772e80000)
ParOldGen total 169472K, used 2077K [0x00000006c9400000, 0x00000006d3980000, 0x000000076dc00000)
object space 169472K, 1% used [0x00000006c9400000,0x00000006c96076e8,0x00000006d3980000)
Metaspace used 3557K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 383K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

可以看到, ​​23425K->2370K​​,证明objA和objB并没有因为彼此引用而躲过回收,同时也说明了JVM不是通过引用计数来判断对象是否存活的。

可达性分析算法

当前主流的系统其实都是采用可达性分析算法来判断对象是否存活,这个算法的逻辑大概如下

graph TD
GC_Roots --> object1 --> object2
object1 --> object3
object4 --> object5
object4 --> object6

可以看到,从​​GC Roots​​​出发,依次可以经过object1、object2、object3,但是object4、object5、object6虽然有相关联系,但是没有经过​​GC Roots​​​,所以当发生GC时,还是会被回收掉的。在Java体系中,固定可以作为​​GC Roots​​的对象有以下几种:


  • 在虚拟机栈中引用的对象
  • 在方法区中类静态属性引用的对象
  • 在方法区中常量引用的对象
  • 在本地方法栈中JNI引用的对象
  • 所有被同步锁持有的对象

最终死亡

那么经过可达性分析后不可达的对象就一定会被回收吗?答案是否定的,真正宣告一个对象的死亡,至少要经过两次标记过程:如果对象在进行可达性分析后发现没有和​​GC Roots​​​相连接的引用链,那么它将会被第一次标记,随后进行一次筛选,筛选的条件时此对象是否有必要执行​​finalize()​​方法。如果对象没有覆盖finalize()方法,或者该方法已经被虚拟机调用过,那么这时候对象都不会被回收。我们看下在《深入理解Java虚拟机》书中的一个小实验:

public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive(){
System.out.println("yse,i am still alive");
}

@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
}

public static void main(String[] args) throws Exception {
SAVE_HOOK = new FinalizeEscapeGC();
//对象第一次自救成功
SAVE_HOOK = null;
System.gc();
//暂停0.5秒等待finalize方法执行
Thread.sleep(500);
if(null != SAVE_HOOK){
SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead");
}

//对象第二次自救成功,这时候并不会成功
SAVE_HOOK = null;
System.gc();
//暂停0.5秒等待finalize方法执行
Thread.sleep(500);
if(null != SAVE_HOOK){
SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead");
}

}
}

运行后看下输出:

finalize method executed!
yse,i am still alive
no,i am dead

可以看到,SAVE_HOOK对象的​​finalize()​​方法的确被垃圾回收器触发,并且在垃圾回收前成功逃脱过。

总结

一直想整理出一份完美的面试宝典,但是时间上一直腾不开,这套一​千多道面试题宝典​,结合今年金三银四各种大厂面试题,以及 ​GitHub 上 star 数超 30K+ 的文档​整理出来的,我上传以后,毫无意外的短短半个小时点赞量就达到了 13k,说实话还是有点不可思议的。

一千道互联网 Java 工程师面试题

内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、SpringBoot、SpringCloud、RabbitMQ、Kafka、Linux等技术栈(485页)

猿友们,你的对象被回收了吗?_java_02

初级—中级—高级三个级别的大厂面试真题

阿里云——Java 实习生/初级


List 和 Set 的区别 HashSet 是如何保证不重复的

HashMap 是线程安全的吗,为什么不是线程安全的(最好画图说明多线程环境下不安全)?

HashMap 的扩容过程

HashMap 1.7 与 1.8 的 区别,说明 1.8 做了哪些优化,如何优化的?

对象的四种引用

Java 获取反射的三种方法

Java 反射机制

Arrays.sort 和 Collections.sort 实现原理 和区别

Cloneable 接口实现原理

异常分类以及处理机制

wait 和 sleep 的区别

数组在内存中如何分配


答案展示:

猿友们,你的对象被回收了吗?_面试_03

猿友们,你的对象被回收了吗?_redis_04

美团——Java 中级


BeanFactory 和 ApplicationContext 有什么区别

Spring Bean 的生命周期

Spring IOC 如何实现

说说 Spring AOP

Spring AOP 实现原理

动态代理(cglib 与 JDK)

Spring 事务实现方式

Spring 事务底层原理

如何自定义注解实现功能

Spring MVC 运行流程

Spring MVC 启动流程

Spring 的单例实现原理

Spring 框架中用到了哪些设计模式

为什么选择 Netty

说说业务中,Netty 的使用场景

原生的 NIO 在 JDK 1.7 版本存在 epoll bug

什么是 TCP 粘包/拆包

TCP 粘包/拆包的解决办法

Netty 线程模型

说说 Netty 的零拷贝

Netty 内部执行流程


答案展示:

猿友们,你的对象被回收了吗?_面试_05

猿友们,你的对象被回收了吗?_java_06

蚂蚁金服——Java 高级

题 1:



  1. jdk1.7 到 jdk1.8 Map 发生了什么变化(底层)?
  2. ConcurrentHashMap
  3. 并行跟并发有什么区别?
  4. jdk1.7 到 jdk1.8 java 虚拟机发生了什么变化?
  5. 如果叫你自己设计一个中间件,你会如何设计?
  6. 什么是中间件?
  7. ThreadLock 用过没有,说说它的作用?
  8. Hashcode()和 equals()和==区别?
  9. mysql 数据库中,什么情况下设置了索引但无法使用?
  10. mysql 优化会不会,mycat 分库,垂直分库,水平分库?
  11. 分布式事务解决方案?
  12. sql 语句优化会不会,说出你知道的?
  13. mysql 的存储引擎了解过没有?
  14. 红黑树原理?


题 2:



  1. 说说三种分布式锁?
  2. redis 的实现原理?
  3. redis 数据结构,使⽤场景?
  4. redis 集群有哪⼏种?
  5. codis 原理?
  6. 是否熟悉⾦融业务?记账业务?蚂蚁⾦服对这部分有要求。


好啦~展示完毕,大概估摸一下自己是青铜还是王者呢?

前段时间,在和群友聊天时,把今年他们见到的一些不同类别的面试题整理了一番,于是有了以下面试题集,也一起分享给大家~

如果你觉得这些内容对你有帮助,可以加入​csdn进阶交流群​​,领取资料

基础篇

猿友们,你的对象被回收了吗?_jvm_07

猿友们,你的对象被回收了吗?_面试_08

JVM 篇

猿友们,你的对象被回收了吗?_redis_09

猿友们,你的对象被回收了吗?_编程语言_10

MySQL 篇

猿友们,你的对象被回收了吗?_编程语言_11

猿友们,你的对象被回收了吗?_面试_12

猿友们,你的对象被回收了吗?_编程语言_13

Redis 篇

猿友们,你的对象被回收了吗?_redis_14

猿友们,你的对象被回收了吗?_编程语言_15



由于篇幅限制,详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!


需要的小伙伴,可以一键三连,下方获取免费领取方式!