Сколько памяти занимает массив java
Перейти к содержимому

Сколько памяти занимает массив java

  • автор:

Что из себя представляет массив в языке программирования Java?

Все мы знаем, что язык программирования Java является чисто объектным языком. Все сущности, с которыми нам приходится иметь дело и осуществлять какие-либо манипуляции, являются объектами (за исключением примитивов), в том числе и массивы. То бишь, если говорить совсем уж простыми словами, то любой массив является конкретным объектом в памяти. Java является сильно типизированным языком. Хоть и градация на языки со слабой и сильной типизацией данных является весьма условной, но Java, так или иначе, больше относится к языкам с сильной типизацией. Это ведёт нас к тому, что все данные имеют свой фиксированный тип (или класс, если говорить в терминах ООП). Вот здесь и вся загвоздка! Мне всегда хотелось узнать, каким образом описываются массивы, как некоторые абстрактные сущности, на физическом уровне. Я понимаю, что невозможно найти готовый класс, в котором бы была описана структура массивов, по той простой причине, что данная сущность является одной из фундаментальных (на равне с примивными типами данных) и её реализация спрятана где-то на уровне JVM или в каком-то другом месте. Давайте рассмотрим тривиальный пример. Мы знаем, что размер любого массива является фиксированным и определяется на этапе создания самого массива. Сведения о размере массива хранятся в целочисленной переменной с одноимённым названием length. Сразу же возникает вопрос относительно этого поля. Откуда оно взялось? Где можно проследить всю эту внутреннюю логику (если можно так выразиться)? Идём далее. Создали массив в памяти, при этом сразу же указали его размер. Размер массива соответствует количеству однотипных элементов, которые могут храниться в этом массиве. И тут опять-таки вопрос. По какой логике JVM определяет количество элементов, которое нам необходимо? Точнее не совсем так. Понятное дело, что мы сами указываем размер массива, но разве количество полей для отдельно взятого типа данных не должно быть фиксированным?! Есть ли какой-нибудь код (пусть даже псевдокод), который мог бы хоть немного пролить свет на данный вопрос.

Отслеживать

13.8k 12 12 золотых знаков 43 43 серебряных знака 76 76 бронзовых знаков

Сколько памяти занимает массив java

Давайте рассмотрим на практике, создав класс с примерами объектов, которые будут использовать наш класс:

 public class InstrumentationExample < public static void printObjectSize(Object object) < System.out.println("Object type: " + object.getClass() + ", size: " + InstrumentationAgent.getObjectSize(object) + " bytes"); >public static void main(String[] arguments) < String emptyString = ""; String string = "Estimating Object Size Using Instrumentation"; String[] stringArray = < emptyString, string, "com.baeldung" >; String[] anotherStringArray = new String[100]; List stringList = new ArrayList<>(); StringBuilder stringBuilder = new StringBuilder(100); int maxIntPrimitive = Integer.MAX_VALUE; int minIntPrimitive = Integer.MIN_VALUE; Integer maxInteger = Integer.MAX_VALUE; Integer minInteger = Integer.MIN_VALUE; long zeroLong = 0L; double zeroDouble = 0.0; boolean falseBoolean = false; Object object = new Object(); class EmptyClass < >EmptyClass emptyClass = new EmptyClass(); class StringClass < public String s; >StringClass stringClass = new StringClass(); printObjectSize(emptyString); printObjectSize(string); printObjectSize(stringArray); printObjectSize(anotherStringArray); printObjectSize(stringList); printObjectSize(stringBuilder); printObjectSize(maxIntPrimitive); printObjectSize(minIntPrimitive); printObjectSize(maxInteger); printObjectSize(minInteger); printObjectSize(zeroLong); printObjectSize(zeroDouble); printObjectSize(falseBoolean); printObjectSize(Day.TUESDAY); printObjectSize(object); printObjectSize(emptyClass); printObjectSize(stringClass); > public enum Day < MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY >> 

Чтобы это сработало, нам необходимо включить параметр -javaagent с путем к нашему JAR-файлу при запуске приложения:

 VM Options: -javaagent:"path_to_agent_directory\InstrumentationAgent.jar" 

Результат работы нашего класса покажет нам приблизительные размеры объектов:

Размеры массивов в Java

Размеры объектов в Java уже обсуждались на Хабре, например, здесь или здесь. Мне бы хотелось подробнее остановиться на размерах многомерных массивов — простая вещь, которая для меня стала неожиданной.

Оптимизируя вычислительный алгоритм по памяти, я наткнулся на то, что при определённых (вполне разумных) входных параметрах создаётся массив float[500][14761][2]. Сколько он может занимать в памяти (на HotSpot 1.6.0_26 32bit, если кому интересно)? Я примерно прикинул, что = = плюс какой-то оверхед. Решив проверить, как на самом деле, я воспользовался Eclipse Memory Analyzer (невероятно волшебная вещь, рекомендую!) и обнаружил, что «Retained Heap» для этого массива составляет ! Неплохой оверхед — 350%. Посмотрим, почему так получилось.

Я поискал, что на эту тему пишут в Интернете, и наткнулся на довольно понятную статью на английском «How to calculate the memory usage of a Java array». Вкратце суть изложения такая:

  • Всякий Java-объект имеет оверхед 8 байт;
  • Java-массив имеет дополнительный оверхед 4 байта (для хранения размера);
  • Ссылка имеет размер 4 байта;
  • Размер всех объектов выровнен по 8 байт;
  • Многомерный массив — это массив ссылок на массивы.

Этого знания достаточно. Итак мы имеем в конце цепочки массивы float[2]. Их размер — это 2 float (по 4 байта) + 12 байт оверхеда — 20 байт. Выравниваем до 8 — получается 24 байта. Всего у нас таких массивов создано в куче — штук. Итого они весят байт.

Затем у нас есть массивы float[14761][] — массивы из ссылок на другие массивы. Каждый такой массив занимает ссылки (по 4 байта) + 12 байт оверхеда — байт (делится на 8 — выравнивать не надо). Всего этих массивов 500 штук, значит они вместе весят байт.

Наконец, у нас есть собственно тот массив, который мы завели — float[500][][] — массив из 500 ссылок на двумерные массивы. Он занимает = , да ещё 4 байта выравнивания — байт.

Складываем, что получилось: = — как раз то число, которое показал Memory Analyzer.

Сразу же пришло в голову очевидное решение: упорядочить измерения массива по возрастанию. Сделаем float[2][500][14761] и алгоритм от этого никак не пострадает. Какой размер получится в этом случае?

  • Массивы float[14761]: = байт, всего таких массивов, итого байт.
  • Массивы float[500][]: = , после выравнивания — , всего 2 таких массива, итого байта.
  • Массив float[2][][]: = 20, после выравнивания — 24 байта.

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

Реализация массивов — Java: Массивы

Когда мы говорим про примитивные типы данных, такие как int или double, то, на интуитивном уровне, все довольно понятно. Под каждое значение выделяется некоторое количество памяти в соответствие с типом, в которой и хранится само значение. А как должна выделиться память под хранение массива? И что такое массив в памяти? На уровне хранения понятия «массив» не существует. Массив представляется цельным куском памяти, размер которого вычисляется по формуле «количество элементов» * «количество памяти под каждый элемент». Из этого утверждения есть два интересных вывода:

  • Размер массива — это фиксированная величина. Те динамические массивы, с которыми мы имеем дело, во многих языках реализованы уже внутри языка, а не на уровне железа
  • Все элементы массива имеют один тип и занимают одно и то же количество памяти. Благодаря этому появляется возможность простым умножением (по формуле, описанной ниже) получить адрес той ячейки, в которой лежит нужный нам элемент. Именно это происходит под капотом, при обращении к элементу массива под определенным индексом

Фактически, индекс в массиве — смещение относительно начала куска памяти, содержащего данные массива. Адрес, по которому расположен элемент под конкретным индексом, рассчитывается так: начальный адрес + индекс * количество памяти, занимаемое одним элементом:

// Инициализация массива из пяти элементов типа int // int занимает 4 байта // Общее количество памяти выделенное под массив int * 5 = 4 * 5 = 20 байт int[] numbers = 19, 10, 8, 17, 9>; numbers[3]; // 17 

Адрес элемента, соответствующего индексу 3, вычисляется так: начальный адрес + 3 * 4 (размер типа данных int). Начальный адрес — это адрес ячейки памяти, начиная с которой располагается массив. Он формируется во время выделения памяти под массив. Ниже пример расчета адресов памяти под разные элементы массива numbers:

Рассмотрим еще раз определение массива:

// Первый элемент // Начальный адрес + 4 * 0 = начальный адрес numbers[0]; // 19 // Начальный адрес + 4 * 1 = начальный адрес + 4 // То есть сместились на 4 байта numbers[1]; // 10 // Начальный адрес + 4 * 2 = начальный адрес + 8 // То есть сместились на 8 байт numbers[2]; // 8 // Последний элемент // Начальный адрес + 4 * 4 = начальный адрес + 16 // То есть сместились на 16 байт // И сам элемент занимает 4 байта. В сумме как раз 20 numbers[4]; // 9 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *