4

When finalizeOperation is running (in production in a larger application):

public interface OperationFinalizerHook {
    void onOperationFinalize(Operation operation, Object context);
}
private final List<OperationFinalizerHook> operationFinalizeHooks = new ArrayList<>();
...
public void finalizeOperation(Object context) {
    final Operation operation = getOperation();
    operationFinalizeHooks.forEach(hook -> hook.onOperationFinalize(operation, context));
}

following call tree/stacktrace is built:

11 at com.company.SomeClass.lambda$finalizeOperation$0 (SomeClass.java:51)
12 at com.company.SomeClass$$Lambda$135/2085968933.accept (Unknown source)
13 at java.util.ArrayList.forEach (ArrayList.java:1249)
14 at com.company.SomeClass.finalizeOperation (SomeClass.java:51)

I'm interested in line 12 - where does this name come from? Why are there random numbers where I would expect the name of a class?

Edit: Here's the code from blog post mentioned by Niklas P:

public class Test {
    public static void main(String... args) {
        List<String> names = Arrays.asList("adam", "");
        Stream lengths = names.stream().map(name -> check(name));
        lengths.count();
    }
    public static int check(String s) {
        if (s.equals(""))
            throw new IllegalArgumentException();
        return s.length();
    }
}

But the result does not contain this numeric name, the stacktrace is (jdk8u102):

Exception in thread "main" java.lang.IllegalArgumentException
    at Test.check(Test.java:19)
    at Test.lambda$main$0(Test.java:12)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at Test.main(Test.java:14)

On jdk8u25 the number is there:

Exception in thread "main" java.lang.IllegalArgumentException
    at Test.check(Test.java:18)
    at Test.lambda$main$0(Test.java:11)
    at Test$$Lambda$1/1554547125.apply(Unknown Source)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.LongPipeline.reduce(LongPipeline.java:438)
    at java.util.stream.LongPipeline.sum(LongPipeline.java:396)
    at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)
    at Test.main(Test.java:13)
Holger
  • 285,553
  • 42
  • 434
  • 765
mabn
  • 2,473
  • 1
  • 26
  • 47

2 Answers2

5

There are two overlapping issues here. First, when you convert a lambda expression to an object type, there has to be something that implements the functional interface—the details are not so important; the only thing you have to understand that there will be something implementing the interface and invoking the code of your lambda expression or the target of a method reference.

The current JRE implementation generates anonymous classes, which, as the name suggests, do not depend on their name for being unique. The number printed after the class name is an artifact of this property. Either way, with number or without, you can’t lookup these classes with a ClassLoader.

Having synthetic artifacts in a stack trace is nothing new to Java. There are generated accessor methods when using inner classes, e.g.

import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;

public class Test {
    public static void main(String... args) {
        List<String> names = Arrays.asList("adam", "");
        Stream lengths = names.stream().map(new Function<String, Integer>() {
            public Integer apply(String name) {
                return check(name);
            }
        });
        lengths.count();
    }
    private static int check(String s) {
        if (s.equals(""))
            throw new IllegalArgumentException();
        return s.length();
    }
}
Exception in thread "main" java.lang.IllegalArgumentException
    at Test.check(Test.java:17)
    at Test.access$000(Test.java:5)
    at Test$1.apply(Test.java:10)
    at Test$1.apply(Test.java:8)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    … (shortened it a bit)
    at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)
    at Test.main(Test.java:13)

Note the presence of access$000 in the stack trace, which doesn’t appear in source code; it’s associated line number is meaningless, it’s just the beginning of the outer class definition.

Now, it seems that there was a change in the stack trace generation, to omit synthetic members of anonymous classes in recent JRE versions. This will also affect the stack traces of reflective invocations, e.g. using MethodHandle instances. This might be considered useful for most use cases, but it also implies that there might be a mismatch between caller and callee in some cases, as the stack trace reports that the caller invokes an interface method, but ends up somewhere else, e.g.

import java.util.*;
import java.util.stream.Stream;

public class Test {
    public static void main(String... args) {
        Stream.of("adam", "", null).filter("foo"::contains).count();
    }
}

will print

Exception in thread "main" java.lang.NullPointerException
    at java.lang.String.contains(String.java:2133)
    at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
…

where ReferencePipeline.java:174 contains the invocation of the accept method of the Predicate interface, but ends up in the method contains of class String. We can max that out:

import java.util.*;
import java.util.stream.Stream;

public class Test {
    public static void main(String... args) {
        Stream.of("adam", "", null).filter(String::isEmpty).count();
    }
}

will produce the following on the most recent JREs:

Exception in thread "main" java.lang.NullPointerException
    at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.LongPipeline.reduce(LongPipeline.java:438)
    at java.util.stream.LongPipeline.sum(LongPipeline.java:396)
    at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)
    at Test.main(Test.java:6)

omitting the synthetic code which will eventually invoke isEmpty on the String instance, which can be even more confusing as ReferencePipeline.java:174 only contains an invocation of an interface method and the interface instance is not null (this has been checked much earlier in that code).

Note that this is a development in motion. With Java 9, there will be the StackWalker API which will allow applications to generate their own snapshots with a configured treatment of hidden/reflection stack frames. Once applications use this API to create predictable stack traces, i.e. do not rely on a specific behavior of Throwable.getStackTrace() anymore, the behavior of throwables could become configurable via JVM options or system properties…

Holger
  • 285,553
  • 42
  • 434
  • 765
1

The numbers are coming from anonymous classes, the JVM created for the lambda operations - see here: the-dark-side-of-lambda-expressions-in-java-8

Holger
  • 285,553
  • 42
  • 434
  • 765
Niklas P
  • 3,427
  • 2
  • 15
  • 19
  • Does that link say anything about how the number correlates to a certain inner class? Aka what this number actually represents? – Jeroen Vannevel Oct 04 '16 at 22:01
  • not really - I found something here: (http://www.retrologic.com/innerclasses.doc7.html) – Niklas P Oct 04 '16 at 22:12
  • From a quick scan I didn't see anything in there either on what generates that number (which is the question at hand). Your post should really answer that question. – Jeroen Vannevel Oct 04 '16 at 22:14
  • It's a nice blog post, but I've run the code mentioned there and it does not produce this number (even though they indicate that it does). I'll add the code to the question. – mabn Oct 04 '16 at 22:23
  • I tried again with older JVM version and in fact on 8u25 and 8u45 there is that number ("Test$$Lambda$1/1554547125.apply(Unknown Source)") but on u65 and u102 it's no longer like that, instead it's "Test.lambda$main$0(Test.java:11)" – mabn Oct 04 '16 at 22:33
  • 2
    @JeroenVannevel as little as possible by design. The JVM designers want to option to change it later without people complaining they depend on the naming doing a certain thing. Not so great for debugging though. – Peter Lawrey Oct 05 '16 at 08:05
  • 2
    @Jeroen Vannevel: anonymous classes are not to be confused with anonymous inner classes (or inner classes in general). [This blog post](https://blogs.oracle.com/jrose/entry/anonymous_classes_in_the_vm) describes the ideas behind it. Note that some technical details are outdated, but the general purpose still holds. See the number as memory address or hash code—it doesn’t matter. – Holger Oct 05 '16 at 09:52
  • @Holger that's what I figured -- I just wanted that added to the answer because as it is now, it's poor in quality. @.Peter makes sense – Jeroen Vannevel Oct 05 '16 at 10:18