Used for loop, distribution and escape analysis

For Java applications in certain areas, it is very important to minimize the amount of object / garbage created. These applications usually cannot withstand GC pauses, so they use specific techniques and methods to avoid garbage. One of these techniques is related to iterative collections or arrays of items. The preferred method is to use the classic for loop. Enhance the for loop by using set iterators under the cover.

To prove this, I am studying loops because I want to better understand the difference between them and measure the amount of garbage created using enhanced for loops, which can be said to be a better and more intuitive syntax.

Before experimenting with this, I (wrongly?) Made some assumptions:

  • Using a normal for loop on an array or collection does not create any new allocation. Use an enhanced for loop on an array (?) Or collection. It does allocate by using an enhanced for loop on an array or primitive collection. Automatically fill primitive values, eventually creating new objects at a fairly high speed

To better understand these differences, especially the fact that arrays do not have iterators, thereby enhancing the working principle of for loops, I followed the steps below.

Step 1: Enhanced-for Loop Under The Cover

Enhanced-for loops are just syntactic sugar, but when used in arrays and in collections of items, what results does it actually produce?

The answer to this can be found in the Java Language Specification.

The two main points of the above link are:

如果Expression的类型是Iterable的子类型,则转换如下。 If the type of Expression is a subtype of Iterable for some type argument X, then let I be the type java.util.Iterator; otherwise, let I be the raw type java.util.Iterator. The enhanced for statement is equivalent to a basic for statement of the form:
for (I #i = Expression.iterator(); #i.hasNext(); ) {
    {VariableModifier} TargetType Identifier =
        (TargetType) #i.next();
    Statement
}

with

Otherwise, the expression must have the array type T []. Let L1 ... Lm be the (possibly empty) sequence of labels immediately preceding the enhanced for statement. The enhanced for statement is equivalent to a basic for statement of the form:
T[] #a = Expression;
L1: L2: ... Lm:
for (int #i = 0; #i < #a.length; #i++) {
    {VariableModifier} TargetType Identifier = #a[#i];
    Statement
}

It can be seen from the above that someone does indeed use iterators for the collection's enhanced for loop. However, for arrays, the enhanced for loop is just syntactic sugar, which is equivalent to an ordinary for loop.

After understanding how the JVM actually implements the Enhanced-for loop in different use cases, our assumptions have changed:

  • Using a normal for loop to traverse the array or collection does not create any new allocation on the array using the enhanced for loop. It does not create any new allocation using the enhanced for loop collection. It does allocate on the enhanced for loop array or a collection of primitives, by accidentally autoboxing the primitive value, it ends up in a pretty high rate of new objects creation

Step 2: Defining The Test

In order to test the different scenarios I have created a very simple test which can be seen here

The test itself is very simple, the main points to note are:

  • Test create a static array and a static array list andpre-populatesthemwith100,000integers.Inthecaseofthearray, thoseareprimitives, butinthecaseofthecollectionasweuseplain integer array list thoseareactual objectsThetestexecutesthedifferentforloopexamplescenarios1,000,000timesThememoryusedisreadbeforetheiterationsstartandiscomparedthroughouttheexecution (every100invocations) oftheprograminordertodetermineifthememoryprofilehaschangedThetestscenariosinclude: AforloopoveranarrayAnenhanced-forloopoveranarrayAnenhanced-forloopoveranarray, byalsoautoboxingtheelementsAforloopoveracollectionAnenhanced-forloopoveracollectionAniteratorbasedforloopoveracollection, replicatingthebehaviourofenhanced-forloop ' ssyntacticsugar

Step 3: Running The Test

We run the test with the following settings:

  • Operating system: MacOSCatalina (10.15.3), [email protected], 8GBDDR3JDK: openjdk version "13.0.2" 2020-01-14JVM_OPTS: -Xms512M-Xmx512M-XX: + UnlockExperimentalVMOptions-XX: + UseEpsilonGC

We use EpsilonGC in order to eliminate any garbage collection and let the memory to just increase.

When running tests, certain scenarios can be easily verified according to our expectations:

  • For loops on arrays or collections will not create any allocations. Enhanced for loops on arrays will not create any allocations. An enhanced for loop array with autoboxing will indeed create new objects.

However, the remaining scenarios and the assumption that the collection's enhanced loop will allocate new iterators. Running the above test using the above JVM properties cannot prove the response on each loop. No matter the memory configuration file is stable. There are no new allocations on the heap.

The first step in the research is to ensure that the bytecode indicates that a new object has been created. The following is the bytecode, which can be used to verify whether the call to get an iterator is in progress on line 5:

  private static long forEachLoopListIterator();
    Code:
       0: lconst_0
       1: lstore_0
       2: getstatic     #5                  // Field LIST_VALUES:Ljava/util/List;
       5: invokeinterface #9,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
      10: astore_2
      11: aload_2
      12: invokeinterface #10,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
      17: ifeq          39
      20: lload_0
      21: aload_2
      22: invokeinterface #11,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      27: checkcast     #8                  // class java/lang/Integer
      30: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      33: i2l
      34: ladd
      35: lstore_0
      36: goto          11
      39: lload_0
      40: lreturn

As we are using an 一种rrayList the next step is to see what the call to #iterator() is doing. It is indeed creating a new iterator object as can be seen in 一种rrayList source code

    public Iterator<E> iterator() {
        return new Itr();
    }

In summary, the results obtained using a stable memory configuration file do not make much sense. There must be other things. It may be a test error (that is, JIT deleted some code because the return value of the block was never used). This should not happen because the return values ​​of all methods that use loops are used to make further decisions about the program, so loops must be executed.

My final thinking was the 'unlikely' scenario that the objects were been placed on the stack. It is known that Hotspot performs this kind of optimizations, by using the output of Escape Analysis.
To be honest I have never seen it happening (or at least I never had the actual time to verify it was indeed happening) until now.

Step 4: Running Without Escape Analysis

The simplest and fastest way to verify the above assumption (escape analysis has been fed into the JIT and caused the object to be allocated on the stack) is to turn off the escape analysis. This can be done by adding -XX: -DoEscapeAnalysis in our JVM options.

Indeed, running the same test again this time, we can see that the memory configuration file for the collection's enhanced for loop is growing steadily. The iterator object, allocated from ArrayList # iterator () on the heap of each loop.

Conclusion

At least for me, the above findings are interesting. In many cases, mainly due to time constraints, we are only making assumptions and following the "known to be effective" approach based on experience. Especially for those working in a delivery-oriented environment, there is not enough money for some research, I think this is normal. It is interesting to conduct some research from time to time and try to prove or better understand this.

Finally, it is worth mentioning that the above behavior was observed in the experiment, not in the actual code. I can imagine that most situations in production systems will not exhibit this behavior (ie, allocation on the stack), but the fact that JIT is such a complex software is encouraging because it can achieve no additional benefits In case of active optimization of code.

This post is also hosted on my personal blog nikoskatsanos.com

from: https://dev.to//nikos_katsanos/for-loops-allocations-and-escape-analysis-4428

Published 0 original articles · liked 0 · visits 651

Guess you like

Origin blog.csdn.net/cunxiedian8614/article/details/105691133