Для памяти вне кучи использование 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 .

Заинтересованы в масштабировании запуска вашего программного обеспечения? Ознакомьтесь с разделом Схема.