结论:堆外内存只有在fullgc的时候才能够回收掉。
DirectByteBuffer(int cap) { 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; }
分配内存
static void reserveMemory(long size, int cap) { if (!memoryLimitSet && VM.isBooted()) { maxMemory = VM.maxDirectMemory(); memoryLimitSet = true; } // 如果能够分配 if (tryReserveMemory(size, cap)) { return; } // 分配失败了 final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess(); // retry while helping enqueue pending Reference objects // which includes executing pending Cleaner(s) which includes // Cleaner(s) that free direct buffer memory // 尝试回收 while (jlra.tryHandlePendingReference()) { if (tryReserveMemory(size, cap)) { return; } } // trigger VM's Reference processing // 如果还是失败,触发gc(只是建议jvm gc) System.gc(); // a retry loop with exponential back-off delays // (this gives VM some time to do it's job) // 循环几次 休息1ms(让出cpu 等待jvm gc),尝试分配,尝试10次后报错 boolean interrupted = false; try { long sleepTime = 1; int sleeps = 0; while (true) { if (tryReserveMemory(size, cap)) { return; } if (sleeps >= MAX_SLEEPS) { break; } if (!jlra.tryHandlePendingReference()) { try { Thread.sleep(sleepTime); sleepTime <<= 1; sleeps++; } catch (InterruptedException e) { interrupted = true; } } } // no luck throw new OutOfMemoryError("Direct buffer memory"); } finally { if (interrupted) { // don't swallow interrupts Thread.currentThread().interrupt(); } } }
分配
private static boolean tryReserveMemory(long size, int cap) { // -XX:MaxDirectMemorySize limits the total capacity rather than the // actual memory usage, which will differ when buffers are page // aligned. long totalCap; // 看看有没有超过最大容量 这里没有根据实际的内存做判断 // 也就是说你把-XX:MaxDirectMemorySize设置的很大,不会触发gc // 后面系统实际分配不了会抛oom while (cap <= maxMemory - (totalCap = totalCapacity.get())) { if (totalCapacity.compareAndSet(totalCap, totalCap + cap)) { reservedMemory.addAndGet(size); count.incrementAndGet(); return true; } } return false; }
回收
static boolean tryHandlePending(boolean var0) { Reference var1; Cleaner var2; try { synchronized(lock) { if (pending == null) { if (var0) { lock.wait(); } return var0; } // jvm回收时,会把虚引用放入pending var1 = pending; // 判断是不是cleaner类型 var2 = var1 instanceof Cleaner ? (Cleaner)var1 : null; pending = var1.discovered; var1.discovered = null; } } catch (OutOfMemoryError var6) { Thread.yield(); return true; } catch (InterruptedException var7) { return true; } // 如果是cleaner类型 调用clean方法 if (var2 != null) { var2.clean(); return true; } else { ReferenceQueue var3 = var1.queue; if (var3 != ReferenceQueue.NULL) { var3.enqueue(var1); } return true; } }
clean对象
public class Cleaner extends PhantomReference
清理器释放堆外内存
private Deallocator(long address, long size, int capacity) { assert (address != 0); this.address = address; this.size = size; this.capacity = capacity; } public void run() { if (address == 0) { // Paranoia return; } unsafe.freeMemory(address); address = 0; Bits.unreserveMemory(size, capacity); } }
总结:
DirectByteBuffer再构造的时候含有一个cleaner对象,这个是一个虚引用,当buffer的引用被回收的时候(没有强引用了),jvm会把clean对象加入引用队列,然后在分配内存失败的时候(现有使用量+分配量 大于 最大容量),会尝试回收,遍历引用队列,调用cleaner的clean方法。
ps:Reference类的静态块起了一个线程,用来不停的清理引用队列。
static { ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent()); Thread handler = new ReferenceHandler(tg, "Reference Handler"); handler.setPriority(Thread.MAX_PRIORITY); handler.setDaemon(true); handler.start(); // provide access in SharedSecrets SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { @Override public boolean tryHandlePendingReference() { return tryHandlePending(false); } }); }
hanler
private static class ReferenceHandler extends Thread { private static void ensureClassInitialized(Class> clazz) { try { Class.forName(clazz.getName(), true, clazz.getClassLoader()); } catch (ClassNotFoundException e) { throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e); } } static { // pre-load and initialize InterruptedException and Cleaner classes // so that we don't get into trouble later in the run loop if there's // memory shortage while loading/initializing them lazily. ensureClassInitialized(InterruptedException.class); ensureClassInitialized(Cleaner.class); } ReferenceHandler(ThreadGroup g, String name) { super(g, name); } public void run() { while (true) { tryHandlePending(true); } } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)