Когда дело доходит до JVM, я считаю, что все должны знать, что такое JVM? Однако, когда дело доходит до анализа побега, я полагаю, что большинство людей могут запутаться: что же такое анализ побега? Позвольте мне поделиться с вами дальше.
В системе компиляции Java в процессе преобразования файла исходного кода Java в исполняемую компьютером машинную инструкцию необходимо пройти два этапа компиляции.Первый этап компиляции заключается в компиляции файла Java в файл класса, может быть распознан JVM с помощью команды javac. Второй этап компиляции заключается в преобразовании файла класса в инструкции байт-кода для распознавания компьютером.
На втором этапе компиляции JVM переводит байт-код в соответствующие машинные инструкции, интерпретируя их, считывает их одну за другой, интерпретирует и переводит одну за другой. Очевидно, что после интерпретации и выполнения его скорость выполнения будет намного ниже, чем при выполнении программ с двоичным байт-кодом. Это функция традиционного интерпретатора JVM. Для решения этой проблемы эффективности была внедрена технология JIT (Just-In-Time Compilation).
С введением технологии JIT, хотя программы Java по-прежнему интерпретируются и выполняются через интерпретатор, когда определенный метод вызывается часто, JVM будет считать его «горячим кодом». Итак, если бы это были вы, что бы вы подумали о том, чтобы сделать это? Эх, да, кешируйте "горячий код". JIT также делает это: JIT переводит некоторый «горячий код» в локальный машинный код, оптимизирует его, а затем кэширует переведенный машинный код для следующего использования.
Стратегия выделения памяти JVM
В JVM память, управляемая JVM, включает область методов, стек виртуальной машины, собственный стек методов, кучу, счетчик программ и т. д. (Я не буду их здесь вводить по одному, позже напишу статьи по JVM для заинтересованных студентов)
Как правило, данные времени выполнения JVM хранятся в стеке и куче. Стек используется для хранения ссылок на некоторые базовые переменные и объекты (конечно, это не абсолютно и будет введено позже), а куча используется для хранения элементов и объектов массива, то есть конкретных экземпляров новый.
С развитием JIT-компиляторов и зрелостью технологии escape-анализа. Размещение в стеке, технология оптимизации скалярной замены приведут к утверждению, что размещение объектов в куче не так уж и абсолютно.
Что такое анализ побега?
Escape-анализ заключается в том, что когда объект новый, его можно вызывать извне, а если он передается в качестве параметра наружу, то это называется escape-методом.
Например:
Экранирование без метода:
public static void returnStr(){
User user = new User();
user.setId(1);
user.setName("张三");
user.setAge(18);
}
public static String returnStr(){
User user = new User();
user.setId(1);
user.setName("张三");
user.setAge(18);
return user.toString();//这里User要实现get,set方法,还要实现toString方法
}
Выход из метода:
public static User returnStr(){
User user = new User();
user.setId(1);
user.setName("张三");
user.setAge(18);
return user;//这里User要实现get,set方法
}
Вы должны увидеть разницу. Два метода в первом абзаце здесь не экранируются, но метод во втором абзаце экранируется. Это показывает, что если вы хотите экранировать метод, вам нужно позволить самому объекту вызываться извне.
Используя escape-анализ, компилятор может оптимизировать код следующим образом:
- Синхронизация отсутствует. Если обнаружено, что объект доступен только из одного потока, то операции над этим объектом могут выполняться независимо от синхронизации.
- Преобразование выделения кучи в выделение стека. Если объект выделяется в подпрограмме, чтобы указатель на объект никогда не исчезал, объект может быть кандидатом на выделение в стеке, а не в куче.
- Разделение объектов или скалярная замена, некоторые объекты могут не существовать как непрерывная структура памяти для доступа, тогда часть (или весь) объекта может храниться не в памяти, а в регистрах ЦП.
Здесь включите и выключите анализ побега с помощью этого:
-XX:+DoEscapeAnalysis
: Указывает, что включен анализ побега.
-XX:-DoEscapeAnalysis
: Указывает, что escape-анализ отключен.Начиная с jdk 1.7 escape-анализ запущен по умолчанию.Чтобы его отключить, нужно указать -XX:-DoEscapeAnalysis
Фактический бой: наблюдайте, выделяется ли он в стеке, печатая GC
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.createUser();
System.out.println("11");
}
public void createUser(){
int i = 0;
while(true){
User user = new User();
user.setId(i);
user.setName("张三");
user.setAge(18);
i++;
}
}
}
public class User {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Здесь есть два класса, первый — это класс Test, а другой — класс сущности User.В классе Test бесконечный цикл используется для создания объекта User и настройки его в идее-XX:+PrintGC
Параметры, отвечающие за печать информации GC, запуск класса
результат:Здесь видно, что программа не завершилась, но информация GC не распечаталась, в это время отключаем escape-анализ.
Настройте эти два в идее
-XX:+PrintGC -XX:-DoEscapeAnalysis
начать урок зановоЗдесь видно, что консоль выводит информацию GC, поэтому также можно сделать вывод, что после включения escape-анализа, если это не метод escape, то объект размещается в стеке.
синхронизировать пропустить
При динамической компиляции синхронизированных блоков JIT-компилятор может использовать escape-анализ, чтобы определить, может ли объект блокировки, используемый синхронизированным блоком, быть доступен только одному потоку и не может быть освобожден для других потоков.
Если объект блокировки, используемый синхронизированным блоком, окажется доступным только для одного потока посредством этого анализа, то JIT-компилятор отменит синхронизацию этой части кода при компиляции синхронизированного блока Вызов снятия блокировки.
скалярная замена
Когда с помощью escape-анализа определяется, что доступ к объекту извне невозможен и объект может быть подвергнут дальнейшей декомпозиции, JVM не будет создавать объект, а разложит переменные-члены объекта на несколько переменных-членов, используемых этим методом. пространство в кадре стека или регистре, чтобы не было выделения памяти объекта из-за отсутствия большого непрерывного пространства. Включить скалярные параметры подстановки-XX:+EliminateAllocations
, он включен по умолчанию после JDK7
Скаляр и агрегат
Скаляр — это величина, которая не может быть далее разложена, а базовый тип данных Java — это скаляр (например, int, long и другие базовые типы данных и ссылочные типы и т. д.), противоположность скаляру — это величина, которая может подвергаться дальнейшему разложению, и эта величина называется совокупной суммой. В 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;
}
В приведенном выше коде точечный объект не экранируется.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);
}
Эта замена может значительно сократить использование памяти кучи, потому что если объект не нужно создавать, то нет необходимости и выделять память кучи.