Прежде чем мы поговорим о расположении памяти для размещения объектов в Java, давайте взглянем на распределение объектов в C++. Существует два способа создания экземпляров объектов в C++:
- Непосредственно определите объект, объект размещается в стеке локальных переменных стека методов, жизненный цикл соответствует стеку методов, и объект автоматически уничтожается при выходе из метода.
- Объекты выделяются в куче с помощью нового ключевого слова, и объекты должны быть уничтожены пользователем вручную.
#include <iostream>
using namespace std;
class ClassA {
private:
int arg;
public:
ClassA(int a): arg(a) {
cout << "ClassA(" << arg << ")" << endl;
}
~ClassA(){
cout << "~ClassA(" << arg << ")" << endl;
}
};
int main() {
ClassA ca1(1); //直接定义对象
ClassA* ca2 = new ClassA(2); //使用new关键字
return 0;
}
Выходной результат:
ClassA(1)
ClassA(2)
~ClassA(1)
Способ прямого определения объекта будет выделять память объекта в стеке, поэтому после выхода из основной функции будет выполнена воображаемая функция ClassA, и объект будет переработан. Память объекта, созданного с помощью new, выделяется в куче, и объект не будет выполнять фиктивную функцию после выхода из основной функции.
В C++ память может быть выделена в стеке или в куче.
Верно ли это и для Java?Если Java также размещает объекты в стеке, когда это необходимо, тем самым автоматически уничтожая объекты, это неизбежно уменьшит некоторые накладные расходы на сборку мусора (сборка мусора в Java требует ряда трудоемких операций, таких как маркировка и сортировка) ), а также повысить эффективность выполнения (данные, хранящиеся в стеке, имеют высокую вероятность выделения виртуальной машиной в высокоскоростные регистры физической машины для хранения). Хотя эти детали относятся к JVM, кажется, что разработчикам все равно.
Тем не менее, мне все еще любопытно.
Напишите ненадежный фрагмент кода для наблюдения за выводом на Java:
public class ClassA{
public int arg;
public ClassA(int arg) {
this.arg = arg;
}
@Override
protected void finalize() throws Throwable {
System.out.println("对象即将被销毁: " + this + "; arg = " + arg);
super.finalize();
}
}
public class TestCase1 {
public static ClassA getClassA(int arg) {
ClassA a = new ClassA(arg);
System.out.println("getA() 方法内:" + a);
return a;
}
public static void foo() {
ClassA a = new ClassA(2);
System.out.println("foo() 方法内:" + a);
}
public static void main(String[] args) {
ClassA classA = getClassA(1);
System.out.println("main() 方法内:" + classA);
foo();
}
}
Выходной результат:
getA() 方法内:com.rhythm7.A@29453f44
main() 方法内:com.rhythm7.A@29453f44
foo() 方法内:com.rhythm7.A@5cad8086
После выполнения метода getA() экземпляр объекта classA, созданный в методе getA(), возвращается и присваивается classA в основном методе.
Затем выполните метод foo(), который создает экземпляр объекта classA внутри метода, но только выводит его HashCode, не возвращая свой объект.
В результате ни один из объектов не выполняет метод finalize().
Если мы заставим использоватьSystem.gc()
уведомить систему о сборке мусора, каков результат?
public static void main(String[] args) {
A a = getA(1);
System.out.println("main() 方法内:" + a);
foo();
System.gc();
}
выходной результат
getA() 方法内:com.rhythm7.A@29453f44
main() 方法内:com.rhythm7.A@29453f44
foo() 方法内:com.rhythm7.A@5cad8086
对象即将被销毁: com.rhythm7.A@5cad8086; arg = 2
Это означает, что сборщик мусора должен быть уведомлен о выполнении сборки мусора, чтобы вернуть объект, созданный в методе foo(). Следовательно, объект, созданный в foo(), не будет уничтожен после извлечения из памяти метода foo(), то есть локальный объект, созданный в методе foo(), не будет размещен в стеке.
Проверьте соответствующую информацию и обнаружите, что JVM действительно существует.«Анализ побега»Концепция чего-либо.
Содержание примерно такое:
Escape-анализ — относительно передовая технология оптимизации в текущей виртуальной машине Java.Это не средство прямой оптимизации кода, а технология анализа, которая обеспечивает основу для других методов оптимизации.
Основная функция escape-анализа заключается в анализе области действия объекта.
Когда объект определен в методе, на него могут ссылаться внешние методы, такие как передача в качестве параметра вызова другим методам, такое поведение называетсяметод побега. Даже к объекту могут обращаться внешние потоки, такие как присвоения переменных класса или переменных экземпляра, к которым можно получить доступ в других потоках, называемыхпобег потока.
С помощью технологии анализа ускользания можно сделать вывод, что объект не ускользнет за пределы метода или потока. В соответствии с этой функцией объекту может быть выделена память в стеке, а пространство памяти, занятое объектом, может быть уничтожено при извлечении стека кадров. В общих приложениях доля локальных объектов, которые не ускользнут, велика.Если можно использовать выделение стека, то большое количество объектов будет автоматически уничтожено с окончанием метода, и нагрузка на систему сбора мусора будет значительно меньше.
Кроме того, роль анализа побега также включаетскалярная заменаиУстранение синхронизации ;
скалярная заменаОтносится к: если доказано, что объект не доступен извне, и объект может быть разобран на несколько основных типов, то при фактическом выполнении программы объект не может быть создан, но могут быть использованы несколько методов, непосредственно его создающих , Он заменяется переменными-членами, используемыми в этом методе.После разделения объекта, в дополнение к тому, что переменные-члены объекта могут быть выделены, прочитаны и записаны в стек, он также может создать условия для последующих дальнейших методов оптимизации. .
Устранение синхронизацииОтносится к: если доказано, что переменная не выходит из потока, то чтение и запись этой переменной определенно не будут конкурировать, и меры синхронизации, реализованные для этой переменной, также могут быть устранены.
Сказав эти функции анализа побега, выполняет ли виртуальная машина Java анализ побега на объектах?
Ответ - нет.
Статья об анализе побегов была опубликована в 1999 году, но анализ побегов не был реализован до Sun JDK 1.6, и до сих пор оптимизация не достигла достаточной зрелости, есть еще много возможностей для улучшения. Основная причина незрелости заключается в том, что нет никакой гарантии, что выигрыш в производительности от анализа побегов должен перевесить его потребление. Поскольку сам по себе анализ убегания является трудоемким процессом, если в результате анализа остается мало объектов, которые не убегают, то анализ займет больше времени, чем время, сокращенное оптимизацией, что не стоит потерь.
Поэтому в настоящее время виртуальная машина может использовать только алгоритм, который не так точен, но с относительно меньшими временными ограничениями для завершения анализа побега. Другое дело, что некоторые методы оптимизации, основанные на анализе выхода,Как упоминалось выше, «распределение в стеке» является более сложным из-за текущей реализации виртуальной машины HotSpot, поэтому на данный момент эта оптимизация в HotSpot не проводилась..
На самом деле в виртуальной машине Java есть такое предложение:
Куча — это область данных времени выполнения, из которой выделяется память для всех экземпляров классов и массивов.
Куча — это область данных времени выполнения, в которой выделена память для всех экземпляров объектов и массивов.
Итак, забудьте об идее выделения объектной памяти в стеке Java, по крайней мере, в настоящее время в HotSpot. Другими словами, размещение объектов в Java происходит только в куче.
PS: При необходимости и подтверждении того, что это выгодно для работающей программы, пользователь может с помощью параметра -XX:+DoEscapeAnalysis вручную открыть анализ побега, после открытия можно просмотреть результаты анализа через параметр -XX :+PrintEscapeAnalysis.