Для памяти вне кучи использование System.gc() ненадежно, и полагаться на FGC старого поколения также ненадежно.
предисловие
Для памяти вне кучи использование System.gc() ненадежно, полагаться на FGC старого поколения также ненадежно, и в большинстве руководств по настройке параметр -DisableExplicitGC отключает System.gc(). Поэтому активная переработка более надежна. JDK предоставляет Cleaner в DirectByteBuffer для активного освобождения памяти. В то же время можно использовать метод свободной памяти Unsafe.
Новый метод DirectBuffer UnpooledByteBufAllocator
код показан ниже:
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) { final ByteBuf buf; if (PlatformDependent.hasUnsafe()) { buf = noCleaner ? new InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf(this, initialCapacity, maxCapacity) : new InstrumentedUnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity); } else { buf = new InstrumentedUnpooledDirectByteBuf(this, initialCapacity, maxCapacity); } return disableLeakDetector ? buf : toLeakAwareBuffer(buf); }
Ключевым моментом является результат noCleaner. Код, влияющий на его результат, выглядит следующим образом:
// Construction method, public UnpooledByteBufAllocator(boolean preferDirect, boolean disableLeakDetector, boolean tryNoCleaner) { super(preferDirect); this.disableLeakDetector = disableLeakDetector; noCleaner = tryNoCleaner && PlatformDependent.hasUnsafe() && PlatformDependent.hasDirectBufferNoCleanerConstructor(); } //tryNoCleaner results from PlatformDependent.useDirectBufferNoCleaner() public UnpooledByteBufAllocator(boolean preferDirect, boolean disableLeakDetector) { this(preferDirect, disableLeakDetector, PlatformDependent.useDirectBufferNoCleaner()); } // Determine whether there is a DirectByteBuffer constructor, if yes, true public static boolean useDirectBufferNoCleaner() { return USE_DIRECT_BUFFER_NO_CLEANER; } // Judgment based on whether there is a DirectByteBuffer constructor, if not, USE_DIRECT_BUFFER_NO_CLEANER=false if (maxDirectMemory == 0 || !hasUnsafe() || !PlatformDependent0.hasDirectBufferNoCleanerConstructor()) { USE_DIRECT_BUFFER_NO_CLEANER = false; DIRECT_MEMORY_COUNTER = null; } else { USE_DIRECT_BUFFER_NO_CLEANER = true; if (maxDirectMemory < 0) { maxDirectMemory = maxDirectMemory0(); if (maxDirectMemory <= 0) { DIRECT_MEMORY_COUNTER = null; } else { DIRECT_MEMORY_COUNTER = new AtomicLong(); } } else { DIRECT_MEMORY_COUNTER = new AtomicLong(); } } // Get the constructor of DirectByteBuffer final ByteBuffer direct; Constructor<?> directBufferConstructor; long address = -1; try { final Object maybeDirectBufferConstructor = AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { try { final Constructor<?> constructor = direct.getClass().getDeclaredConstructor(long.class, int.class); Throwable cause = ReflectionUtil.trySetAccessible(constructor, true); if (cause != null) { return cause; } return constructor; } catch (NoSuchMethodException e) { return e; } catch (SecurityException e) { return e; } } }); ((Constructor<?>) maybeDirectBufferConstructor).newInstance(address, 1); directBufferConstructor = (Constructor<?>) maybeDirectBufferConstructor; } finally { if (address != -1) { UNSAFE.freeMemory(address); } } DIRECT_BUFFER_CONSTRUCTOR = directBufferConstructor;
Значение noCleaner равно true: создает объект InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf. Сокращение от noCleaner;
noCleaner имеет значение false: создайте объект InstrumentedUnpooledUnsafeDirectByteBuf. Аббревиатура hasCleaner;
Эти два конструктора отличаются:
Отражение noCleaner вызывает частный DirectByteBuffer (длинный адрес, кепка int)
Новая операция hasCleaner вызывает DirectByteBuffer(int cap)
Два способа освобождения памяти различны:
noCleaner использует unSafe.freeMemory(адрес);
hasCleaner использует чистый метод Cleaner DirectByteBuffer.
У чистого метода hasCleaner есть 2 стратегии:
Java9 использует метод invokeCleaner из Unsafe. Вызывается чистый метод ByteBuffer’s Cleaner.
2. Java6 — — Java9 использует чистый метод свойства Cleaner DirectByteBuffer.
Принцип чистого метода:
Метод clean внутренне вызывает метод run потока Deallocator с именем thunk. Объект потока создается одновременно с созданием DirectByteBuffer. Метод run потока будет внутренне вызывать небезопасный метод freeMemory, и в то же время вызывать метод Bits.unreserveMemory, что соответственно уменьшит количество используемой памяти (потому что каждый раз, когда вы обращаетесь за прямой памятью, вам нужны Bits, чтобы судить о том, достаточно, если мало после ФСК, ООМ, так что практика тут еще очень важна)
Примечание. Этот очиститель является фиктивной ссылкой. Когда DirectByteBuffer создаст его, он поместит себя в конструктор фантомной ссылки. Если этот DirectByteBuffer переработан (никто не ссылается на этот Cleaner), то GC назначит этот Cleaner В ожидающей переменной Reference существует выделенный поток ReferenceHandler, который выполняет метод tryHandlePending Reference в бесконечном цикле. Этот метод вызовет метод очистки ожидания для завершения операции восстановления памяти.
Это когда создается более чистый объект:
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; } //Construct cleaner here cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; }
Конструкция имеет только один целочисленный параметр
Итак, вы знаете, метод построения noCleaner не может вызывать метод clean у cleaner. Можно использовать только метод freeMemory от unSafe. И это то, что Netty делает по умолчанию.
При этом метод построения noCleaner не распространяется на содержимое памяти из Bits. При подаче заявки на память производительность будет лучше, чем у hasCleaner. Что касается дизайна Bits, я считаю его недостаточно элегантным. Когда памяти не хватает, он вызовет System.gc(), но заснет только на 100 миллисекунд. Недостаточно, чтобы вообще переработать в память вне кучи.
На самом деле роль Очистителя заключается в обновлении некоторых свойств битов для облегчения следующего применения памяти, и это не имеет никакого эффекта.
Я предполагаю, что использование Netty noCleaner связано с оптимизацией производительности. Чтобы пользователи не забывали использовать ReferenceCountUtil.release(), что приводило к утечкам памяти, Netty также использует виртуальные ссылки для отслеживания каждого ByteBuf, в основном избегая утечек памяти.
Подводя итог: независимо от того, использует ли noCleaner память или освобождает память, это лучше, чем использование hasCleaner.
Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .
Заинтересованы в масштабировании запуска вашего программного обеспечения? Ознакомьтесь с разделом Схема.