前言
作为一个有经验的开发人员,面试中难免会被问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
接着我们运行下代码,查看打印出的日志
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 实习生/初级
List 和 Set 的区别 HashSet 是如何保证不重复的
HashMap 是线程安全的吗,为什么不是线程安全的(最好画图说明多线程环境下不安全)?
HashMap 的扩容过程
HashMap 1.7 与 1.8 的 区别,说明 1.8 做了哪些优化,如何优化的?
对象的四种引用
Java 获取反射的三种方法
Java 反射机制
Arrays.sort 和 Collections.sort 实现原理 和区别
Cloneable 接口实现原理
异常分类以及处理机制
wait 和 sleep 的区别
数组在内存中如何分配
答案展示:
美团——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 内部执行流程
答案展示:
蚂蚁金服——Java 高级
题 1:
- jdk1.7 到 jdk1.8 Map 发生了什么变化(底层)?
- ConcurrentHashMap
- 并行跟并发有什么区别?
- jdk1.7 到 jdk1.8 java 虚拟机发生了什么变化?
- 如果叫你自己设计一个中间件,你会如何设计?
- 什么是中间件?
- ThreadLock 用过没有,说说它的作用?
- Hashcode()和 equals()和==区别?
- mysql 数据库中,什么情况下设置了索引但无法使用?
- mysql 优化会不会,mycat 分库,垂直分库,水平分库?
- 分布式事务解决方案?
- sql 语句优化会不会,说出你知道的?
- mysql 的存储引擎了解过没有?
- 红黑树原理?
题 2:
- 说说三种分布式锁?
- redis 的实现原理?
- redis 数据结构,使⽤场景?
- redis 集群有哪⼏种?
- codis 原理?
- 是否熟悉⾦融业务?记账业务?蚂蚁⾦服对这部分有要求。
好啦~展示完毕,大概估摸一下自己是青铜还是王者呢?
前段时间,在和群友聊天时,把今年他们见到的一些不同类别的面试题整理了一番,于是有了以下面试题集,也一起分享给大家~
如果你觉得这些内容对你有帮助,可以加入csdn进阶交流群,领取资料
基础篇
JVM 篇
MySQL 篇
Redis 篇
由于篇幅限制,详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
需要的小伙伴,可以一键三连,下方获取免费领取方式!