Использование памяти Java простой структуры данных - PullRequest
3 голосов
/ 14 октября 2011

Я хочу иметь достаточно точное измерение моего кэша, реализованного в Java.Скажите, пожалуйста, возможен ли такой подход?

У меня есть хэш-карта, отображающая строку в массив строк.Есть ли какой-нибудь способ получить хорошее приближение этой структуры данных?

  1. Как получить размер строки?Вызовите String.toByte () и добавьте некоторый плюс для накладных расходов на содержание объекта?

  2. Является ли строковый массив суммой всех строк?Или есть какие-то издержки?

  3. Имеет ли хэш-карта также некоторую перечитанность, может быть, обернуть объекты в некоторый объект ввода?

  4. Для всех неиспользованныхместо на карте, хэш-карта все еще выделяет некоторое пространство, могу ли я суммировать 2 * null pointer для всех неподтвержденных пробелов на карте?

Я доволен частичными ответами, а также задает мне вопросправильное направление.

Ответы [ 5 ]

3 голосов
/ 14 октября 2011

Вы пробовали Instrumentation.getObjectSize()?Это может указывать, что вы хотите, но JavaDoc утверждает, что это только оценка.

3 голосов
/ 14 октября 2011

Я думаю, что хорошим практическим подходом является использование профилировщика памяти, такого как YourKit .

2 голосов
/ 14 октября 2011

Фактические накладные расходы памяти, подразумеваемые экземпляром объекта, зависят от некоторых внутренних деталей реализации JVM, и может быть трудно определить , поскольку оно может изменяться в течение всего времени жизни объекта (в сборщике мусора,объект может «перемещаться» между поколениями, которые используют различные структуры управления памятью).

Очень грубое приближение состоит в том, что каждый экземпляр любого объекта включает в себя два «слова» (два 32-разрядных значения на 32-разрядном компьютере).два 64-битных значения на 64-битных машинах);одно из слов более или менее является указателем на экземпляр Class для этого объекта, другое содержит некоторое состояние объекта, например монитор для этого объекта (то, которое вы блокируете с помощью synchronized).Тогда есть поля объекта.Для массива длина массива должна быть записана где-то в объекте, а также значения.

На этом этапе посмотрите на исходный код для классов Java (ищите файл с именем src.zipв дистрибутиве JDK).В файле String.java мы видим, что внутри экземпляра String есть четыре поля: ссылка на массив значений char и три int (одно - индекс первого строкового символа вмассив, вторая - длина строки, а третья кэширует строковый хеш-код).Таким образом, для 32-разрядной машины можно оценить, что минимальное использование памяти для String экземпляра n символов равно сумме:

  • двух 32-разрядныхслова для String заголовка объекта экземпляра
  • четыре 32-разрядных слова для String поля экземпляра
  • три 32-разрядных слова для заголовка экземпляра массива и длины
  • n 16-битные слова для самих символов (char - 16-битный)

Это только минимум, потому что экземпляр String ссылается только на chunk внутреннего массива символов, поэтому размер памяти массива может быть больше.С другой стороны, массив символов может совместно использоваться несколькими экземплярами String.Эта структура позволяет String.substring() быть очень быстрым: новый экземпляр String внутренне использует тот же массив, поэтому копирование данных не требуется;но это также означает, что если у вас есть большая строка, вы берете ее небольшую подстроку и сохраняете эту небольшую подстроку, вы на самом деле также сохраняете большой массив в ОЗУ (для экземпляра String *)1043 *, вы можете сделать new String(str), чтобы получить новый экземпляр, который будет внутренне использовать вновь выделенный и урезанный экземпляр массива).С другой стороны, если у вас есть две строки, одна из которых является подстрокой другой, и вы храните обе строки в своем кэше, то вы платите только один раз за общий внутренний массив.

Следовательно, даже без учета всехСкрытые затраты, подразумеваемые GC, довольно трудно понять, что означает «размер памяти для строки»: если два экземпляра String совместно используют один и тот же внутренний массив, как вы подсчитываете «размер» каждой строки?

Просмотр источника для HashMap покажет вам, что есть внутренние экземпляры, которые также выделены;существует массив HashMap.Entry экземпляров и один HashMap.Entry экземпляров для каждого сохраненного значения.Размер массива динамически регулируется в зависимости от количества записей и настроенного коэффициента загрузки.

Поскольку учет объема памяти труден, совершенно другое решение состоит в том, чтобы позволить самому ГХ решать, когда старые записи кэша должны бытьудален.При этом внутренне используются «мягкие ссылки»: они являются своего рода указателями, которые GC может установить на null, когда память становится тесной (разрыв ссылок может позволить GC освободить больше объектов).Это делает грубый кэш-память с поддержкой памяти, которая автоматически удаляется в зависимости от доступной памяти.Полезной библиотекой для этого является Google Guava и его MapMaker класс.

1 голос
/ 14 октября 2011

1) Предположим, что, хотя это не гарантировано (разные JVM могут действовать по-разному)

2) Сумма строк плюс накладные расходы на содержание объекта (массива)

3)Конечно, много.Объекты упакованы в записи, эти записи затем сохраняются во внутреннем HashSet и т. Д. Ну, по крайней мере, в Oracle JVM.

4) На карте нет «неиспользуемого» пространства ... Что делатьВы имеете в виду?

Итак, к сожалению, нет НИКАКОГО способа получить точный ответ на любой из этих вопросов.Это зависит от виртуальной машины, GC, операционной системы и т. Д. Профилировщик может дать вам некоторую полезную информацию, касающуюся одной конфигурации, но это максимум, на что вы можете надеяться.

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

0 голосов
/ 14 октября 2011

Простой способ определить количество используемой памяти - использовать следующее: jmap -histo:live <pid> (идентификатор процесса вашего Java-процесса)

Это даст вам гистограмму кучи. Для каждого Java-класса печатаются количество объектов, объем памяти в байтах и ​​полные имена классов.
Вы также можете сделать: jmap -dump:live pid
Создает дамп кучи Java в двоичном формате hprof.
Я бы посмотрел больше на jmap . Это очень полезно, когда ваше узкое место - это память для Java.
Например, вы можете создать скрипт, который выполняет jmap -histo каждые 30 секунд. Затем вы можете построить график вывода и увидеть развитие памяти для каждого объекта, созданного в ваших классах Java.

Вот один пример jmap -histo:

$ jmap -histo `pgrep java` |more
num   #instances    #bytes  class name
--------------------------------------
  1:    224437    27673848  [C
  2:     38611    23115312  [B
  3:     47801    12187536  [I
  4:    208624     8344960  java.lang.String
  5:     45332     6192904  <constMethodKlass>
  6:     45332     5450864  <methodKlass>
  7:      3889     4615536  <constantPoolKlass>
  8:     45671     4193136  [Ljava.lang.Object;
  9:     66203     3222312  <symbolKlass>
 10:      3889     3192264  <instanceKlassKlass>
 11:      3455     2999296  <constantPoolCacheKlass>
 12:     19754     1106224  java.nio.HeapCharBuffer

Дополнительные примеры здесь


Кроме того, профиль вашего процесса будет хорошим выбором.
Я бы рекомендовал использовать visualvm (бесплатно) или jprofiler7 (не бесплатно, но потрясающе!)

...