Is there a way to print Escape Analysis?

275 views
Skip to first unread message

Carl Mastrangelo

unread,
Jan 24, 2018, 9:44:44 PM1/24/18
to mechanical-sympathy
Consider the following code:

public class Test {
  static volatile Integer discard;
  public static void main(String [] args) throws InterruptedException {
    printMemory();
    System.gc();
    printMemory();
    int iterations = 1000;
    int[] vals = new int[100_000_000];
    while (args.length == 0) {
      printMemory();
      System.gc();
      Thread.sleep(200);
      discard = iterations++;
    }
  }

  private static void printMemory() {
    System.out.println(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
  }
}

I am surprised to see the memory used by this code starts at about 200MB, goes up to 600MB, but never seems to go back down.  The large int array accounts for the memory usage jump, but it never seems to be garbage collected.  Why?   The variable is never read after it is allocated.  It cannot be reordered by the compiler to be after the while loop, because I can see the memory jump.  I am intentionally allocating memory in the loop by boxing the integer.  Enabling +PrintCompilation and PrintInlining never shows main() being inlined; perhaps it is not being compiled?

Henri Tremblay

unread,
Jan 25, 2018, 12:14:41 AM1/25/18
to mechanica...@googlegroups.com
From as far as I know, the variable is still in scope, even if unused.

The JVM doesn't optimize reachability to help the GC. Again, from as far as I know.

main can't be inlined, it's main, the top of the class stack

It won't be compiled because it is executed only once (and never finish). So no compilation.
-
Henri

--
You received this message because you are subscribed to the Google Groups "mechanical-sympathy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mechanical-sympathy+unsub...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Kirk Pepperdine

unread,
Jan 25, 2018, 12:51:54 AM1/25/18
to mechanica...@googlegroups.com
Where would main be inlined to? And why? It’s only called once. Also, vals pointer is still on the stack so it is live so no chance for it to be collected. 

From my run…

9421512
[GC (System.gc())  9200K->1314K(251392K), 0.0025157 secs]
[Full GC (System.gc())  1314K->1160K(251392K), 0.0080973 secs]
1188704
405215328
[GC (System.gc())  395718K->392017K(642048K), 0.0040519 secs]
[Full GC (System.gc())  392017K->391536K(642048K), 0.0641594 secs]
402275264
[GC (System.gc())  394157K->391568K(642048K), 0.0048252 secs]
[Full GC (System.gc())  391568K->391501K(642048K), 0.1082497 secs]
400897776
[GC (System.gc())  392812K->391533K(642048K), 0.0053044 secs]
[Full GC (System.gc())  391533K->391501K(642048K), 0.0086243 secs]
400897776
[GC (System.gc())  392812K->391533K(642048K), 0.0040812 secs]
[Full GC (System.gc())  391533K->391501K(642048K), 0.0091088 secs]
400897776
[GC (System.gc())  392812K->391533K(642048K), 0.0037178 secs]
[Full GC (System.gc())  391533K->391501K(642048K), 0.0067113 secs]
400897776
[GC (System.gc())  392812K->391533K(642048K), 0.0036010 secs]
[Full GC (System.gc())  391533K->391501K(642048K), 0.0076227 secs]

Looks pretty much expected from my POV.

Kind regards,
Kirk



--
You received this message because you are subscribed to the Google Groups "mechanical-sympathy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mechanical-symp...@googlegroups.com.

Chris Newland

unread,
Jan 25, 2018, 2:28:51 AM1/25/18
to mechanical-sympathy
Hi Carl,

As Henri and Kirk have mentioned, there is nothing for the main() method to be inlined into since it's at the top of the execution stack.

I ran your code with these logging switches:
java -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -Xmx1g -cp classes Test

and let it run for several minutes to let the JIT optimise all it can.

Before running the code I expected that the while loop would be OSR-compiled (On-Stack Replacement) but on my machine (low powered Linux laptop) only your printMemory() method was compiled by the C1 compiler.

If an OSR compilation of the while loop was to occur then here is what I would expect (very oversimplified explanation, writing this on the train!)

0) Interpreter starts executing the main method.
1) The int[] is allocated and is in scope during the interpreter stack frame for the main method.
2) The JIT compiler detects a hot loop and decides to compile the loop body. This involves making variables that are in-scope during the interpreter frame available to the native code stack frame.
3) Execution switches to the compiled version of the while loop.
4) If/when the native code version of the loop exits, control passes back to the interpreter and the variables of the main method stack frame are in-scope again (including your int[]).

Escape analysis isn't able to help you here I'm afraid.

Cheers,

Chris
@chriswhocodes

Aleksey Shipilev

unread,
Jan 25, 2018, 3:13:37 AM1/25/18
to mechanica...@googlegroups.com
This is not escape analysis. It is more about compiler able to figure out the reachability of local
variable, and let GC act:
https://shipilev.net/jvm-anatomy-park/8-local-var-reachability/

It is predicated on the condition that method is actually compiled and optimized accordingly. OSR
would not cut it here, I think, because the OSR version of the method would still treat that
variable alive.

$ java Test
10548688
260648
410809368
410673344
400260544
400260544
400260544
400260544
400260544
400260544

It changes if you compile the method before entering it:

$ java -Xcomp Test
73841024
296240
421393656
10580272
10580608
10580608
10580608
10580608

Thanks,
-Aleksey

signature.asc

Henri Tremblay

unread,
Jan 25, 2018, 11:21:50 AM1/25/18
to mechanica...@googlegroups.com
Thanks Aleksey. I know a bit farther now.

--
You received this message because you are subscribed to the Google Groups "mechanical-sympathy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mechanical-sympathy+unsub...@googlegroups.com.

Carl Mastrangelo

unread,
Jan 25, 2018, 2:48:59 PM1/25/18
to mechanica...@googlegroups.com
That's pretty cool.    I'm curious: Does -Xcomp work because it can show the variable doesn't escape, or because of deadcode elimination?   For example, when implementing an InputStream, one way to implement read would be:

    @Override
    public int read() {
      byte[] onebyte = new byte[1];
      int ret = read(onebyte, 0, 1);
      if (ret == -1) {
        return -1;
      }
      return 0xFF & onebyte[0];
    }

How can I tell that the compiler knows onebyte array doesn't escape? 




--
You received this message because you are subscribed to a topic in the Google Groups "mechanical-sympathy" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/mechanical-sympathy/hj5VaYIoNiE/unsubscribe.
To unsubscribe from this group and all its topics, send an email to mechanical-sympathy+unsub...@googlegroups.com.

Martin Thompson

unread,
Jan 25, 2018, 2:59:58 PM1/25/18
to mechanical-sympathy


On Thursday, 25 January 2018 19:48:59 UTC, Carl Mastrangelo wrote:
That's pretty cool.    I'm curious: Does -Xcomp work because it can show the variable doesn't escape, or because of deadcode elimination?   For example, when implementing an InputStream, one way to implement read would be:

    @Override
    public int read() {
      byte[] onebyte = new byte[1];
      int ret = read(onebyte, 0, 1);
      if (ret == -1) {
        return -1;
      }
      return 0xFF & onebyte[0];
    }

How can I tell that the compiler knows onebyte array doesn't escape? 

The issue in the previous example is not about EA (Escape Analysis).  On Stack Replacement (OSR) of the loop may not have included the context for the array reference outside the loop. With Xcomp the whole method is compiled, not just the loop. You have to consider the context of the compilation unit which can include inlined code. 

Chris Newland

unread,
Jan 25, 2018, 5:49:01 PM1/25/18
to mechanical-sympathy
Hi Carl,

HotSpot's LogCompilation output (enable with -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation) does contain information about eliminated allocations (and also elided locks) from EA but it's tricky to read by eye.

JITWatch (https://github.com/AdoptOpenJDK/jitwatch) is a free open-source tool (disclaimer: I'm the author) that can highlight heap allocations which were avoided due to the JIT's EA. I made a video on how to use this feature: https://www.youtube.com/watch?v=LK1Ain1JDlQ

Kris Mok and Vladimir Ivanov (HotSpot experts) gave some great info on how OSR compilation works including variable scope: https://github.com/AdoptOpenJDK/jitwatch/wiki/Understanding-the-On-Stack-Replacement-(OSR)-optimisation-in-the-HotSpot-C1-compiler

In your example below, why do you think the array 'onebyte' doesn't escape? I can see it passed as a parameter to method read(byte[], int, int). If that read method is not inlined (inlining is attempted before EA is applied) then this would be classed as an ArgEscape (object escapes by passing it as an argument to another method) and could not benefit from avoiding the heap allocation.

Cheers,

Chris
@chriswhocodes


On Thursday, 25 January 2018 19:48:59 UTC, Carl Mastrangelo wrote:

Carl Mastrangelo

unread,
Jan 25, 2018, 6:22:35 PM1/25/18
to mechanica...@googlegroups.com
Interesting tool, I'll try it on some of my code.    

I think that onebyte doesn't escape because I also am the implementer of the other read() overloads.   I was pointing out that implementing the single byte read could be implemented in terms of the multi byte read.   However, it would be unpleasant if it actually allocated memory to do the read.   If onebyte can be allocated on the stack, how do I know it actually is?  (or better yet, not allocated at all)

To unsubscribe from this group and all its topics, send an email to mechanical-sympathy+unsubscribe...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Chris Newland

unread,
Jan 26, 2018, 1:39:06 PM1/26/18
to mechanical-sympathy
Hi Carl,

A "product" JVM will tell you when EA prevented a heap allocation if you enable LogCompilation with -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation.

The output looks something like:

<eliminate_allocation type='878'>
    <jvms method='871' bci='47'/>
</eliminate_allocation>

You can chase those references manually through the log or use JITWatch ;)

Note that an avoided heap allocation is not the same as a stack allocation in other languages. HotSpot has a component called the register allocator which will decide best where to put the fields. Preferably in CPU registers or otherwise onto the stack (known as a stack spill)*

* = VM experts are cringing right now at this explanation ;)

If LogCompilation doesn't contain those eliminate_allocation tags for your allocation then assume it was heap allocated.

You can also look at the GC logs with and without the switch -XX:-DoEscapeAnalysis if you want to see the effect of disabling the EA phase.

If you want a lot more detail on the EA process then you can build a debug VM and use the switches:

-XX:+PrintEscapeAnalysis
-XX:+PrintEliminateAllocations

Fun fact: HotSpot won't eliminate allocations for arrays of more than 64 elements. Control this with -XX:EliminateAllocationArraySizeLimit=n

Cheers

Chris
@chriswhocodes

To unsubscribe from this group and all its topics, send an email to mechanical-sympathy+unsub...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages