Java岗大厂面试百日冲刺【Day41】—— JVM3
本文已获得原作者 _陈哈哈 授权并经过重新整理规划后发布。
本栏目Java开发岗高频面试题主要出自以下各技术栈:Java基础知识、集合容器、并发编程、JVM、Spring全家桶、MyBatis等ORMapping框架、MySQL数据库、Redis缓存、RabbitMQ消息队列、Linux操作技巧等。
地球人都知道,Java有个东西叫垃圾收集器(GC),它让创建的对象不需要像c/c++那样delete、free掉,你能不能谈谈,GC是在什么时候,对什么东西,做了什么事情? 这个是经典抛砖引玉的问法,既能看出我们对GC掌握的情况,也能从多点切入深问,看着三句话,其实每句话的知识点都够我们喝一壶的。JVM接下来三篇会以:什么时候进行GC、对哪些对象、做哪些处理三个角度的面试问题进行整理,来吧,卷。 本篇的内容为第二篇,主要基于对哪些对象这个方向来学习,大家有问题请在评论区喷我或互喷,喷出来的问题才会印象深刻。 对哪些对象,对于GC垃圾回收来说很好理解,当然是针对垃圾,或者说堆中占着茅坑不拉屎(无引用)的对象,将这类对象的内存释放,给新的对象腾出坑来(空间)。 这些垃圾我们通常把他们称为已死亡对象或可回收对象。
面试题1:如何判断对象是否存活
对于判断对象是否存活,主要是两种基本算法,引用计数和可达性分析,目前java主要采用的是可达性分析算法
.
1.引用计数算法
判断对象是否存活的方式如:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的
。
其优点是实现简单,判定效率高;缺点是该算法有很多例外情况要考虑,必须要配合大量额外处理才能保证正确地工作,且存在一个基本的难题,也就是很难处理循环引用
关系。
2.可达性分析算法
其原理简单来说,就是将对象及其引用关系看作一个图,通过一系列称为GC Roots
的根对象作为起始节点集,从这些节点开始,通过引用关系向下搜索,搜索过程所走过的路径称为引用链(Reference Chain)
,如果一个对象和 GC Roots 之间不可达,也就是不存在引用链条,那么即认为是可回收对象
。JVM会把虚拟机栈和本地方法栈中正在引用的对象、静态属性引用的对象和常量
,作为 GC Roots。
如图所示,对象object 5、object 6、object 7虽然互有关联,但是它们到GC Roots是不可达的, 因此它们将会被判定为可回收的对象。
面试题2:哪些对象可以作为GC Roots?
在Java技术体系里面,固定可作为GC(Garbage Collector) Roots的对象包括以下几种:
常说的GC(Garbage Collector) roots,特指的是垃圾收集器的根对象(GC Root Object)
,也叫作称为GC垃圾回收根(GC Root),GC会收集那些不是GC roots且没有被GC roots引用的对象清理掉
。
一个对象可以属于多个root,GC root存在几下种:
1. 虚拟机栈中引用 的对象
- 如各个线程被调用的方法堆栈中使用到的 参数、局部变量、临时变量等。
2. 方法区中类静态属性引用的对象
- 如Java 类的引用类型静态变量
3. 方法区中常量引用的对象
- 如字符串常量池(String Table) 里的引用
4. 本地方法栈内 JNI(通常说的本地方法)引用的对象
5. 所有被同步锁 (synchronized关键字) 持有的对象
6. Java 虚拟机内部的引用
- 如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)、系统类加载器等。
- 反映 Java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等。
除了固定的 GC Roots 集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象 临时性
地加入,共同构成完整 GC Roots 集合。
面试题3:你了解的对象引用方式都有哪些?
在Java语言中,除了基本数据类型外,其他的都是指向各类对象的对象引用;Java中根据其生命周期的长短,将引用分为强引用、弱引用、软引用、虚引用
4类。
1 强引用
我们平常典型编码Object obj = new Object()
中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。 当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的存活对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null
,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。
2 软引用
软引用通过SoftReference
类实现。
软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象
:即JVM 会确保在抛出OutOfMemoryError之前
,清理软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue.poll()
方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。
应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
3 弱引用
弱引用通过WeakReference
类实现。
弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
应用场景:弱引用同样可用于内存敏感的缓存。
4 虚引用
虚引用也叫幻象引用
,通过PhantomReference
类来实现。
无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通过判断引用队列中是否已经加入了虚引用
,来了解被引用的对象是否将要被垃圾回收
。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取一些程序行动。
应用场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知。