Java 10: Byte Code Generation for Enhanced For Loops [duplicate]
Asked Answered
E

1

9

The following example describes the generation of the following lines of code until Java 9.

List data = new ArrayList<>();for (String b : data); 

public class Test

{
  public Test() {}
  public static void main(String[] paramArrayOfString) throws IOException {
    ArrayList localArrayList = new ArrayList();
    String str;
    for (Iterator localIterator = localArrayList.iterator(); localIterator.hasNext(); str = (String)localIterator.next()) {}
}

In Java 10, iterator variables are declared outside for loops and initialized to the null value immediately once the operation is over, so GC can get rid of unused memory.

{
    Iterator iterator = data.iterator();
    for (; iterator.hasNext();) 
    {
        String b = (String)iterator.next();
    }
    b = null;
    iterator = null;
}

How is setting reference null explicitly better than reference going out of scope by the end of the for loop.

Source: https://dzone.com/articles/features-in-java-10

Also, adding link from the comments : https://bugs.openjdk.java.net/browse/JDK-8192858

Ens answered 2/4, 2018 at 9:52 Comment(11)
Where is the bytecode here?Hylophagous
You say "In Java 10" in both sections, presumably you mean "In Java <10" in one section. Also, this is obviosuly decomblied bytecode - how do you know it's not just your decompiler?Butters
"In Java 10, iterator variables are declared outside for loops and initialized to the null value immediately once the operation is over" - says who? Please provide a reference for this information. Better yet, point out where it is specified in the JLS section on enhanced for-loops: docs.oracle.com/javase/specs/jls/se10/html/…Jabberwocky
I found this: bugs.openjdk.java.net/browse/JDK-8192858Toccaratoccata
Source is: dzone.com/articles/features-in-java-10. Now I am wondering about the authenticity.Ens
@Toccaratoccata good find. that's a pretty ugly inconsistent hack, it seems. what makes enhanced for loops such a special case that it should be treated differently from any local variable going out of scope, like variables declared in a normal for-loop, block-local variables, etc. For people who program small, single-responsibility methods (like one should), this is only going to reduce performance (although slightly only).Jabberwocky
@RoshiniSingh This one has better explanation: bugs.openjdk.java.net/browse/JDK-8175883Holmquist
Thanks @ToccaratoccataEns
@Toccaratoccata thanks I edited. let me know if it looks fine.Ens
@Holmquist Thanks for pointing to better explanationEns
@ErwinBolwidt the good thing about it is that this “pretty ugly inconsistent hack” is not a thing. I’d say, the fact that no-one has noticed yet, demonstrates how “important” this fix is.Holierthanthou
T
2

Edit: There already exists a related question: Java "for" statement implementation prevents garbage collecting that provides more information.

After reading through the bug-reports (https://bugs.openjdk.java.net/browse/JDK-8192858 and https://bugs.openjdk.java.net/browse/JDK-8175883) the reason for this change can be summarised as the following:

There was an issue in the bytecode produced by javac which resulted in a reference to an array / iterable being kept after the completion of the loop.

As a result, even if an array / iterable is explicitly nullified, there is still a reference to the array / iterable which means that the array / iterable was not eligible for garbage collection until leaving the scope of the method.

With large arrays / iterables (as per the example below) this could result in an OutOfMemoryError.

This is demonstrated by this use case here (taken from the bug report):

public class IteratorInOneScope { 

    private static final int HALF_OF_MEMORY = (int) (Runtime.getRuntime().maxMemory() * 0.5); 

    public static void main(String[] args) { 
        byte[] data = new byte[HALF_OF_MEMORY]; 

        for (byte b : data); // <-- if you comment this line - the application finished successfully 
        data = null; // this expects to discard reference -> allow to release the memory 

        byte[] data2 = new byte[HALF_OF_MEMORY]; // the memory can't be allocated second time, if the "for" loop statement above is used 

        System.out.println("Success"); 
    } 
}

Which compiled to the following bytecode:

 0: getstatic #2 // Field HALF_OF_MEMORY:I 
 3: newarray byte 
 5: astore_0 <==== array ref in slot #0 

 6: aload_0 
 7: astore_1 <==== array ref in slot #1 

 8: aload_1 
 9: arraylength 
10: istore_2 

11: iconst_0 
12: istore_3 

13: iload_3 
14: iload_2 
15: if_icmpge 29 

18: aload_1 
19: iload_3 
20: baload 
21: istore 4 
23: iinc 3, 1 
26: goto 13 

29: aconst_null 
30: astore_0 <== nulls slot #0 

31: getstatic #2 // Field HALF_OF_MEMORY:I 
34: newarray byte 
36: astore_1 
37: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 
40: ldc #4 // String Success 
42: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 
45: return 

There is no update to JLS 14.14.2, for this reason:

JLS 14.14.2 is only concerned with semantics of the statement, not garbage collection behavior. Compilers are free to generate whatever bytecode they want that produces the specified behavior. So, if javac wants to set some unused locals to null, it's free to do so. No spec change necessary, and it would be a mistake to include it in the spec, because it doesn't impact the statement's semantics.

Toccaratoccata answered 2/4, 2018 at 10:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.