Are objects in Java allocated on the heap?

By LittleMagic
https://www.jianshu.com/p/8377e09971b8

In order to prevent ambiguity, we can put it another way:

Are Java object instances and array elements allocated memory on the heap?

A: not necessarily. When certain conditions are met, they can allocate memory on the (virtual machine) stack.

JVM memory structure is very important, review more

This may be different from our usual understanding. Virtual machine stack is generally used to store basic data types, references and return addresses. How can instance data be stored?

This is because of two optimizations by the Java JIT (just in time) compiler, called escape analysis (escape analysis) and scalar replacement.

Pay attention to the position of JIT

The description of escape analysis on Chinese wiki is basically accurate, and the excerpt is as follows:

In the theory of compiler optimization, escape analysis is a method to determine the dynamic range of pointer - analyzing where the pointer can be accessed in the program. When a variable (or object) is allocated in a subroutine, a pointer to the variable may escape to another thread of execution or return to the caller subroutine.

If a subroutine assigns an object and returns a pointer to the object, the object may not be sure where it is accessed in the program - so the pointer "escapes" successfully. If the pointer is stored in a global variable or other data structure, because the global variable can be accessed outside the current subprogram, the pointer also escapes.

escape analysis Determine where a pointer can be stored, and whether the pointer's life cycle can be guaranteed only in the current process or thread.

In a nutshell, the escape analysis You can analyze the use scope of object references (i.e. dynamic scope) to determine whether the objects should allocate memory on the heap, or you can do some other optimization.

about escape analysis , you can read this article: The interview asked me Java escape analysis, and I was killed instantly. The following example illustrates the possibility of an object escaping.

static StringBuilder getStringBuilder1(String a, String b) {
    StringBuilder builder = new StringBuilder(a);
    builder.append(b);
    return builder; // builder escapes to the outside through method return value
}

static String getStringBuilder2(String a, String b) {
    StringBuilder builder = new StringBuilder(a);
    builder.append(b);
    return builder.toString(); // builder scope is maintained inside the method, not escaping
}

Take JDK 1.8 for example, you can turn it on or off by setting the JVM parameters - XX:+DoEscapeAnalysis, - XX:-DoEscapeAnalysis escape analysis (the default is on, of course).

Let's write an example of no object escaping.

public class EscapeAnalysisTest {
  public static void main(String[] args) throws Exception {
    long start = System.currentTimeMillis();
    for (int i = 0; i < 5000000; i++) {
      allocate();
    }
    System.out.println((System.currentTimeMillis() - start) + " ms");
    Thread.sleep(600000);
  }

  static void allocate() {
    MyObject myObject = new MyObject(2019, 2019.0);
  }

  static class MyObject {
    int a;
    double b;

    MyObject(int a, double b) {
      this.a = a;
      this.b = b;
    }
  }
}

Then observe the difference by turning the DoEscapeAnalysis switch on and off.

Turn off escape analysis

~ java -XX:-DoEscapeAnalysis EscapeAnalysisTest
76 ms
~ jmap -histo 26031
 num #instances #bytes class name
----------------------------------------------
   1: 5000000      120000000  me.lmagics.EscapeAnalysisTest$MyObject
   2: 636       12026792  [I
   3: 3097        1524856  [B
   4: 5088         759960  [C
   5: 3067          73608  java.lang.String
   6: 623          71016  java.lang.Class
   7: 727          43248  [Ljava.lang.Object;
   8: 532          17024  java.io.File
   9: 225          14400  java.net.URL
  10: 334          13360  java.lang.ref.Finalizer
# ......

Enable escape analysis

~ java -XX:+DoEscapeAnalysis EscapeAnalysisTest
4 ms
~ jmap -histo 26655
 num #instances #bytes class name
----------------------------------------------
   1: 592       11273384  [I
   2: 90871        2180904  me.lmagics.EscapeAnalysisTest$MyObject
   3: 3097        1524856  [B
   4: 5088         759952  [C
   5: 3067          73608  java.lang.String
   6: 623          71016  java.lang.Class
   7: 727          43248  [Ljava.lang.Object;
   8: 532          17024  java.io.File
   9: 225          14400  java.net.URL
  10: 334          13360  java.lang.ref.Finalizer
# ......

Visible, off escape analysis After that, there are 5000000 MyObject instances on the heap. After the escape analysis is enabled, there are only 90871 instances left. Both the number of instances and the memory usage are less than 2% of the original.

In addition, if the heap memory is limited to a small amount (for example, plus - Xms10m -Xmx10m), and the GC log (- XX:+PrintGCDetails) is printed, close escape analysis It will also cause frequent GC, open escape analysis This is not the case. This shows that escape analysis It does reduce the pressure on the heap memory.

However, escape analysis is only the premise of memory allocation on the stack, and then scalar replacement is needed to realize it.

Scalar refers to the data that can no longer be subdivided in the JVM, such as int, long, reference, etc. In contrast, the data that can be subdivided is called aggregate.

Considering the above example, MyObject is an aggregate because it consists of two scalars A and b. Through escape analysis, the JVM will find that MyObject does not escape from the scope of allocate() method, and the scalar substitution process will directly split MyObject into a and b, that is to say:

static void allocate() {
    int a = 2019;
    double b = 2019.0;
}

It can be seen that the allocation of objects has been completely eliminated, while int and double are basic data types, which can be allocated directly on the stack. Therefore, when the object does not escape from the scope and can be decomposed into pure scalar representation, the object can be allocated on the stack.

The JVM provides the parameter - XX: + eliminatealocations to enable scalar substitution, which is still enabled by default. Obviously, if you turn it off, it's equivalent to banning memory allocation on the stack. Only escape analysis can't work.

In the Debug JVM, you can also view the scalar substitution details through the parameter - XX: + printeliminatealocations.

In addition to scalar substitution, by escape analysis Can also achieve synchronous elimination

(synchronization elision), of course, it has nothing to do with the topic of this article.

for instance:

private void someMethod() {
    Object lockObject = new Object();
    synchronized (lockObject) {
      System.out.println(lockObject.hashCode());
    }
}

The life cycle of lockObject is only in someMethod() method, and there is no multi-threaded access problem. Therefore, synchronized block is meaningless and will be optimized:

private void someMethod() {
    Object lockObject = new Object();
    System.out.println(lockObject.hashCode());
}

Recommend to my blog to read more:

1.Java JVM, collection, multithreading, new features series

2.Spring MVC, Spring Boot, Spring Cloud series tutorials

3.Maven, Git, Eclipse, Intellij IDEA series tools tutorial

4.Latest interview questions of Java, backend, architecture, Alibaba and other large factories

Feel good, don't forget to like + forward!

Tags: Java jvm Spring JDK

Posted on Wed, 20 May 2020 03:48:52 -0400 by xphoenixx