这篇文章出现的原因在于自己一直困惑于java中对DirectBuffer的内存的回收,知道它是可以经由java虚拟机自动管理的,不需要我们自己去手动的释放,但是要知道DirectBuffer的内存独立于jvm,而是直接调用的malloc来分配的,那么也必定需要free来释放。。。莫非虚拟机的gc还有这功能。。。?
其实不然,最终采用的是一种折中的方法来实现的。。。。
在将具体的实现之前先要对java中的Reference比较了解了才可以。。。。
Reference主要是分为4中类型:
(1)strong reference,这个也是我们平时用到的最多的,我们平时申明一个变量,那么它默认就是这种类型,对于这种类型应用的java对象,当且只当当前的运行栈中没有它的引用之后,它就将会被gc掉。。。这也就是我们说的最多的垃圾回收。。
(2)SoftReference。。这是一种比strong reference弱一些的引用类型,当当前虚拟机内存很紧张的时候,那么gc将会将其引用的对象给回收掉。。这也就是说就算我们对对象用SoftReference引用,但是它也可能会被gc给回收了。。。所以这种引用类型非常适合用于实现高速缓存。。。。
(3)WeakReference,它比上面提到的SoftReference还要弱一些。。。只要gc运行了,它被引用的对象就会被回收。。可以用下面的例子来说明:
public class Fjs {
static public Logger logger = Logger.getLogger(Fjs.class);
public static void main(String args[]) {
/*logger.debug("aaaaa");
ByteBuffer buffer = ByteBuffer.allocateDirect(10000);
PhantomReference ref = new PhantomReference (buffer, null);
Reference ref2 = new WeakReference(buffer);
ref.get();*/
Reference ref = new WeakReference(new Fjs());
System.out.println(ref.get()); //打印出引用的对象
System.gc();
System.out.println(ref.get());
}
}
输出的结果是:
fjs.Fjs@5ca0b138
null
由此可见,gc之后引用的Fjs类型的对象被回收掉了。。。
但是如果我们将WeakReference换成了SoftReference了之后,如下:
public static void main(String args[]) {
/*logger.debug("aaaaa");
ByteBuffer buffer = ByteBuffer.allocateDirect(10000);
PhantomReference ref = new PhantomReference (buffer, null);
Reference ref2 = new WeakReference(buffer);
ref.get();*/
Reference ref = new SoftReference(new Fjs());
System.out.println(ref.get()); //打印出引用的对象
System.gc();
System.out.println(ref.get());
}
输出的结果将会是:
fjs.Fjs@704d4834
fjs.Fjs@704d4834
这里也就可以得出结论SoftReference要比WeakReference强一些。。。。
(4)Phantomreference,这个是最神奇的了。。。代码如下:
public static void main(String args[]) {
/*logger.debug("aaaaa");
ByteBuffer buffer = ByteBuffer.allocateDirect(10000);
PhantomReference ref = new PhantomReference (buffer, null);
Reference ref2 = new WeakReference(buffer);
ref.get();*/
Reference ref = new PhantomReference(new Fjs(), null);
System.out.println(ref.get()); //打印出引用的对象
}
这里还没有执行gc,输出就会使null,也就是木有啦。。其实不然。。我们来看看 PhantomReference的定义吧:
public class PhantomReference<T> extends Reference<T> {
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
/**
* Creates a new phantom reference that refers to the given object and
* is registered with the given queue.
*
* <p> It is possible to create a phantom reference with a <tt>null</tt>
* queue, but such a reference is completely useless: Its <tt>get</tt>
* method will always return null and, since it does not have a queue, it
* will never be enqueued.
*
* @param referent the object the new phantom reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
这里可以看到它将get方法重载为直接返回null,因此可以知道,如果我们有对象被PhantomReference引用的话,如果还想用它,那么就还需要维持一个它的其他类型的引用,向上面那样,直接传一个匿名对象进去,那么就再也不要想获取那个对象的引用了。。。另外这里构造函数中还需要一个ReferenceQueue,它的作用也很重要。。当我们引用的对象被回收之后,这个PhantomReference对象本身将会被放到这个队列中去。。。。这件事情是gc来干的。。我们就不去深究了。。只要知道是这个样子实现的就好了。。。
我们可以用下面的例子来证明:
public static void main(String args[]) throws IllegalArgumentException, InterruptedException {
/*logger.debug("aaaaa");
ByteBuffer buffer = ByteBuffer.allocateDirect(10000);
PhantomReference ref = new PhantomReference (buffer, null);
Reference ref2 = new WeakReference(buffer);
ref.get();*/
ReferenceQueue queue = new ReferenceQueue();
Reference ref = new PhantomReference(new Fjs(), queue);
System.out.println(ref);
System.out.println(queue.remove(100));
System.gc();
System.out.println(queue.remove(100));
}
输出的结果将会是:
java.lang.ref.PhantomReference@4c6ca32e
null
java.lang.ref.PhantomReference@4c6ca32e
可以看到ref在gc之后被加入到了queue里面了。。。。
到了这里就会考虑PhantomReference有啥用了吧。。。那么这里就可以结合DirectBuffer的回收来说了。。哈哈。。。
首先我们来看看DirectBuffer的创建吧:
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
这个是整个DirctBuffer的创建过程。。这里可以看到其实是调用unsafe对象来分配内存,然后这里会记录下分配的内存的位置,还要创建一个非常重要的cleaner对象。。。。而且在创建cleaner对象的时候还创建了一个Deallocator对象。。。这两个东西将会是DirectBuffer释放申请的内存的非常关键的两个东西。。。
而且这里可以看到底层用到的内存是通过unsafe对象分配的,擦。。这个就没有办法了。。。不过无非就是通过malloc啥的分配内存嘛。。。。
那么我们来看看这个cleaner对象是个啥东西吧。。。。
好吧,sun.misc.Cleaner,我勒个去。。又看不了。。不过这里可以知道它继承自PhantomReference。。。呵呵。。。呵呵。。。
那么我们就可以知道当其会被加入到一个queue里面。。那么关键就在这个地方了。。。
来看一段代码:
/* List of References waiting to be enqueued. The collector adds
* References to this list, while the Reference-handler thread removes
* them. This list is protected by the above lock object.
*/
private static Reference pending = null;
/* High-priority thread to enqueue pending References
*/
private static class ReferenceHandler extends Thread {
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
for (;;) {
Reference r;
synchronized (lock) {
if (pending != null) {
r = pending;
Reference rn = r.next;
pending = (rn == r) ? null : rn;
r.next = r;
} else {
try {
lock.wait();
} catch (InterruptedException x) { }
continue;
}
}
// Fast path for cleaners
if (r instanceof Cleaner) {
((Cleaner)r).clean();
continue;
}
ReferenceQueue q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
}
}
}
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
/* If there were a special system-only priority greater than
* MAX_PRIORITY, it would be used here
*/
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
}
这个是定义在reference里面的一段代码,从上面注释就可以知道。。。对于所有需要放入到queue里面的reference将会先在这里连接到pending后面,形成一个链表。。。这个可能是gc做的吧。。
而在下面定义了一个Thread,它要做的事情。。不断的遍历这个链表,将后面的reference放到其应该去的queue里面去,不过这里就还有一段其他的代码。。
判断它是否是cleaner类型的,如果是的话,那么将会执行它的clean方法。。。
而且可以看到这个Thread在后面被启动了。。。而且将其设置为后台线程。。当虚拟机启动后它就会被启动。。。。
那么到这里其实就基本知道了DirectBuffer里面申请的内存是怎么自动被释放的了吧。。。由于clean方法我们看不到代码。。不过其实主要你也就是执行Deallocator的run方法:
public void run() {
if (address == 0) {
// Paranoia
return;
}
unsafe.freeMemory(address);
address = 0;
Bits.unreserveMemory(size, capacity);
}
也就是再次调用unsafe将以前申请的内存释放掉。。。。
到这里也就可以知道。当我们定义DirectBuffer类型的对象被释放之后,其底层申请的直接内存也会自动的被释放。。不过这个自动释放的过程算是比较的曲折了。。。。
不过这里有一个问题。。。不知道怎么回事。。。按照道理来说PhantomReference,只要gc启动了,那么PhantomReference类型的对象就应该被gc放到pending链表,然后再由那个后台线程来处理。。。
那么如果真是这样子的话,那么只要gc我们申请的DirectBuffer底层的内存就会被释放掉。。。
但是实际情况不是这样子的,如果我们DirectBuffer对象没有被释放。。就算是gc,底层申请的内存也不会被释放。。那么也就是说gc并没有把继承自PhantomReference的cleaner放到pending链表了。。。这个就与PhantomReference有一定的冲突了吧。。。不过也有可能Cleaner类型做了一些其余的事情吧。。。或者说gc对于Cleaner特殊的对待了。。。
不懂。。。。。