Original | interviewer: Java objects must be allocated on the heap?

I am looking at the Java virtual machine for a rainy day in my work. First of all, I will throw out an interview question that I think of myself, and then I will introduce the following knowledge such as escape analysis, scalar replacement, allocation on the stack and so on

The interview questions

Do Java objects have to be allocated on the heap?

Think about it first, and then read it better!

Analysis of the

We all know that Java objects are allocated on the heap, and the heap space is shared by all threads. Those of you who know NIO libraries know that there is also out-of-heap memory, also known as direct memory. Direct memory is an area of memory that is requested directly from the operating system and is generally faster to access than heap memory. The size of direct memory is not directly limited by Xmx values, but be careful when using it. After all, the system memory is limited, and the sum of heap and direct memory is still limited by the operating system memory.

As you can see from the above analysis, Java objects can be allocated on the heap as well as directly in off-heap memory. But that’s not what I want to talk about today, I want to talk about allocation on the stack, and when we get to allocation on the stack we have to talk about escape analysis

Escape analysis

Escape analysis is a static analysis that dynamically determines the dynamic range of Pointers and analyzes where Pointers can be accessed in a program.

In other words, the purpose of escape analysis is to determine whether an object’s scope is likely to escape the method body

There are two criteria

  1. Whether objects are stored in the heap (static fields or instance fields of objects in the heap)
  2. Whether objects are passed into unknown code (method callers and parameters)

So let’s look at these two arguments

As to whether an object is stored in the heap, we know that the heap memory is shared by threads. Once an object is allocated in the heap, all threads can access the object, so that the real-time compiler can’t track all uses of the object. Such an object is a runaway object, as shown below

public class Escape {
    private static User u;
    public static void alloc(a) {
        u = new User(1."baiya"); }}Copy the code

The User object is a member variable of the class Escape and is accessible to all threads, so Escape occurs

The second is whether the object is passed to unknown code, the compiler takes method of Java compiled for the unit, instant compiler will put method has not been inlined methods as unknown code, so could not determine the unknown method method call will put the caller or parameters in the heap, so that the caller and parameters of the method is to escape, as shown below

public class Escape {
    private static User u; 
    public static void alloc(User user) { u = user; }}Copy the code

The alloc parameter user is assigned to the u member of the class Escape, and is therefore accessed by all threads.

On the stack

Stack allocation is an optimization technique provided by the Java Virtual Machine. The basic idea of this technique is that thread-private objects can be broken up and allocated on the stack instead of the heap. So what’s the advantage of being on the stack? We know that variables in the stack are destroyed automatically after the method call, so we save the JVM for garbage collection, which can improve system performance

Stack allocation is implemented based on escape analysis and scalar substitution

We use a concrete example to verify that non-escape analysis objects are indeed allocated on the stack

public class OnStack {
    public static void alloc(a) {
        User user = new User(1."baiya");
    }
    public static void main(String[] args) {
        long start = Instant.now().toEpochMilli();
        for (int i = 0; i < 100 _000_000; i++) {
            alloc();
        }
        long end = Instant.now().toEpochMilli();
        System.out.println("Time:"+ (end - start)); }}Copy the code

The above code loops through the alloc method 100 million times to create User objects. Each User object takes up about 16 bytes of space to create 100 million times, so it would require 1.5 gigabytes of memory if all users were allocated on the heap. If we set the heap size to less than this, gc should occur, and if we set it to a very small size, a lot of GC should occur.

We execute the above code with the following arguments

-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+EliminateAllocations

-server indicates that the server mode is enabled. Escape analysis requires the support of the server mode

-xmx10-xMS10m sets the heap memory to 10m, much less than 1.5GB

-xx :+DoEscapeAnalysis Enables escape analysis

-xx :+PrintGCDetails Prints gc logs if GC occurs

-xx :+EliminateAllocations enables scalar substitution, allowing objects to be split and allocated on the stack, such as the User object, which has two attributes ID and Name, and can be allocated as separate local variables

After the JVM parameters are configured, the code is executed and the result shows that gc is performed three times in 10 milliseconds. You can infer that the User objects are not all allocated to the heap, but most allocated to the stack. The advantage of allocating on the stack is that the corresponding memory is automatically freed after the method ends, which is an optimization method.

We said that dependency escape analysis and scalar substitutions were allocatable on the stack, so we could break either condition, remove escape analysis and execute the code above by -xx: -doescapteAnalysis or turn off scalar substitutions -xx :-EliminateAllocations, When we look at the execution, we see that a lot of GC took place and it took 3182 milliseconds, which is much higher than the 10 milliseconds above, so we can assume that the optimizations allocated on the stack were not performed

Calculate the size of the User object

The object consists of four parts

  1. Object header: Records the instance name, ID, and instance status of an object.

    8 bytes for normal objects, 12 bytes for arrays (8 bytes for normal object header + 4 bytes for array length)

  2. Basic types of

    Boolean,byte takes 1 byte

    Char,short takes 2 bytes

    Int,float takes up 4 bytes

    Long,double takes 8 bytes

  3. Reference type: Each reference type takes 4 bytes

  4. Padding: calculated in multiples of 8, less than 8 will automatically fill up

Our User object above has two attributes: an id of type int takes up 4bytes, a name of type reference takes up 4bytes, plus an object header of 8 bytes, which is exactly 16 bytes

conclusion

There is a lot more to learn about virtual machines and it is important to know how to write good code, optimize performance, and troubleshooting problems. For example, escape analysis, real-time compiler will optimize based on the results of escape analysis, such as elimination and scalar replacement. Interested friends can check their own information to learn. Using this stack allocation example, we will write code that will not escape objects into the method body, which will be optimized by the compiler to improve performance. Objects in Java are not necessarily allocated on the heap, but may also be allocated on the stack

The resources

  1. Real Java Virtual Machine
  2. In-depth Understanding of the Java Virtual Machine
  3. zh.wikipedia.org/wiki/ escape analysis

Welcome to pay attention to the public number [every day white teeth], get the latest article, we communicate together, common progress!