Почему куча памяти растет вместе с metaspace в Java 8?

Я делаю небольшой тест, чтобы понять, как работает metaspace memory (Java 8 onwards). Когда я создаю 100 000 классов динамически, память метапространства растет (очевидно), но куча памяти тоже растет. Может кто-нибудь объяснить мне, почему это происходит?

PS: Я запускаю тест со 128 МБ кучи и 128 Мб метапроцесса.

@Test
public void metaspaceTest() throws CannotCompileException, InterruptedException {

ClassPool cp = ClassPool.getDefault();
System.out.println("started");

for (int i = 0; i <= 100000; i++) {
    Class c = cp.makeClass("br.com.test.GeneratedClass" + i).toClass();
    Thread.sleep(1);
    if (i % 10000 == 0) {
    System.out.println(i);
    }
}

System.out.println("finished");
}

См. Изображения ниже:

enter image description here

enter image description here

Ответ 1

Я изучил ваш код, особенно ClassPool#makeClass. Есть несколько моментов, которые я заметил, которые заставляют пространство кучи увеличиваться по мере увеличения metaspace.

  1. Это cache классов, созданных методом makeClass в хэш - таблице

защищенные классы Hashtable;

Итак, для 10-го миллиона классов, у него есть запись для каждого из них. Следовательно, Heap Space тоже увеличивается, и это не GC, поскольку ссылка hashtable по-прежнему используется вашим циклом for и постоянно обновляется, следовательно, не подходит для gc.

  1. CtNewClass создает новый экземпляр класса и имеет определение конструктора, как CtNewClass ниже:

      CtNewClass(String name, ClassPool cp, boolean isInterface, CtClass superclass) {
        super(name, cp);
        this.wasChanged = true;
        String superName;
        if (!isInterface && superclass != null) {
            superName = superclass.getName();
        } else {
            superName = null;
        }
    
        this.classfile = new ClassFile(isInterface, name, superName);
        if (isInterface && superclass != null) {
            this.classfile.setInterfaces(new String[]{superclass.getName()});
        }
    
        this.setModifiers(Modifier.setPublic(this.getModifiers()));
        this.hasConstructor = isInterface;
    }
    

В коде выше строки this.classfile = new ClassFile(isInterface, name, superName); на самом деле создает новый экземпляр ConstPool для каждого класса, то есть новые экземпляры HashMap для каждого экземпляра и эту резервную память в кучном пространстве.

Классы HashMap; //из класса ConstPool

Строки HashMap; // из класса ConstPool

Кроме того, это создает два новых ArrayLists. Наблюдайте this.fields = new ArrayList(); и this.methods = new ArrayList(); в конструкторе. Кроме того, новый связанный список this.attributes = new LinkedList(); ,

Следовательно, вывод заключается в том, что ClassPool имеет управление кешем, которое занимает большое количество кучи. Тогда каждый класс имеет свой собственный набор коллекций для управления свойствами, константами и т.д.

Надеюсь, поможет!

Ответ 2

В вашем пуле классов используется куча памяти. Он получил hashtables и списки и другие вещи. Он также использует код отражения Java, который использует память кучи. Куча памяти, которая не является gc'ed, вероятно, это все эти структуры данных в пуле классов, пара хэш-таблицах, связанных списках, списках массивов, пулах и т.д. Например, каждый созданный вами класс хранится в хэш-таблице класса бассейн. Это хэш-таблица из 100 000 элементов.

Во-вторых, если в создаваемых вами классах есть статические инициализаторы, которые будут использовать кучную память. Статические инициализаторы включают инициализацию статического поля и статический блок-код.