Почему дамп кучи профилировщика Android Studio показывает примерно 7-кратные накладные расходы для arraylist?

#java #android #arraylist #heap-memory

#java #Android #arraylist #куча-память

Вопрос:

Мне было интересно и я тестировал выделение памяти объекта на Android / Java и обнаружил, что в куче происходит что-то странное, ну, может быть, это просто нормально.

Вот что я сделал и нашел; существует текстовый файл данных размером 850 КБ с 26220 строками, по 30 символов в строке. Я получаю все строки через inputstream и добавляю их в arraylist. Затем я сбрасываю кучу в профилировщик Android Studio, и, как вы можете видеть на скриншоте, дамп кучи выглядит как подсчет всех связанных объектов по отдельности и добавление их всех, что в 7 раз увеличивает объем памяти, чем фактические текстовые данные. Обычно общий сохраняемый размер кучи приложений раньше составлял 2,8 МБ, после создания arraylist он получает около 9 МБ, поэтому объем памяти составляет 6,2 МБ для текстовых данных объемом 0,85 МБ. Является ли такое поведение совершенно нормальным в Android / Java или что-то не так с profiler?
Заранее всем спасибо.

скриншот кучи профилировщика

Обновить

На этот раз я сделал еще один снимок экрана из гистограммы профилировщика памяти Android Studio, а не из дампа кучи. Интересно, что он показывает потребление памяти, как ожидалось, или, по крайней мере, не является ненормальным на этой гистограмме, в отличие от вводящего в заблуждение / сбивающего с толку дампа кучи. Как вы можете видеть на скриншоте, перед созданием arraylist общая потребляемая память приложением составляет 34,5 МБ, а память Java (предположительно куча) — 5,2 МБ. После создания arraylist из необработанного текстового файла объемом 850 КБ общая память становится 37 МБ, а Java 7,1 МБ, что составляет 2,5 МБ общей памяти и 1,9 МБ разницы в памяти кучи.

Похоже, что сохраненная информация о размере в дампе кучи Android Studio вводит в заблуждение и должна игнорироваться, как предложил Андреас.

скриншот 2

Ответ №1:

Вы неправильно читаете.

Почему они суммировали «Сохраненный размер», мне непонятно, потому что это совершенно бесполезное значение.

В этом случае у нас есть ArrayList<String> с 26220 String объектами:

  • Это означает, что у нас есть 1 ArrayList , поэтому только 1 из этих 525 выделений принадлежит нам. Мелкий размер — мелкий (ArrayList) = 10500 / 525 = 20 байт.

  • An ArrayList ссылается на an Object[] , поэтому только 1 из этих 661 выделений принадлежит нам. Малый размер зависит от размера массива, но допустим, это 16 байт накладных расходов 4 байта на элемент, поэтому мелкий (объект[]) = 16 4 * 26220 = 104896 байты.

  • Элементы массива ссылаются на 26220 String объектов, поэтому 26220 из этих 39896 выделений принадлежат нам. Мелкий размер составляет 638336/39896 = 16 байт на String , поэтому мелкий (строка) = 26220 * 16 = 419520 байт.

  • String ссылается на byte[] то, что 26220 из этих 40407 выделений принадлежат нам. Небольшой размер зависит от размера массива, но позволяет использовать средний размер 30 символов в строке, то есть 30 байт на массив, что означает, что это 16 байт накладных расходов 30 байт = 46 байт, округленный до кратного 8, означает 48 байт, поэтому 48 * 26220 = 1258560 байт. Не знаю, почему он показывает только 965588 байт, но давайте просто пойдем с этим, так что shallow(byte[]) = 965588 байт.

Сохраненный размер — это малый размер сохраненный размер всего, на что ссылается ссылка:

 retained(byte[]) = shallow(byte[]) // byte[] doesn't reference anything
                 = 965588 bytes

retained(String) = shallow(String)   retained(byte[])
                 = shallow(String)   shallow(byte[])
                 = 419520   965588 = 1385108 bytes

retained(Object[]) = shallow(Object[])   retained(String)
                   = shallow(Object[])   shallow(String)   shallow(byte[])
                   = 104896   419520   965588 = 1490004 bytes

retained(ArrayList) = shallow(ArrayList)   retained(Object[])
                    = shallow(ArrayList)   shallow(Object[])   shallow(String)   shallow(byte[])
                    = 20   104896   419520   965588 = 1490024 bytes
  

Теперь суммарный «сохраненный размер» смехотворен, потому что, если мы суммируем эти 4 значения, мы в конечном итоге подсчитываем 965588 байт из byte[] 4 раз, получая 965588 1385108 1490004 1490024 = 5330724 байта, и это просто бесполезно. Пожалуйста, игнорируйте суммированное значение «Сохраненный размер», поскольку оно совершенно бессмысленно.

ArrayList Используется 1490024 байта памяти, а не 6,2 МБ.

Комментарии:

1. Спасибо за подробное объяснение, Андреас. Я также заметил, что это немного больше похоже на гистограмму профилировщика, показывающую ожидаемое потребление памяти. Верно, похоже, что сохраненная информация о размере в дампе кучи Android Studio вводит в заблуждение / сбивает с толку и должна игнорироваться.