Не взорвет ли система размещение миллионов данных в памяти?

Java

В компании есть необходимость проверить пакет данных.Предыдущий метод заключался в том, чтобы напрямую использовать различные сложные операции SQL, чтобы выйти.Мало того, что время медленное, но и его нелегко поддерживать на более позднем этапе. Что означает SQL, поэтому однажды я подумал о том, чтобы спросить, почему люди, которые делали это раньше, не проверили данные, а затем не отфильтровали их в памяти? Вы прямо сказали, что не боитесь памяти лопнуть? Этот сервер учета представляет собой отдельный сервер с конфигурацией из четырех ядер и восьми гигабайт, а размер кучи конфигурации составляет 4G. В духе скептицизма, пытаясь выяснить, сколько памяти на самом деле займет несколько миллионов фрагментов данных в памяти?

запоминающее устройство компьютера

Обычно используемые компьютерные запоминающие устройстваbit,Byte,KB,MB,GB,TBПозже есть и другие, но нам в принципе не нужно их использовать.Мы часто используемbitназываемые битами или битами, будутByteУпоминается какBили байты, будетKBУпоминается какK,будетMBНазовите это М или мега, будетGBУпоминается какG. Так каковы их единицы преобразования?

Конверсионные отношения

Прежде всего, мы должны знать, что все данные в компьютере создаются0 1составить, а затем сохранить0 1Чем хранятся эти бинарные данные? отbitхранится, аbitХранит одну двоичную цифру. такbitэто компьютернаименьшая единица.

Большинство компьютеров в настоящее время используют 8-битные блоки, которые мы выше назвали байтами.Byte, как базовая единица мощности компьютера. Поэтому мы обычно относимся к символу или числу в зависимости от того, сколько байтов он занимает.

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

11B(Byte 字节) = 8bit(位)
21KB = 1024B
31MB = 1024KB
41GB = 1024MB
51TB = 1024GB

Сколько памяти занимает объект в Java

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

Основные типы Java

Мы знаем, что типы Java делятся на базовые и ссылочные типы.int、short、long、byte、float、double、boolean、char

тип данных Занятая память (единицаByte)
boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8

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

Ссылочный тип Java

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

Расположение объектов в памяти

В виртуальной машине HotSpot расположение объектов, хранящихся в памяти, можно разделить на три области: заголовок объекта (Header), данные экземпляра (Instance Data), заполнение выравнивания (Padding).

заголовок объекта

В заголовке объекта хранятся две части данных.

  • Данные времени выполнения: хранит данные времени выполнения самого объекта, такие как хэш-код, время создания GC, флаги состояния блокировки, блокировки, удерживаемые потоками, предвзятые идентификаторы потоков и т. д. Эта часть данных является 32-битной и 64-битной в 32-битной и 64-битной виртуальных машинах соответственно.
  • Указатель типа: указатель объекта на его метаданные класса, и виртуальная машина использует этот указатель, чтобы определить, экземпляром какого класса является объект. Если объект представляет собой массив Java, то в заголовке объекта также должен быть фрагмент данных (занимающий 4 байта) для записи длины массива. Итак, это указатель, и JVM по умолчанию сжимает указатель и сохраняет его в 4 байта.

Возьмем для примера 64-битную виртуальную машину, тогда память, занимаемая заголовком объекта, равна 8 (данные выполнения) + 4 (указатель типа) = 12 байт. Если это массив, то это 16 байт.

данные экземпляра

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

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

Выровнять отступы

Заполнение выравнивания не обязательно присутствует и не имеет особого значения. Он просто действует как заполнитель. Поскольку система автоматического управления памятью HotSpot VM требует, чтобы начальный адрес объекта был целым числом, кратным 8 байтам, иными словами, размер объекта должен быть целым числом, кратным 8 байтам. И если заголовок объекта плюс данные экземпляра не являются целым числом, кратным 8, он будет заполнен путем его заполнения.

Практическая тренировка

Мы многое проанализировали выше, так что это то же самое, что и размер выделения нового объекта в памяти, когда мы анализировали? Мы можем создать новый объект.

1class Animal{
2
3    private int age;
4
5}

Так как же узнать, сколько памяти этот объект занимает в памяти? JDK предоставляет инструментjol-coreЭто может дать нам анализ размера памяти, занимаемой объектом в памяти. Пакет можно импортировать прямо в проект.

1--Gradle
2compile 'org.openjdk.jol:jol-core:0.9'
3
4--Maven
5<dependency>
6    <groupId>org.openjdk.jol</groupId>
7    <artifactId>jol-core</artifactId>
8    <version>0.9</version>
9</dependency>

Затем мы вызываем основную функцию следующим образом

1public class AboutObjectMemory {
2
3    public static void main(String[] args) {
4        System.out.print(ClassLayout.parseClass(Animal.class).toPrintable());
5    }
6}

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

1aboutjava.other.Animal object internals:
2 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
3      0    12        (object header)                           N/A
4     12     4    int Animal.age                                N/A
5Instance size: 16 bytes
6Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

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

Строка строка — это особое существование в Java, такое как строка"abcdefg"Сколько байтов занимает такая строка? Я думаю, некоторые люди ответят, что это 7 байт или 14 байт.Оба этих ответа неточны.Давайте посмотрим, сколько памяти занимает класс String в памяти.
Давайте проанализируем это сами. В классе String есть два атрибута: заголовок объекта имеет фиксированный размер 12 байт, int — 4 байта, а массив char[] фактически хранится здесь как ссылочный объект, поэтому хранится адрес, поэтому он занимает 4 слова. раздел, поэтому размер对象头12Byte+实例数据8Byte+填充数据4Byte=24ByteЗаголовок объекта и данные экземпляра здесь не кратны 8, поэтому для заполнения требуются данные заполнения.

1    private final char value[];
2
3    private int hash; // Default to 0

Итак, верен наш анализ или нет, мы все равно используем вышеперечисленные инструменты для его анализа. Можно видеть, что наши расчетные результаты согласуются с результатами нашего анализа.

1java.lang.String object internals:
2 OFFSET  SIZE     TYPE DESCRIPTION                               VALUE
3      0    12          (object header)                           N/A
4     12     4   char[] String.value                              N/A
5     16     4      int String.hash                               N/A
6     20     4          (loss due to the next object alignment)
7Instance size: 24 bytes

затем одинпустой строкиСколько памяти он занимает? Мы только что получили, что объект String занимает 24 байта.На самом деле, массив char[] по-прежнему будет занимать память.Как мы уже говорили, когда говорили о заголовке объекта выше, объект массива также является экземпляром объекта, и его объект заголовок больше, чем общий заголовок объекта.У объекта есть дополнительные 4 байта для описания длины массива, поэтому длина заголовка объекта массива char[] составляет 16 байтов.Поскольку в настоящее время это пустая строка , длина данных экземпляра равна 0. Таким образом, пустой массив char[] занимает в памяти размер对象头16Byte+实例数据0Byte=16Byte. Пустая строка занимает память какString对象+char[]数组对象=40Byte

Тогда пример, который мы привели вышеabcdefgСколько памяти он занимает? Память, занимаемая объектом String, не изменится, а изменится содержимое массива char[], здесь нам нужно знать, что строка хранится в массиве char[], а char занимает 2 байта, поэтомуabcdefgРазмер массива char[]对象头16Byte+实例数据14Byte+对齐填充2Byte=32Byte. ТакabcdefgРазмер памятиString对象+char[]数组对象=56Byte

Используйте список для хранения объектов

Итак, если мы поместим в память 20 миллионов таких объектов, сколько памяти они займут? Основываясь на вышеизложенных знаниях, мы можем приблизительно оценить. Мы определяем массив List для хранения этого объекта и предотвращения его повторного использования.

1List<Animal> animals = new ArrayList<>(20000000);
2for (int i = 0; i < 20000000; i++) {
3    Animal animal = new Animal();
4    animals.add(animal);
5}

Обратите внимание, что здесь я напрямую инициализировал размер набора равным 20 миллионам, поэтому память, занимаемая программой при обычном запуске, составляет 100+ МБ, а при обычном запуске программы занимает только 30+ МБ, поэтому дополнительные 60+ МБ — это именно то, что нужно. размер массива, который мы инициализируем. Причина, по которой нам необходимо инициализировать размер, состоит в том, чтобы исключить влияние коллекции на наши наблюдения при ее расширении.

Здесь я публикую сравнительную диаграмму неинициализированного размера коллекции и использования памяти инициализированного размера.Вы можете видеть, что есть различия в памяти.Массив ArrayList используется для хранения данных.transient Object[] elementData;Массив объектов, поэтому он хранит указатели на объекты.Один указатель занимает 4 байта, поэтому указателей 20 миллионов, то есть 76M. Мы видим, что график разностей такой, как мы и ожидали.

Выше мы рассчиталиAnimalОбъект занимает 16 байт, поэтому 20 миллионов занимают около 305 МБ, а общий размер коллекции составляет почти 380 МБ. Далее мы запустим программу, чтобы проверить, верен ли наш результат. Далее я используюjconsoleИнструмент для просмотра использования памяти.

Мы видим результаты, соответствующие нашему бюджету.

Тогда если есть большое количество объектов, которые нужно искать в базе данных и в дальнейшем помещать в память, то если использовать объекты для подключения, то надо стараться уменьшить поля в объекте, потому что даже если вы не не присваивать значения, это на самом деле занимает память Да, давайте возьмем другой пример, чтобы увидеть, не слишком ли велико использование памяти для каждого значения атрибута. мы будемAnimalОбъект трансформируется следующим образом

1class Animal{
2
3    private int age;
4    private int age1;
5    private int age2;
6    private int age3;
7    private int age4;
8
9}

В этот момент мы можем рассчитатьAnimalРазмер памяти, занимаемой объектом, равен (Заголовок объекта 12 байт + данные экземпляра 20 байт = 32 байта) В настоящее время, поскольку 32 кратно 8, заполнение не требуется. Тогда, если в это время все еще есть 20 миллионов фрагментов данных, память, занимаемая этим объектом, должна составлять 610 МБ, а данные указателя в коллекции - 76 МБ, поэтому общее количество занято почти 686 МБ, то является ли ожидаемый результат так же, как и у нас, мы перезапускаем программу наблюдения, вы можете увидеть следующий рисунок. Видно, что анализируемые нами данные аналогичны.

Храните объекты в Картах

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

Это структура массива плюс связанный список (или красно-черное дерево), а данные, хранящиеся в массиве, представляют собой объект Node.

1static class Node<K,V> implements Map.Entry<K,V> {
2        final int hash;
3        final K key;
4        V value;
5        Node<K,V> next;
6}

Мы определяем следующий объект Map в качестве примера

1Map<Animal,Animal> map

На этом этапе мы можем сами рассчитать объем памяти, необходимый для объекта Node.对象头12Byte+实例数据16Byte+对其填充4Byte=32Byte, разумеется, значения key и value здесь нужно рассчитывать отдельно, т.к. Node-объекты в это время хранят только свои ссылки. Размер памяти, занимаемый объектом Animal, составляет 16 байт, как упоминалось выше, поэтому размер, занимаемый объектом Node здесь, составляет 32 байта + 16 байт + 16 байт = 64 байта.

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

1Map<Animal,Animal> map = new HashMap<>(20000000);
2for (int i = 0; i < 20000000; i++) {
3    map.put(new Animal(),new Animal());
4}

В приведенном выше примере в объекте Map хранится 20 миллионов фрагментов данных, и вычисляется, сколько памяти он занимает в памяти.

  • Размер памяти, занимаемой массивом: Давайте сначала подсчитаем, сколько занимает массив. Вот небольшая точка знаний. Размер инициализации в HashMap основан на кратном 2. Например, если вы определяете размер как 60, то система выдаст вам размер инициализации 64. Таким образом, мы определяем его как 20 миллионов, и система фактически инициализирует его для нас как 33554432, поэтому в настоящее время только массив в HashMap занимает почти 132 МБ.
  • Объем памяти, занимаемой данными: выше мы рассчитали, что узел Node занимает 64 байта, тогда 20 миллионов фрагментов данных занимают 1280 МБ.

Сложив два размера занимаемой памяти, мы можем узнать приблизительный размер 1,4 ГБ памяти, занимаемой в системе. Так правда ли то, что мы себе представляем? Когда мы запускаем программу, мы видим размер памяти, как показано на рисунке. Видно, что результат именно такой, как мы и ожидали.

Суммировать

Возвращаясь к упомянутым выше требованиям, будут ли миллионы данных помещены в память для разрыва памяти? В настоящее время вы можете получить его путем собственных расчетов. В конце концов, наша потребность была рассчитана мной, и на самом деле она занимала сотни мегабайт памяти, а для кучи памяти 4 Гб она далека от разрыва. Так что иногда нам приходится скептически относиться ко всему. Вы можете загрузить код с GitHub и запустить его локально для мониторинга, а также можете определить несколько объектов самостоятельно, а затем вычислить, соответствует ли он размеру памяти, показанному на рисунке. Это сделает память более глубокой. Слово каждомуТак было всегда, верно?. На самом деле в написанной мною статье тоже есть небольшая дырка, можете попробовать найти, это часть инициализации коллекции.

Адрес исходного кода проекта

Если вам интересно, можете обратить внимание на мой новый паблик аккаунт и поискать [Сумка с сокровищами программиста]. Или просто отсканируйте код ниже.

Ссылаться на