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

Я делаю небольшой тест, чтобы понять, как работает метапространственная память (Java 8 и выше). Когда я создаю 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");
}

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

2 ответа

Решение

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

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

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

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

  1. 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 имеет управление кешем, которое занимает много места в куче. Затем у каждого класса есть свой набор коллекций для управления свойствами, константами и т. Д.

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

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

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

Другие вопросы по тегам