Оригинал | Пожалуйста, перестаньте говорить, что объекты Java размещаются в куче памяти!

Оригинал | Пожалуйста, перестаньте говорить, что объекты Java размещаются в куче памяти!

△ Холлис, человек с уникальным стремлением к программированию △
Оригинал | Пожалуйста, перестаньте говорить, что объекты Java размещаются в куче памяти!
Это 256-й оригинальный
автор
публикации Холлиса l Исходный код Холлиса l Hollis (ID: hollischuang)
Java как объектно-ориентированный кроссплатформенный язык, его объекты, память и т. Д. Это сложный момент для понимания, поэтому даже новичок в Java должен иметь некоторое представление о JVM более или менее. Можно сказать, что соответствующие знания о JVM - это, по сути, точка знаний, которую должен освоить каждый разработчик Java, а также точка знаний, которую необходимо проверить во время собеседования.

В структуре памяти JVM двумя общими областями являются память кучи и память стека (если не указано иное, стек, упомянутый в этой статье, относится к стеку виртуальных машин). Что касается разницы между кучей и стеком, многие разработчики также Как и Шу Цзячжэнь, в Интернете есть много книг или статей, которые, вероятно, представлены следующим образом:


1、堆是线程共享的内存区域,栈是线程独享的内存区域。
2、堆中主要存放对象实例,栈中主要存放各种基本数据类型、对象的引用。

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

В моей предыдущей статье «Кучная память Java используется потоками! Интервьюер: Вы уверены? В разделе "я представил основные сведения о совместном использовании потоков, когда память кучи не заполнена, и в этой статье будет обсуждаться вторая тема.

Распределение объектной памяти

В «Спецификации виртуальной машины Java» есть такое описание кучи:

在Java虚拟机中,堆是可供各个线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域。

В "Куча Java" память распределяется между потоками! Интервьюер: Вы уверены? «В статье мы также представили, что когда объект Java выделяется в куче, он в основном выделяется в области Eden. Если TLAB запущен, он будет размещен в TLAB первым. В некоторых случаях он может быть выделен напрямую в старом поколении. , Правила распределения не фиксированы на 100%, это зависит от того, какой сборщик мусора используется в данный момент, и от настроек параметров, связанных с памятью на виртуальной машине.

Но в целом соблюдаются следующие принципы:

  • Объекты размещаются первыми в области Эдема.
  • Приоритет распределения в Eden. Если в Eden недостаточно места, запускается сборщик мусора Monitor.
  • Большие предметы напрямую входят в старость
  • Для объектов Java, которым требуется большой объем непрерывного пространства памяти, когда объем памяти, необходимый объекту, превышает значение параметра -XX: PretenureSizeThreshold, объект будет напрямую выделять память в старом поколении.
    Однако, несмотря на то, что в спецификации виртуальной машины есть такие требования, каждый производитель виртуальной машины может сделать некоторые оптимизации для выделения памяти объекта при реализации виртуальной машины. Наиболее типичным из них является зрелость технологии JIT в виртуальной машине HotSpot, которая делает выделение памяти в куче для объектов неопределенным.
    Фактически, в «Углубленном понимании виртуальной машины Java» автор также выдвинул аналогичную точку зрения, поскольку зрелость технологии JIT делает «объекты выделяют память в куче» не столь уж абсолютным. Однако в книге не раскрывается, что такое JIT, и не раскрывается, что делает JIT-оптимизация. Тогда давайте подробнее рассмотрим:

    JIT технология

Все мы знаем, что исходный код программы Java может быть скомпилирован и преобразован в байт-код Java с помощью javac. JVM переводит байт-код в соответствующие машинные инструкции, считывает их одну за другой и интерпретирует перевод один за другим. Это функция традиционного интерпретатора JVM. Очевидно, что после интерпретации и выполнения компилятора Java скорость его выполнения неизбежно будет намного ниже, чем при прямом выполнении исполняемого двоичного байт-кода. Для решения этой проблемы эффективности вводится технология JIT (Just In Time).
При использовании технологии JIT программы Java по-прежнему интерпретируются и выполняются через интерпретатор.Когда JVM обнаруживает, что метод или блок кода выполняется особенно часто, она будет рассматривать это как «код горячей точки» (Hot Spot Code). Затем JIT переведет часть «горячего кода» в машинный код, относящийся к локальной машине, и оптимизирует его, а затем кэширует переведенный машинный код для следующего использования.
Обнаружение горячей точки

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

基于计数器的热点探测(Counter Based Hot Spot Detection)。采用这种方法的虚拟机会为每个方法,甚至是代码块建立计数器,统计方法的执行次数,某个方法超过阀值就认为是热点方法,触发JIT编译。

Оптимизация компиляции

После того, как JIT обнаружит горячие точки и идентифицировал горячий код, он не только кэширует свой байт-код, но и оптимизирует код. Среди этих оптимизаций наиболее важными являются: анализ выхода, устранение блокировок, расширение блокировок, встраивание методов, устранение нулевой проверки, исключение обнаружения типов, устранение общих подвыражений и т. Д.
Анализ побега в этих оптимизациях связан с содержанием этой статьи.

Анализ побега

Escape Analysis - самая продвинутая технология оптимизации в текущей виртуальной машине Java. Это универсальный алгоритм глобального анализа потока данных, который может эффективно снизить нагрузку на синхронизацию и давление на выделение кучи памяти в программах Java. С помощью анализа выхода компилятор Hotspot может проанализировать диапазон использования новой ссылки на объект, чтобы решить, следует ли разместить этот объект в куче.
Основное поведение анализа escape-последовательности заключается в анализе динамической области действия объекта: когда объект определяется в методе, на него может ссылаться внешний метод, например, передаваемый в другие места в качестве параметра вызова, который называется escape-функцией метода.
Например:

public static String craeteStringBuffer(String s1, String s2) {

    StringBuffer sb = new StringBuffer();

    sb.append(s1);

    sb.append(s2);

    return sb.toString();

}

sb - это внутренняя переменная метода. Приведенный выше код не возвращает ее напрямую, поэтому этот StringBuffer не будет изменен другими методами, поэтому его область действия находится только внутри метода. Можно сказать, что эта переменная не выходит за пределы метода.
С помощью escape-анализа мы можем определить, могут ли переменные в методе быть доступны или изменены другими потоками. На основе этой функции JIT может выполнить некоторые оптимизации:

  • Синхронное бездействие
  • Скалярная замена
  • Распределение стека
    Что касается пропусков синхронизации, вы можете сослаться на введение технологии устранения блокировок в моей предыдущей статье «Углубленное понимание технологии оптимизации блокировки многопоточности (5) - Java-виртуальных машин». В этой статье в основном анализируется подшкалярная подстановка и распределение стека.

    Скалярная замена, размещение в стеке

Мы говорим, что после JIT-анализа escape-последовательности, если обнаруживается, что объект не выходит за пределы тела метода, он может быть оптимизирован.Самым большим результатом этой оптимизации является то, что он может изменить объект Java для выделения памяти в куче. Об этом принципе.
На самом деле существует множество причин для размещения объектов в куче, но одна из наиболее важных связана с этой статьей, а именно потому, что память кучи совместно используется потоками, так что объекты, созданные одним потоком, могут быть доступны другим потокам.
Итак, только представьте, если мы создаем объект внутри тела метода, и объект не выходит за пределы метода, необходимо ли размещать объект в куче?
Фактически, в этом нет необходимости, потому что к этому объекту не будут обращаться другие потоки, а жизненный цикл находится только внутри метода, поэтому нет необходимости выделять память в куче и уменьшать потребность в восстановлении памяти.
Затем, после анализа выхода, если обнаруживается, что объект не ускользнул вне закона, какой метод можно оптимизировать, чтобы уменьшить возможность размещения объекта в куче?
Это распределение в стеке. В HotSopt выделение памяти в стеке в настоящее время не реализовано, но реализовано посредством скалярной замены.
Поэтому мы сосредоточимся на том, что такое скалярная замена и как реализовать распределение стека посредством скалярной замены.
Скалярная замена

Скаляр - это фрагмент данных, который нельзя разбить на более мелкие данные. Примитивный тип данных в Java - это скаляр. Напротив, данные, которые можно разложить, называются агрегированными.Объекты в Java являются агрегатами, потому что они могут быть разложены на другие агрегаты и скаляры.
На этапе JIT, если обнаруживается, что объект не может быть доступен для внешнего мира после анализа выхода, то после оптимизации JIT объект будет дизассемблирован на несколько содержащихся в нем переменных-членов. Этот процесс является скалярной заменой.

public static void main(String[] args) {

   alloc();

}

private static void alloc() {

   Point point = new Point(1,2);

   System.out.println("point.x="+point.x+"; point.y="+point.y);

}

class Point{

    private int x;

    private int y;

}

В приведенном выше коде точечный объект не избегает метода выделения, и точечный объект можно разобрать на скаляры. Тогда JIT не будет напрямую создавать объекты Point, а напрямую использовать два скалярных int x и int y для замены объектов Point.

private static void alloc() {

   int x = 1;

   int y = 2;

   System.out.println("point.x="+x+"; point.y="+y);

}

Видно, что после анализа ускользания Point было обнаружено, что количество агрегации, которое он не ускользнуло, было заменено двумя суммами агрегации.
Благодаря скалярной подстановке исходный объект заменяется несколькими переменными-членами. Память, которая изначально должна была быть выделена в куче, больше не нужна, и выделение памяти для переменных-членов может быть выполнено в стеке локальных методов.

Экспериментальное доказательство

Обсуждение дешево, покажите мне код,
нет данных, нет BB;
затем давайте проведем эксперимент, чтобы увидеть, может ли анализ выхода из строя произойти, произойдет ли выделение стека после того, как оно вступит в силу, и что происходит с распределением стека? Каковы преимущества?
Посмотрим на следующий код:

public static void main(String[] args) {

    long a1 = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {

        alloc();

    }

    // 查看执行时间

    long a2 = System.currentTimeMillis();

    System.out.println("cost " + (a2 - a1) + " ms");

    // 为了方便查看堆内存中对象个数,线程sleep

    try {

        Thread.sleep(100000);

    } catch (InterruptedException e1) {

        e1.printStackTrace();

    }

}

private static void alloc() {

    User user = new User();

}

static class User {

}

Фактически, содержимое кода очень простое, то есть используйте цикл for для создания 1 миллиона объектов User в коде.
Мы определили объект User в методе alloc, но не ссылались на него вне метода. Другими словами, этот объект не уйдет за пределы alloc. После escape-анализа JIT можно оптимизировать его распределение памяти.
Мы указываем следующие параметры JVM и запускаем:

-Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError 

Среди них -XX: -DoEscapeAnalysis означает отключение анализа побега.
После того, как программа распечатает стоимость XX мс и до конца кода, мы используем команду jmap, чтобы проверить, сколько объектов User находится в текущей памяти кучи:

➜  ~ jmap -histo 2809

 num     #instances         #bytes  class name
----------------------------------------------

   1:           524       87282184  [I

   2:       1000000       16000000  StackAllocTest$User

   3:          6806        2093136  [B

   4:          8006        1320872  [C

   5:          4188         100512  java.lang.String

   6:           581          66304  java.lang.Class

Из приведенных выше результатов выполнения jmap мы можем видеть, что в общей сложности в куче создается 1 миллион StackAllocTests. Когда
анализ выхода отключен (-XX: -DoEscapeAnalysis), хотя объект User, созданный в методе alloc, не выходит за пределы метода , Но он все еще находится в куче памяти. Другими словами, если нет оптимизации компилятора JIT, нет технологии анализа выхода, так и должно быть при нормальных обстоятельствах. То есть все объекты размещаются в памяти кучи.
Затем мы включаем анализ выхода, а затем выполняем приведенный выше код.

-Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError 

После того, как программа распечатает стоимость XX мс и до конца кода, мы используем команду jmap, чтобы проверить, сколько объектов User находится в текущей памяти кучи:

➜  ~ jmap -histo 2859
 num     #instances         #bytes  class name
----------------------------------------------

   1:           524      101944280  [I

   2:          6806        2093136  [B

   3:         83619        1337904  StackAllocTest$User

   4:          8006        1320872  [C

   5:          4188         100512  java.lang.String

   6:           581          66304  java.lang.Class

Из приведенных выше результатов печати мы можем обнаружить, что после включения анализа выхода (−XX: + DoEscapeAnalysis) в динамической памяти остается только более 80 000 объектов StackAllocTestUser. Другими словами, после JIT-оптимизации количество объектов, выделенных в динамической памяти, упало с 1 миллиона до 80 000.
В дополнение к вышеупомянутому методу проверки количества объектов с помощью jmap, читатели также могут попытаться уменьшить память кучи, а затем выполнить приведенный выше код для анализа в соответствии с количеством сборщиков мусора. Вы также можете обнаружить, что после включения анализа выхода количество сборщиков мусора во время работы Будет значительно сокращено. Именно потому, что многие распределения кучи оптимизированы для распределения стека, поэтому количество сборщиков мусора было значительно сокращено.

Анализ побега незрелый

В предыдущем примере после включения анализа выхода количество объектов изменилось с 1 миллиона до 80 000, но не равно нулю, что указывает на то, что оптимизация JIT не полностью оптимизирует все ситуации.
Документ об анализе побега был опубликован в 1999 году, но он не был реализован до JDK 1.6, и эта технология до сих пор не очень развита.
Основная причина в том, что нет гарантии, что потребление производительности анализа побега будет выше, чем его потребление. Хотя скалярная замена, выделение стека и устранение блокировок могут быть выполнены после анализа выхода. Однако сам по себе анализ побега также требует серии сложного анализа, который на самом деле является относительно трудоемким процессом.
Ярким примером является то, что после анализа побега обнаруживается, что ни один объект не убегает. Тогда процесс анализа побегов был потрачен зря.
Хотя эта технология еще не очень развита, но она также является очень важным средством своевременной оптимизации компилятора.

подводить итоги

В нормальных условиях объекты размещаются в куче, однако с развитием методов оптимизации компилятора, несмотря на то, что этого требует спецификация виртуальной машины, все же существуют некоторые различия в конкретной реализации.
Например, после того, как виртуальная машина HotSpot вводит JIT-оптимизацию, она выполнит escape-анализ для объекта. Если обнаруживается, что объект не выходит за пределы метода, то можно реализовать выделение стека посредством скалярной замены и избежать выделения памяти в куче.
Следовательно, объект должен выделить память в куче, что неверно.
Наконец, оставим вопрос для размышлений: мы обсуждали TLAB раньше, а сегодня мы представили распределение стека. Как вы думаете, есть ли сходство и различие между этими двумя оптимизациями?
Об авторе: Холлис, имеет уникальный квест для кодирования людей, текущих технических экспертов Alibaba, блоггера по персональным технологиям, технических статей, количество чтения всей сети в десятки миллионов, "трех классов программиста" соавтор.
Динамические черные ноты

Серия статей
о том, как стать богом для инженеров Java, находится в обновлении GitHub, добро пожаловать, следите, добро пожаловать в звезду.
Оригинал | Пожалуйста, перестаньте говорить, что объекты Java размещаются в куче памяти!
Столкновение с проблемой Java 300: что такое анализ выхода?
Углубленный параллелизм. Проблема 013: развернуть оптимизацию синхронизированной блокировки.

  • БОЛЬШЕ | Еще больше интересных статей - Применение
    9 шаблонов проектирования в Spring должно быть очень умелым!
    Средняя зарплата 16000 юаней! Зарплаты программистов в первоклассных городах в 2020 году сильно зависят от
    продажи билетов, так почему же Али не продает билеты в кино? Что сделано технически? ? ?
    Оказывается, собеседование по MySQL спросит эти ...

Если вам понравилась эта статья,
нажмите
Оригинал | Пожалуйста, перестаньте говорить, что объекты Java размещаются в куче памяти!
и удерживайте QR-код и подписывайтесь на Hollis. Отправьте его кругу друзей. Это моя самая большая поддержка.
Хорошая статья, читаю ❤️

рекомендация

отblog.51cto.com/13626762/2544186