关注Java后端技术栈”
回复“面试”获取最新资料
本文内容
如何确定一个对象是垃圾对象?
对于一部分人来说先搞清楚,对象与对象引用到底是什么区别?如果还不是很确定这两者的关系,请看:浅谈Java中的对象和对象引用
Java是面向对象编程的语言,都说在Java世界里万事万物皆对象。
类的声明周期和对象的声明周期。关于类的声明周期请看:JVM系列——java文件到JVM中的整个过程
下面大致说说对象的生命周期。
在创建阶段有几个步骤完成对象的创建:
为对象分配存储空间
开始构造对象
从超类到子类对static成员进行初始化
超类成员变量按顺序初始化,递归调用超类的构造方法
子类成员变量按顺序初始化,子类构造方法调用
一旦对象被创建,并被分派给某些变量赋值,这个对象的状态就切换到了应用阶段
该对象应该至少被一个强引用持有着。
当一个对象处于不可见阶段时,说明程序本身不再持有该对象的不论什么强引用,尽管该这些引用仍然是存在着的。简单说就是程序的运行已经超出了该对象的作用域了。
对象处于不可达阶段是指该对象不再被不论什么强引用所持有。
与“不可见阶段”相比,“不可见阶段”是指程序不再持有该对象的不论什么强引用,这样的情况下,该对象仍可能被JVM
等系统下的某些已装载的静态变量或线程或JNI等强引用持有着,这些特殊的强引用被称为”GC root
”。存在着这些GC root
会导致对象的内存泄露情况,无法被回收。
当垃圾回收器发现该对象已经处于“不可达阶段”而且垃圾回收器已经对该对象的内存空间又一次分配做好准备时,则对象进入了“收集阶段”。假设该对象已经重写了finalize()方法,则会去运行该方法的终端操作。
这里要特别说明一下:不要重载finazlie()
方法!finial方法来自Object中。参考:
原因有两点:
会影响JVM的对象分配与回收速度
在分配该对象时,JVM需要在垃圾回收器上注冊该对象,以便在回收时可以运行该重载方法;在该方法的运行时需要消耗CPU时间且在运行完该方法后才会又一次运行回收操作,即至少需要垃圾回收器对该对象运行两次GC。
可能造成该对象的再次“复活”
在finalize()方法中,假设有其他的强引用再次持有该对象,则会导致对象的状态由“收集阶段”又又一次变为“应用阶段”。这个已经破坏了Java对象的生命周期进程,且“复活”的对象不利用兴许的代码管理。
当对象运行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。
垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间又一次分配阶段”。
先来了解对象的四大引用方式:
JDK
从1.2开始增加了多种引用方式:软引用、弱引用、虚引用 。
/**
* /强引用
*/
private void strangeReference() {
String str = new String("");
System.out.println(str);
UserVo [] userVo = new UserVo[1000000000];
System.out.println(userVo.toString());
}
方法中就算内存不够了而导致OOM
,也不会进行回收这种引用。也是我们平时对象使用方式最多的方式。
/**
* 软引用
*/
private void softReference() {
Object obj = new Object();
SoftReference
如果一个对象具有软引用,内存足够GC就不会回收它,内存不足时就会回收这些对象的内存,故可以防止内存 泄露,增强代码的健壮性 。软引用用来实现内存敏感的高速缓存,比如说:网页缓存、图片缓存等。
/**
* 弱引用
*/
private void weakReference() {
String str = new String("abc");
WeakReference abcWeakRef = new WeakReference(str);
}
弱引用也是用来描述非必需对象的,当JVM
进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在Java中,用java.lang.ref.WeakReference
类来表示。
经典实用案列java.langThreadLocal
中
ThreadLocal
相关请参考:
ThreadLocal造成内存溢出OOM面试再问ThreadLocal,别说你不会
再谈ThreadLocal
ThreadLocal 面试六连问,中高级必问
/**
* 虚引用
*/
private void phantomReference() {
String str = new String("abc");
ReferenceQueue rq = new ReferenceQueue();
PhantomReference phantomRef = new PhantomReference(str, rq);
}
如果一个对象仅持有虚引用,那么它就和没有引用一样,在任何时候都可以被垃圾回收期收回 作用:跟踪对象被垃圾回收器回收的活动。
要想进行垃圾回收,得知道什么样的对象是垃圾。
对于某个对象而言,只要应用程序中持有该对象的引用,就说明对象不是垃圾,如果一个对象没有任何引用指向它,那么改对象就是垃圾对象。
在对象头处维护一个counter,每增加一次对该对象的引用计数器自加,如果对该对象的引用失联,则计数器自减。当counter为0时,表明该对象已经被废弃,不处于存活状态。这种方式一方面无法区分软、虛、弱、强引用类别。另一方面,会造成死锁,假设两个对象相互引用始终无法释放counter,永远不能
GC
。
弊端:如果一个对象A持有对象B,而对象B也持有一个对象A,那发生了类似操作系统中死锁的循环持有,这种情况下A与B的counter恒大于1,会使得GC
永远无法回收这两个对象 。
通过一系列为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明该对象是不可用的。如果对象在进行可行性分析后发现没有与GC Roots相连的引用链,也不会理解死亡。它会暂时被标记上并且进行一次筛选,筛选的条件是是否与必要执行finalize()方法。如果被判定有必要执行finaliza()方法,就会进入F-Queue队列中,并有一个虚拟机自动建立的、低优先级的线程去执行它。稍后GC将对F-Queue中的对象进行第二次小规模标记。如果这时还是没有新的关联出现,那基本上就真的被回收了。
那么那些点可以作为GC Roots
呢?一般来说,如下情况的对象可以作为GC Roots
:
虚拟机栈(栈桢中的本地变量表)中的引用的对象
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI(Native方法)的引用的对象
对于大多数人来说主要是理解上面这四点。要通过这四点去反推,去领悟为什么能把他们当做GC Roots
。把四点理解清楚了,理解的这GC Roots
就简单了。
往期精彩:
面试被问:缓存击穿有什么解决方案
面试被问:5 亿整数的大文件,排个序 ?
面试被问:高并发下的接口幂等性
面试官问:浏览器输入 URL 回车之后发生了什么?
面试题:数据量很大,分页查询很慢,有什么优化方案?
凭借这套面试题,工资狂涨60%!!!
在这金三银四的季节,栈长为大家准备了几份面试宝典:
《java面试宝典5.0》
《Java(BAT)面试必备》
《350道Java面试题:整理自100+公司》
《资深java面试宝典-视频版》
大量电子书籍
获取方式:点“在看”,V信扫描上面二维码领取。