我是清都山水郎,天教懒慢带疏狂。曾批给露支风券,累奏流云借月章。 诗万首,酒千觞,几曾着眼看侯王。玉楼金阙慵归去,且插梅花醉洛阳。
在进行垃圾回收的时候,对于 JVM
而言,什么对象才算是垃圾呢?如何判断某些对象是垃圾呢?
很明显的,已经没有被使用的对象,就是垃圾。
引用计数法
引用计数法是用于判断对象是垃圾的一种方式。
如果被其他对象引用,那么对象的引用计数就会+1。当引用失效时,引用计数就-1。当引用等于0时,即表示对象已无用。
引用计数虽然实现简单,但是无法处理循环引用的问题,也因此没有被采用。
循环引用
对象A引用对象B,对象B的引用+1;对象B引用对象A,对象A的引用+1。但是对象A,对象B没有被使用。从上帝视角来看,A、B已经是无用对象了。但是由于引用计数不等于0,因此不会被认为是垃圾。
GC Root 可达性分析
JVM
使用 GC Root
可达性分析判断对象是否为垃圾。
对象是否应该被回收,判断条件如下
- 从
Root
对象出发,如果对象可被访问到,那么此对象就不应该被回收。 - 对象被回收前,可执行一次
finalize()
方法,如果在finalize()
方法中复活,则不会被回收。需要注意的是:finalize()
方法只会被一个对象执行一次。
根对象定义
想必,你一定很疑惑,what is root object?
。
官方文档定义:
Garbage Collection Roots
A garbage collection root is an object that is accessible from outside the heap. The following reasons make an object a GC root:
- System Class
Class loaded by bootstrap/system class loader. For example, everything from the rt.jar like java.util.* .- JNI Local
Local variable in native code, such as user defined JNI code or JVM internal code.- JNI Global
Global variable in native code, such as user defined JNI code or JVM internal code.- Thread Block
Object referred to from a currently active thread block.- Thread
A started, but not stopped, thread.- Busy Monitor
Everything that has called wait() or notify() or that is synchronized. For example, by calling synchronized(Object) or by entering a synchronized method. Static method means class, non-static method means object.- Java Local
Local variable. For example, input parameters or locally created objects of methods that are still in the stack of a thread.- Native Stack
In or out parameters in native code, such as user defined JNI code or JVM internal code. This is often the case as many methods have native parts and the objects handled as method parameters become GC roots. For example, parameters used for file/network I/O methods or reflection.- Finalizable
An object which is in a queue awaiting its finalizer to be run.- Unfinalized
An object which has a finalize method, but has not been finalized and is not yet on the finalizer queue.- Unreachable
An object which is unreachable from any other root, but has been marked as a root by MAT to retain objects which otherwise would not be included in the analysis.- Java Stack Frame
A Java stack frame, holding local variables. Only generated when the dump is parsed with the preference set to treat Java stack frames as objects.- Unknown
An object of unknown root type. Some dumps, such as IBM Portable Heap Dump files, do not have root information. For these dumps the MAT parser marks objects which are have no inbound references or are unreachable from any other root as roots of this type. This ensures that MAT retains all the objects in the dump.
其他参考:知乎回答
通过 finalize() 复活
public class FinalizeTest {
public static FinalizeTest obj;
public static void main(String[] args) throws InterruptedException {
obj = new FinalizeTest();
obj = null;
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj = null");
} else {
System.out.println("obj 可用");
}
System.out.println("第2次GC");
obj = null;
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj = null");
} else {
System.out.println("obj 可用");
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("对象复活");
obj = this;
}
}
以上程序依次打印如下:
对象复活
obj 可用
第2次GC
obj = null
finalize()
对于一个对象而言,只会在要被回收时,才会调用一次。在第 1 次 GC 时,通过 finalize
复活,第二次 GC 没有调用 finalize
,顺利被回收。
糟糕的 finalize
finalize()
方法由 FinalizerThread
线程执行。一个对象若重写 finalize()
方法,那么在被回收时,会被加入到 FinalizerThread
的执行队列 FinalReference
中。
此时对象会被 Reference
中的 referent
字段所指向,因此对象会重新变成可达。如果 finalize()
过于耗时,那么会阻碍 GC 的正常回收。
下面就展示一段糟糕的 finalize 实现。
使用 -Xms10m -Xmx10m
执行 一段时间后,会报 OOM
public class FinalizeReferenceTest {
public static void main(String[] args) {
for (int i = 0; i < 50000; i++) {
LF lf = new LF();
}
}
public static class LF {
private byte[] bs = new byte[1024];
@Override
protected void finalize() throws Throwable {
super.finalize();
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
}
}
在 finalize()
的实现中, Thread.sleep(1000)
表示在执行耗时的任务。FinalizerThread
在执行 finalize()
方法时,是从队列中一项一项取出来执行的, finalize()
执行过久,导致大量对象一直被引用指向,无法正常回收。
如果将 LE#finalize()
方法去掉,程序会正常执行。
引用队列
JAVA
中根据对象引用强弱,有 4 大引用对象:强引用,软引用(SoftReference
),弱引用(WeakReference
),虚引用(PhantomReference
)。
正常创建的对象为强引用对象。对于 软引用,弱引用,虚引用,当对象被回收时,会被加入到引用队列中,可以通过引用队列来监听到对象被回收。
下面的 demo
展示了,当对象被回收后,通过引用队列进行监控
public class ReferenceQueueTest {
static ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
public static void main(String[] args) throws InterruptedException {
monitorSoftReference();
addSoftReference();
}
private static void addSoftReference() throws InterruptedException {
Object o = new Object();
ObjectWeakReference reference = new ObjectWeakReference(o, referenceQueue);
o = null;
System.gc();
Thread.sleep(1000);
}
private static void monitorSoftReference() throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
ObjectWeakReference reference = null;
try {
reference = (ObjectWeakReference) referenceQueue.remove();
} catch (Exception e) {
e.printStackTrace();
}
if (reference != null) {
System.out.println("对象被回收");
}
}
});
thread.start();
}
public static class ObjectWeakReference extends WeakReference<Object> {
public ObjectWeakReference(Object referent, ReferenceQueue<? super Object> q) {
super(referent, q);
}
}
}
参考
Garbage Collection Roots
JAVA垃圾回收,线程栈中哪些东西作为GC RootS?
ReferenceQueue的使用
《实战Java虚拟机:JVM故障诊断与性能优化(第2版)》
既然选择了远方,即使天寒地冻,路遥马亡,我本就一无所有,又有何惧。
‘深入交流’