Search code examples
javajvmescape-analysis

Escape analysis doubts


I thought I'd do a little experiment with escape analysis (Java 8, 64-bit server JVM). I came up with this really stupid "application", where I create a lot of Address objects (they consist of a zip code, street, country and a timestamp the object was generated. Also, Address has an isOk() method, that returns true if the timestamp is divisible with 7...).

So here's the program:

private boolean generate() {
    boolean valid = true;
    for (int i=0;i<1_000_000_000;i++) {
        valid = valid && doGenerate();
    }

    return valid;
}

private boolean doGenerate() {
    long timeGenerated = System.currentTimeMillis();
    Address address = new Address(1021, "A Street", "A country", timeGenerated);
    return address.isOk();
}

So far so good, I profiled it with jVisualVM, there are no Address objects on the heap while it's running. The whole application completes in a few seconds.

However, when I refactor it like this:

private boolean generate() {
    boolean valid = true;
    for (int i=0;i<1_000_000_000;i++) {
        long timeGenerated = System.currentTimeMillis();
        Address address = new Address(1021, "A Street", "A country", timeGenerated);
        valid = valid && address.isOk();
    }

    return valid;
}

Baaang, there's no escape analysis, every Address object ends up allocated on the heap with heavy garbage collection cycles. Why is it so? I mean, the Address instances do not escape either way (in the second version the Address objects' scope is even narrower, they don't escape the method, not even the for loop block), so why the two versions behave so differently?


Solution

  • You write “that returns true if the timestamp is divisible with 7”. That should make obvious, what happens. In your first code:

    boolean valid = true;
    for (int i=0;i<1_000_000_000;i++) {
        valid = valid && doGenerate();
    }
    return valid;
    

    valid will become false as soon as the timestamp happens not to be divisible by 7. Then, according to the way && works, it will remain false forever and since && is short-circuiting, the method doGenerate(), which bears the allocation, will never get called again.

    In contrast, in your second variant

    boolean valid = true;
    for (int i=0;i<1_000_000_000;i++) {
        long timeGenerated = System.currentTimeMillis();
        Address address = new Address(1021, "A Street", "A country", timeGenerated);
        valid = valid && address.isOk();
    }
    return valid;
    

    valid will also become and remain false as soon as the timestamp happens not to be divisible by 7, but the only thing which is short-circuited, is the invocation of isOk(). The construction happens, regardless of the value of valid.

    In principle, the construction of Address could get eliminated here, but that would require on-stack replacement as it has to happen while your loop runs. It’s not clear whether that’s the issue here, but the more important conclusion is, that in neither case, we see EA happen, as in the first case, you are not invoking the method containing the allocation (after an unknown, but expectedly small number of invocations) at all.

    So the two examples are not equivalent and not allowing to draw conclusions about Escape Analysis.