Сохраненный размер кучи строки в Java - PullRequest
7 голосов
/ 08 декабря 2011

Это вопрос, который мы с трудом понимаем. Это сложно описать с помощью текста, но я надеюсь, что суть будет понята.

Я понимаю, что фактическое содержимое строки заключено во внутренний массив символов. В обычных случаях размер оставшейся кучи строки будет включать 40 байтов плюс размер массива символов. Это объясняется здесь . При вызове подстроки массив символов сохраняет ссылку на исходную строку, и, следовательно, сохраненный размер массива символов может быть намного больше, чем сама строка.

Однако при профилировании использования памяти с использованием Yourkit или MAT происходит нечто странное. Строка, которая ссылается на оставшийся размер массива char, не включает оставшийся размер массива символов.

Примером может быть следующий (полупсевдокод):

String date = "2011-11-33"; (24 bytes)
date.value = char{1172}; (2360 bytes)

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

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

У нас возникает такая ситуация:

Array's retained size = 300 bytes
array[0] = String 40 bytes;
array[1] = String 40 bytes;
array[1].value = char[] (220 bytes)

Поэтому вам нужно изучить каждую запись массива, чтобы попытаться определить, откуда берется оставшийся размер.

Опять же, это можно объяснить тем, что массив содержит все строки, которые содержат ссылки на один и тот же символьный массив, и, следовательно, в целом сохраненный размер массива является правильным.

Теперь мы подошли к проблеме.

Я храню в отдельном объекте ссылку на массив, который я обсуждал выше, а также другой массив с теми же строками. В обоих массивах строки ссылаются на один и тот же массив символов. Это ожидаемо - ведь речь идет об одной и той же строке. Однако оставшийся размер этого символьного массива учитывается для обоих массивов в этом новом объекте. Другими словами, оставшийся размер кажется двойным. Если я удаляю первый массив, то второй массив все равно будет содержать ссылку на массив символов и наоборот. Это вызывает путаницу, так как кажется, что java содержит две отдельные ссылки на один и тот же массив символов. Как это может быть? Это проблема с памятью Java или это просто способ, которым профилировщики отображают информацию?

Эта проблема вызвала у нас много головной боли при попытке отследить огромное использование памяти в нашем приложении.

Опять же - я надеюсь, что кто-то там сможет понять вопрос и объяснить его.

Спасибо за вашу помощь

Ответы [ 4 ]

4 голосов
/ 08 декабря 2011

Я храню в отдельном объекте ссылку на массив, который я обсуждал выше, а также другой массив с теми же строками.В обоих массивах строки ссылаются на один и тот же массив символов.Это ожидаемо - ведь речь идет об одной и той же строке.Однако оставшийся размер этого символьного массива учитывается для обоих массивов в этом новом объекте.Другими словами, оставшийся размер кажется двойным.

Здесь у вас есть транзитивная ссылка в дереве доминирующих :

enter image description here

Массив символов не должен отображаться в сохраненном размере любого массива.Если профилировщик отображает это таким образом, это вводит в заблуждение.

Вот как JProfiler показывает эту ситуацию в представлении самых больших объектов:

enter image description here

Экземпляр строки, содержащийся в обоих массивах, отображается вне экземпляров массива с меткой [переходная ссылка].Если вы хотите изучить фактические пути, вы можете добавить держатель массива и строку на график и найти все пути между ними:

enter image description here

Отказ от ответственности: Моя компания разрабатывает JProfiler.

3 голосов
/ 08 декабря 2011

Я бы сказал, что именно так профилировщик отображает информацию.Он понятия не имеет, что эти два массива следует рассматривать для «дедупликации».Как насчет того, чтобы обернуть два массива в какой-нибудь объект-пустышку и запустить против этого свой профилировщик?Затем он должен быть в состоянии позаботиться о «двойном учете».

0 голосов
/ 08 декабря 2011

Если вы запускаете с -XX:-UseTLAB

public static void main(String... args) throws Exception {
    StringBuilder text = new StringBuilder();
    text.append(new char[1024]);
    long free1 = free();
    String str = text.toString();
    long free2 = free();
    String [] array = { str.substring(0, 100), str.substring(101, 200) };
    long free3 = free();
    if (free3 == free2)
        System.err.println("You must use -XX:-UseTLAB");
    System.out.println("To create String with 1024 chars "+(free1-free2)+" bytes\nand to create an array with two sub-string was "+(free2-free3));
}

private static long free() {
    return Runtime.getRuntime().freeMemory();
}

печать

To create String with 1024 chars 2096 bytes
and to create an array with two sub-string was 88

Вы можете видеть, как он потребляет больше памяти, чем вы могли ожидать, если бы они совместно использовали одно и то же внутреннее хранилище.

Если вы посмотрите на код класса String .

public String substring(int start, int end) {
    // checks.
    return ((beginIndex == 0) && (endIndex == count)) ? this :
        new String(offset + beginIndex, endIndex - beginIndex, value);
}

String(int offset, int count, char value[]) {
    this.value = value;
    this.offset = offset;
    this.count = count;
}

Вы можете видеть, что подстрока для String не принимает копию базового массива значений.


Еще одна вещь, которую следует учитывать, это -XX:+UseCompressedStrings, которая по умолчанию включена в более новых версиях JVM. Это побуждает JVM по возможности использовать byte [] вместо char [].

Размер заголовков для объекта String и массива варьируется для 32-разрядных JVM, 64-разрядных JVM с 32-разрядными ссылками и 64-разрядных JVM с 64-разрядными ссылками.

0 голосов
/ 08 декабря 2011

Если строки не интернированы, они могут быть equal(), но не ==. При создании объекта String из массива char конструктор создает копию массива char. (Это единственный способ оградить неизменяемую строку от более поздних изменений значений массива символов.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...