Глубокое понимание escape-анализа в Java.

Java задняя часть JVM переводчик

В системе компиляции Java в процессе преобразования файла исходного кода Java в исполняемую компьютером машинную инструкцию требуется два этапа компиляции: первый этап заключается в преобразовании файла .java в файл .class. Второй этап компиляции — это процесс преобразования .class в машинные инструкции.

Первая компиляция этоjavacЗаказ.

На втором этапе компиляции JVM переводит байт-коды в соответствующие машинные инструкции, интерпретируя их, считывает их одну за другой, интерпретирует и транслирует одну за другой. Очевидно, что после интерпретации и выполнения его скорость выполнения будет намного ниже, чем у исполняемой двоичной программы байт-кода. Это функция традиционного интерпретатора JVM (Interpreter). Для решения этой проблемы эффективности была внедрена технология JIT (Just-In-Time Compilation).

После введения технологии JIT программы Java по-прежнему интерпретируются и выполняются интерпретатором. Когда JVM обнаруживает, что метод или блок кода запускается очень часто, она будет рассматривать это как «Код горячей точки». Затем JIT преобразует некоторый «горячий код» в машинный код, относящийся к локальной машине, оптимизирует его, а затем кэширует переведенный машинный код для следующего использования.

Из-за всего, что связано с компиляцией JIT и обнаружением горячих точек, я вУглубленный анализ принципов компиляции JavaЭто было введено в , поэтому я не буду повторять это здесь.Эта статья в основном знакомит с оптимизацией в JIT. Одним из наиболее важных аспектов JIT-оптимизации является анализ побегов.

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

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

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

Например следующий код:

public static StringBuffer craeteStringBuffer(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb;
}

public static String createStringBuffer(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb.toString();
}

в первом фрагменте кодаsbescapes, а второй код вsbСпасения нет.

Используя escape-анализ, компилятор может оптимизировать код следующим образом:

1. Синхронизация отсутствует. Если обнаружено, что объект доступен только из одного потока, то операции с этим объектом могут выполняться независимо от синхронизации.

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

3. Отдельный объект или скалярная замена. Некоторым объектам может не понадобиться существовать как непрерывная структура памяти для доступа, поэтому часть (или весь) объекта может храниться не в памяти, а в регистрах ЦП.

Когда код Java выполняется, вы можете указать, следует ли включить анализ побега с помощью параметров JVM.

-XX:+DoEscapeAnalysis: Указывает, что включен анализ побега.

-XX:-DoEscapeAnalysis: Указывает, что escape-анализ отключен.Начиная с jdk 1.7 escape-анализ запускается по умолчанию.Чтобы его отключить, нужно указать -XX:-DoEscapeAnalysis

синхронизировать пропустить

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

Если в ходе этого анализа будет подтверждено, что объект блокировки, используемый синхронизированным блоком, доступен только одному потоку, JIT-компилятор десинхронизирует эту часть кода при компиляции синхронизированного блока. Этот процесс отмены синхронизации называется пропуском синхронизации, также называемымснятие блокировки.

Например, следующий код:

public void f() {
    Object hollis = new Object();
    synchronized(hollis) {
        System.out.println(hollis);
    }
}

Объект hollis заблокирован в коде, но жизненный цикл объекта hollis находится только в методе f() и не будет доступен другим потокам, поэтому он будет оптимизирован на этапе JIT-компиляции. оптимизирован для:

public void f() {
    Object hollis = new Object();
    System.out.println(hollis);
}

Таким образом, при использовании synchronized, если JIT обнаружит, что нет проблем с безопасностью потоков после анализа побега, блокировка будет устранена.

скалярная замена

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

На этапе JIT, если обнаруживается, что объект не будет доступен внешнему миру после escape-анализа, то после 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;
}

В приведенном выше коде точечный объект не экранируется.allocметод, а точечный объект можно разобрать на скаляры. Тогда 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 было установлено, что он не убегал, а был заменен двумя совокупными суммами. Так в чем же преимущество скалярной замены? Это может значительно уменьшить использование памяти кучи. Потому что раз нет необходимости создавать объекты, нет необходимости и выделять память в куче.

Скалярная замена обеспечивает хорошую основу для распределения стека.

выделение стека

В виртуальной машине Java объекты выделяются в памяти в куче Java, что является здравым смыслом. Однако есть особый случай, то есть, если у объекта нет escape-метода после escape-анализа, он может быть оптимизирован для размещения в стеке. Это избавляет от необходимости выделять память в куче и устраняет необходимость в сборке мусора.

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

Здесь все же необходимо вкратце сказать, что в существующей виртуальной машине нет реальной реализации распределения стека.Не все объекты и массивы размещаются в куче.В нашем примере объект не размещается в куче, он фактически реализуется путем скалярной подстановки.

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

Статьи по анализу ускользания были опубликованы в 1999 году, но не были реализованы до JDK 1.6, а технология еще не очень зрелая.

Фундаментальная причина в том, что нет никакой гарантии, что потребление производительности escape-анализа должно быть выше, чем его потребление. Хотя скалярная замена, выделение стека и устранение блокировки могут быть выполнены после анализа побега. Однако сам анализ побега также требует серии сложных анализов, что на самом деле является относительно трудоемким процессом.

Крайним примером является то, что после анализа побега обнаруживается, что нет объекта, который не ускользает. Тогда процесс анализа побега пропадает зря.

Хотя эта технология не очень зрелая, она также является очень важным средством технологии оптимизации компилятора «точно в срок».

wechat

использованная литература

"Глубокое понимание виртуальной машины Java"

Википедия