Before we talk about where Java’s object allocation memory is, let’s look at how C++ object allocation works. C++ instantiates objects in two ways:

  • Objects are defined directly, assigned to the local variable stack of the method stack, with the same life cycle as the method stack, and automatically destroyed when the method exits.
  • Objects are allocated on the heap using the new keyword and are manually destroyed by the user.
#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; }}; intmain() { ClassA ca1(1); ClassA* Ca2 = new ClassA(2); // Use the new keywordreturn 0;
}
Copy the code

Output result:

ClassA(1)
ClassA(2)
~ClassA(1)
Copy the code

Defining an object directly allocates its memory on the stack, so when main exits, it executes ClassA’s dummy function and the object is reclaimed. Objects instantiated with new are allocated on the heap and do not execute dummy functions after main exits. In C++, memory can be allocated on the stack or in heap memory. If Java were to automatically destroy objects by assigning them to the stack when necessary, it would certainly reduce some of the overhead of garbage collection (which requires time-consuming operations such as mark-up). It also improves execution efficiency (there is a high probability that data stored on the stack will be allocated by the virtual machine to be stored in high-speed registers on the physical machine). Although these details are specific to the JVM, they may not be of much concern to developers. However, I was still curious.

Write an unconvincing piece of code to observe the Java output:

public class ClassA{
     public int arg;
     public ClassA(int arg) {
         this.arg = arg;
     }

     @Override
     protected void finalize(a) throws Throwable {
         System.out.println("Object to be destroyed:" + this + "; arg = " + arg);
         super.finalize(); }}public class TestCase1 {
     public static ClassA getClassA(int arg) {
         ClassA a = new ClassA(arg);
         System.out.println("In getA() method :" + a);
         return a;
     }
 
     public static void foo(a) {
         ClassA a = new ClassA(2);
         System.out.println("Foo () method :" + a);
     }
 
 
     public static void main(String[] args) {
         ClassA classA = getClassA(1);
         System.out.println("In the main() method:+ classA); foo(); }}Copy the code

Output result:

GetA () :com.rhythm7.A@29453f44 main() :com.rhythm7.A@29453f44 foo() :com.rhythm7.A@5cad8086Copy the code

After getA() is executed, instance A of the classA object instantiated in getA() is returned and assigned to classA in main. The foo() method is then executed, internally instantiating a classA object, but only printing its HashCode, not returning its object. The result is that neither object has a Finalize () method. What if we force system.gc () to tell the System to do garbage collection?

public static void main(String[] args) {
    A a = getA(1);
    System.out.println("In the main() method: + a);
    foo();
    System.gc();
}
Copy the code

The output

GetA () method :com.rhythm7.A@29453f44 main() method :com.rhythm7.A@29453f44 foo() method :com.rhythm7.A@5cad8086 Object about to be destroyed: com.rhythm7.A@5cad8086; arg = 2Copy the code

This means that the garbage collector needs to be notified to do garbage collection in order to collect objects instantiated in method foo(). Therefore, you can be sure that objects instantiated in foo() will not be destroyed when foo() goes off the stack, that is, local objects instantiated in foo() will not be allocated on the stack.

A review of the literature shows that there is indeed a concept of “escape analysis” for the JVM. Escape analysis is one of the most advanced optimization techniques in Java virtual machines. It is not a means of directly optimizing code, but an analysis technique that provides the basis for other optimization methods. The main function of escape analysis is to analyze object scope. When an object is defined in a method, it may be referenced by an external method, for example, as a call parameter to another method. This behavior is called method escape. The object can even be accessed by an external thread, such as assigning values to class variables or instance variables that can be accessed in other threads, called thread escape. Escape analysis allows you to determine that an object will not escape out of a method or thread. This allows the object to be allocated memory on the stack, and the memory occupied by the object can be destroyed as the frame goes off the stack. In a typical application, the proportion of local objects that do not escape is large, and if stack allocation is used, a large number of objects will be destroyed automatically at the end of the method, putting less pressure on the garbage collection system. In addition, the functions of escape analysis include scalar replacement and synchronous elimination. Scalar substitution refers to: If an object is proven to be unreachable and can be broken down into primitive types, then the program can create its member variables instead of the object when it is actually executed. After splitting the object, In addition to allowing the object’s member variables to be allocated and read and written on the stack, this allows for further optimization. Synchronization elimination refers to the fact that if a variable is proven not to escape from the thread, then there must be no contention between reads and writes of the variable, and synchronization of the variable can be eliminated. Having said that, does the Java virtual machine do escape analysis on objects?

The answer is no.

The paper on escape analysis was published in 1999, but escape analysis was not implemented until Sun JDK 1.6, and the optimization is not yet mature enough for much improvement. The main reason for immaturity is that the performance benefit of escape analysis cannot be guaranteed to exceed its cost. Because escape analysis itself is a time-consuming process, if the result of the analysis is that there are few non-escape objects, then the analysis takes longer than the optimization reduces the time, which is not worth the loss. So at present, virtual machines can only use less accurate, but relatively small time pressure algorithm to complete escape analysis. Another point is that some optimizations based on escape analysis, such as “on-stack allocation” mentioned above, have not been implemented in HotSpot due to the complexity of the current implementation of the HotSpot VIRTUAL machine. In fact, in the Java virtual machine, there is a sentence that reads:

The heap is The Runtime data area from which memory for all class instances and arrays is allocated. The heap is the run-time data area where all object instances and arrays are allocated memory.

So forget the idea of allocating object memory on the Java stack, at least not in current HotSpot. That is, Java objects are allocated only on the heap.

PS: If necessary and confirmed to be beneficial to the program operation, users can manually enable escape analysis by using parameter -xx :+DoEscapeAnalysis. After this function is enabled, users can view the analysis results by using parameter -xx :+PrintEscapeAnalysis.