在说引用之前我们,常会判断一个对象是否已死,这个会涉及到两种算法,以至于会影响虚拟机的垃圾回收的方式和垃圾回收器的应用。
两种算法:引用计数法,可达性分析算法
应用计数算法
: 在一个对象中添加一个引用计数器,每当有一个地方引用它的时候,计数器的值就加一; 当引用失效的时候,计数器的值就减一;任何时刻计数器为零的对象就不能再次使用的。但是这个算法有个弊端,就是当对象出相互引用的时候,可能会导致两个对象的引用都不为零,无法回收。
可达性分析算法
:通过一系列的“GC Roots” 的根对象作为起始节点集,从这些节点向下搜索,某个对象到“GC Roots” 之间没有任何引用链相连,或者说是从GC Roots 到这个对象不可达的时候,则证明这个对象是不可能再被使用的。举一个例子 List list = new ArrayList() ; 这个语句中 list 这个局部变量在 栈中,而这个对象在堆中,而 作为GC roots 的指的是栈中list 这个引用对象指的是 Arraylist 对象。
Java中对引用 的概念进行看扩充,将引用分为强引用,软引用,弱引用,虚引用四种,四种由强到弱。
例如:SoftReference a = new SoftReference(new A());
如果仅有软引用该对象时,首次垃圾回收不会回收该对象,如果内存仍不足,再次回收时才会释放对象
软引用自身需要配合引用队列来释放
例如:WeakReference a = new WeakReference(new A());
如果仅有弱引用引用该对象时,只要发生垃圾回收,就会释放该对象
弱引用自身需要配合引用队列来释放
典型例子是 ThreadLocalMap 中的 Entry 对象
查看弱引用的源码发现,构造方法有两种,一种是只传入对象的,创建弱引用将对象关联,另外一种是可以传入一个引用队列,当引用对象被回收的时候就会将引用对象,放入引用队列中,就会对被回收的对象进行一种标记,在引用对象中自己实现清除方法,将引用队列中的对象关联的对象进行清除,当gc 后可以,可以进行清除
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
ThreadlocalMap 就会是类似的情况,其中的内部节点类 Entry 继承了WeakReference 在创建的对象的时候键是作为弱引用,而值是强引用。所以容易出现内存泄漏。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
所以当我们需要防止内存泄漏的时候,可以将这个引用队列传入,另外再写一个clean 方法将这个引用队列中的对象进行清除。
static ReferenceQueue<Object> queue = new ReferenceQueue<>();
static class Entry extends WeakReference<String> {
String value;
public Entry(String key, String value) {
super(key, queue);
this.value = value;
}
}
public void clean() {
Object ref;
while ((ref = queue.poll()) != null) {
System.out.println(ref);
for (int i = 0; i < table.length; i++) {
if(table[i] == ref) {
table[i] = null;
}
}
}
}
Entry[] table = new Entry[4];
在jkd 中有一个 WeakHashMap 类其中节点就是实现了 WeakReaference
例如: PhantomReference a = new PhantomReference(new A(), referenceQueue);
必须配合引用队列一起使用,当虚引用所引用的对象被回收时,由 Reference Handler 线程将虚引用对象入队,这样就可以知道哪些对象被回收,从而对它们关联的资源做进一步处理
典型例子是 Cleaner 释放 DirectByteBuffer 关联的直接内存
我们查看PhantomReference 的源码发现构造方法中必须传入一个引用队列,如果使用虚引用创建的对象,会在垃圾回收的时候,同时将引用的对象也会放入一个引用队列中,就会知道这个队列中引用的资源被回收,可以做一些进一步的处理,对一些这个对象所引用的外部资源(非堆内存的对象)进行清除。
public class PhantomReference<T> extends Reference<T> {
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
但是值得注意的一个点当我们注意一些,gc 只会清除堆中的垃圾和对象,不会去清除常量池中的对象,比如第二个对象被存在常量池中,在直接内存内,gc 不会清除,同样的虚引用对象也不会加入引用队列。
list.add(new PhantomReference<String>(new String("Hello"),queue));
list.add(new PhantomReference<String>("Hello",queue));
其实查看前几种的引用的作用都是配合引用队列对一些资源进行清理,可能是外部内存(非堆内存)的资源清除,或者是一些关联强引用资源对象的清除。而在jkd 9 之后出现了一个类 Cleaner 类进行这些动作,对这个进行了封装,我们只需要传入弱引用的对象和需要 清理的动作,后台会自动创建一个线程当弱引用被回收的时候,就会触发清除的动作。但是这个线程是守护线程,在主线程停止之后也会随之停止。
// 传入的是对象和 Runnable 任务
public Cleanable register(Object obj, Runnable action) {
Objects.requireNonNull(obj, "obj");
Objects.requireNonNull(action, "action");
return new CleanerImpl.PhantomCleanableRef(obj, this, action);
}
在jdk 底层也会有这个类,但不是同一个包下,实现基本相同,在nio 那种使用直接内存的地方会出现清除的动作。
如何使用这个方法 ?
这里特别的说明 System.gc () 这个方法时表示用来垃圾回收的,会显式触发 Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。但是不能保证立刻进行回收,虚拟机中也有一个参数可以禁止显示调用的-XX:-+DisableExplicitGC ,这个方法的显示调用就不会生效。
原理步骤:
缺点: