Author: LittleMagic www.jianshu.com/p/8377e0997…

To avoid ambiguity, we can rephrase:

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

A: Not necessarily. They can allocate memory on the stack when certain conditions are met.

JVM memory structure is very important, review more

This may be a little different than what we normally understand. How can a virtual machine stack store instance data when it is used to store basic data types, references, and return addresses?

This is due to two optimizations made by the Java JIT (Just-in-time) compiler, called Escape Analysis and Scalar replacement respectively.

Notice where the JIT is located

The description of escape analysis on The Chinese wiki is basically accurate, excerpted as follows:

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

If a subroutine allocates an object and returns a pointer to that object, the place in the program where the object was accessed may not be determined-thus the pointer has successfully “escaped.” Pointers also escape if they are stored in global variables or other data structures, because global variables can be accessed outside the current subroutine.

Escape analysis determines all the places where a pointer can be stored and whether the lifetime of the pointer can be guaranteed to remain in the current process or thread.

In simple terms, escape analysis in the JVM can determine whether an object allocates memory on the heap by analyzing the scope of use of object references (that is, dynamic scope), as well as other optimizations.

For escape analysis, you can read this article: The interview asked me Java escape analysis, instantly killed. The following example illustrates one possibility of object escape.

static StringBuilder getStringBuilder1(String a, String b) { StringBuilder builder = new StringBuilder(a); builder.append(b); return builder; } static String getStringBuilder2(String a, String b) { StringBuilder builder = new StringBuilder(a); builder.append(b); return builder.toString(); // The scope of the builder remains inside the method, without escaping}Copy the code

In the case of JDK 1.8, escape analysis can be turned on or off by setting the JVM parameters -xx :+DoEscapeAnalysis, -xx: -doEscapeAnalysis (which of course is turned on by default).

Let’s start with an example where no object escapes.

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; }}}Copy the code

Then observe the difference by turning DoEscapeAnalysis on and off.

Shut-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
# ......Copy the code

Open 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
# ......Copy the code

With escape analysis turned off, there are 5000000 instances of MyObject on the heap. With escape analysis turned on, there are 90871 instances left, which is less than 2% of the original number of instances and memory footprint.

Alternatively, turning off escape analysis can cause frequent GC if the heap memory limit is small (such as -xMS10m -XMx10m) and GC logs are printed (-xx :+PrintGCDetails), which is not the case if escape analysis is turned on. This shows that escape analysis does reduce heap memory stress.

However, escape analysis is only a prerequisite for memory allocation on the stack, and a scalar replacement is required to actually implement it.

Scalars are data in the JVM that cannot be subdivided, such as int, long, reference, etc. Conversely, data that can be subdivided is called aggregate.

Still considering the above example, MyObject is an aggregate because it consists of two scalars a and b. Through escape analysis, the JVM finds that myObject does not escape the allocate() method, and the scalar replacement process disassembles myObject directly into a and B, i.e.

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

As you can see, allocation of objects is completely eliminated, and ints and doubles are basic data types that can be allocated on the stack. So objects can be allocated on the stack as long as they do not escape scope and can be decomposed into pure scalar representations.

The JVM provides the parameter -xx :+EliminateAllocations to turn on scalar substitutions, and the default remains on. Obviously, if you turn it off, you disable memory allocation on the stack, and only escape analysis does not work.

In the Debug version of the JVM also can pass parameters – XX: + PrintEliminateAllocations to view of the specific conditions of the scalar replace.

In addition to scalar replacement, synchronous elimination can also be realized through escape analysis

Synchronization elision, of course, is irrelevant to the topic of this article.

Here’s an example:

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

The lifetime of the lockObject is only in the someMethod() method, and there is no multi-thread access problem, so synchronized blocks are meaningless and will be optimized away:

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

Read more on my blog:

1.Java JVM, Collections, Multithreading, new features series tutorials

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

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

4.Java, backend, architecture, Alibaba and other big factory latest interview questions

Life is good. See you tomorrow