javajvmjshell

In Java, what does the / (i.e., forward slash) mean in object references like $Lambda$15/0x00000008000a9440@32e6e9c3)?


In JShell, if I do this:

interface Foo { String foo(); }
(Foo) () -> "hi"

I get

|  created interface Foo
$2 ==> $Lambda$15/0x00000008000a9440@32e6e9c3

From the research below, I know the following:

$Lambda = an in-memory reference, as opposed to one persisted to disk by an anonymous inner class (AIC), to the generated bytecode

$15 = an object reference to the AIC

@32e6e9c3 = the sequential number of the object created--at least, in IntelliJ

But what does the / (slash) indicate, as in /0x00000008000a9440?


Solution

  • Summary

    $Lambda$15/0x00000008000a9440 is the name of the created hidden class.

    As it will be shown below, 0x00000008000a9440 is called a suffix.

    The name of the class can be retrieved by calling the java.lang.Class.getName() method. Therefore:

    Example program to show name of hidden class

    Program class

    package info.brunov.stackoverflow.question72804142;
    
    import java.util.function.Supplier;
    
    public final class Program {
        public static void main(final String args[]) {
            printRuntimeInformation();
    
            final Supplier<String> supplier1 = () -> "";
            final Supplier<String> supplier2 = () -> "";
            final Supplier<String> supplier3 = () -> "";
            System.out.println(
                String.format("Supplier 1: %s", supplier1.getClass().getName())
            );
            System.out.println(
                String.format("Supplier 2: %s", supplier2.getClass().getName())
            );
            System.out.println(
                String.format("Supplier 3: %s", supplier3.getClass().getName())
            );
        }
    
        private static void printRuntimeInformation() {
            System.out.println(
                String.format(
                    "Java Virtual Machine specification name: %s",
                    System.getProperty("java.vm.specification.name")
                )
            );
            System.out.println(
                String.format(
                    "Java Virtual Machine specification version: %s",
                    System.getProperty("java.vm.specification.version")
                )
            );
            System.out.println(
                String.format(
                    "Java Virtual Machine specification vendor: %s",
                    System.getProperty("java.vm.specification.vendor")
                )
            );
            System.out.println(
                String.format(
                    "Java Virtual Machine implementation name: %s",
                    System.getProperty("java.vm.name")
                )
            );
            System.out.println(
                String.format(
                    "Java Virtual Machine implementation version: %s",
                    System.getProperty("java.vm.version")
                )
            );
            System.out.println(
                String.format(
                    "Java Virtual Machine implementation vendor: %s",
                    System.getProperty("java.vm.vendor")
                )
            );
        }
    }
    

    Program output

    Java Virtual Machine specification name: Java Virtual Machine Specification
    Java Virtual Machine specification version: 18
    Java Virtual Machine specification vendor: Oracle Corporation
    Java Virtual Machine implementation name: OpenJDK 64-Bit Server VM
    Java Virtual Machine implementation version: 18.0.1-ea+10-Debian-1
    Java Virtual Machine implementation vendor: Debian
    Supplier 1: info.brunov.stackoverflow.question72804142.Program$$Lambda$18/0x0000000800c031f0
    Supplier 2: info.brunov.stackoverflow.question72804142.Program$$Lambda$19/0x0000000800c033f8
    Supplier 3: info.brunov.stackoverflow.question72804142.Program$$Lambda$20/0x0000000800c03600
    

    Documentation references

    JEP 371: Hidden Classes

    The hidden classes have been introduced since JDK 15. For additional details, please, refer to the JEP: JEP 371: Hidden Classes.

    Here is an excerpt from the JEP on the hidden class names:

    The major difference in how a hidden class is created lies in the name it is given. A hidden class is not anonymous. It has a name that is available via Class::getName and may be shown in diagnostics (such as the output of java -verbose:class), in JVM TI class loading events, in JFR events, and in stack traces. However, the name has a sufficiently unusual form that it effectively makes the class invisible to all other classes. The name is the concatenation of:

    1. The binary name in internal form (JVMS 4.2.1) specified by this_class in the ClassFile structure, say A/B/C;
    2. The '.' character; and
    3. An unqualified name (JVMS 4.2.2) that is chosen by the JVM implementation.

    For example, if this_class specifies com/example/Foo (the internal form of the binary name com.example.Foo), then a hidden class derived from the ClassFile structure may be named com/example/Foo.1234. This string is neither a binary name nor the internal form of a binary name.

    Given a hidden class whose name is A/B/C.x, the result of Class::getName is the concatenation of:

    1. The binary name A.B.C (obtained by taking A/B/C and replacing each '/' with '.');
    2. The '/' character; and
    3. The unqualified name x.

    For example, if a hidden class is named com/example/Foo.1234, then the result of Class::getName is com.example.Foo/1234. Again, this string is neither a binary name nor the internal form of a binary name.

    The namespace of hidden classes is disjoint from the namespace of normal classes. Given a ClassFile structure where this_class specifies com/example/Foo/1234, invoking cl.defineClass("com.example.Foo.1234", bytes, ...) merely results in a normal class named com.example.Foo.1234, distinct from the hidden class named com.example.Foo/1234. It is impossible to create a normal class named com.example.Foo/1234 because cl.defineClass("com.example.Foo/1234", bytes, ...) will reject the string argument as being not a binary name.

    Javadoc: java.lang.Class#getName() method

    Let's refer to the method documentation: Class (Java SE 15 & JDK 15).

    An excerpt from the documentation:

    public String getName()

    Returns the name of the entity (class, interface, array class, primitive type, or void) represented by this Class object.

    If this Class object represents a class or interface, not an array class, then:

    • If the class or interface is not hidden, then the binary name of the class or interface is returned.
    • If the class or interface is hidden, then the result is a string of the form: N + '/' + <suffix> where N is the binary name indicated by the class file passed to Lookup::defineHiddenClass, and <suffix> is an unqualified name.

    Implementation details: OpenJDK Java Virtual Machine: Hidden class name

    Introduction

    Let's consider the source code of OpenJDK 18.

    Let's refer to the tag: openjdk/jdk18 at jdk-18+37.

    Please, note that:

    Hidden class name mangling

    Hidden class creation (the java.lang.invoke.MethodHandles.Lookup.defineHiddenClass() method) includes the mangling of its name.

    Let's consider the following call stack:

    "main@1" prio=5 tid=0x1 nid=NA runnable
      java.lang.Thread.State: RUNNABLE
          at java.lang.System$2.defineClass(System.java:2346)
          at java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2432)
          at java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClassAsLookup(MethodHandles.java:2413)
          at java.lang.invoke.MethodHandles$Lookup.defineHiddenClass(MethodHandles.java:2119)
          at java.lang.invoke.InnerClassLambdaMetafactory.generateInnerClass(InnerClassLambdaMetafactory.java:385)
          at java.lang.invoke.InnerClassLambdaMetafactory.spinInnerClass(InnerClassLambdaMetafactory.java:293)
          at java.lang.invoke.InnerClassLambdaMetafactory.buildCallSite(InnerClassLambdaMetafactory.java:228)
          at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:341)
          at java.lang.invoke.DirectMethodHandle$Holder.invokeStatic(DirectMethodHandle$Holder:-1)
          at java.lang.invoke.Invokers$Holder.invokeExact_MT(Invokers$Holder:-1)
          at java.lang.invoke.BootstrapMethodInvoker.invoke(BootstrapMethodInvoker.java:134)
          at java.lang.invoke.CallSite.makeSite(CallSite.java:315)
          at java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:279)
          at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:269)
          at info.brunov.stackoverflow.question72804142.Program.main(Program.java:9)
    

    Then let's consider the following execution path as the continuation of the call stack:

    Class<?> java.lang.ClassLoader#defineClass0(ClassLoader loader, Class<?> lookup, String name, byte[] b, int off, int len, ProtectionDomain pd, boolean initialize, int flags, Object classData)
    
    // Native calls below.
    jclass Unsafe_DefineClass0(JNIEnv *env, jobject unsafe, jstring name, jbyteArray data, int offset, int length, jobject loader, jobject pd)
    jclass Unsafe_DefineClass_impl(JNIEnv *env, jstring name, jbyteArray data, int offset, int length, jobject loader, jobject pd)
    JNIEXPORT jclass JNICALL
    jclass JVM_DefineClass(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd)
    jclass jvm_define_class_common(const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source, TRAPS)
    InstanceKlass* SystemDictionary::resolve_from_stream(ClassFileStream* st, Symbol* class_name, Handle class_loader, const ClassLoadInfo& cl_info, TRAPS)
    InstanceKlass* SystemDictionary::resolve_hidden_class_from_stream(ClassFileStream* st, Symbol* class_name, Handle class_loader, const ClassLoadInfo& cl_info, TRAPS)
    InstanceKlass* KlassFactory::create_from_stream(ClassFileStream* stream, Symbol* name, ClassLoaderData* loader_data, const ClassLoadInfo& cl_info, TRAPS)
    InstanceKlass* ClassFileParser::create_instance_klass(bool changed_by_loadhook, const ClassInstanceInfo& cl_inst_info, TRAPS)
    void ClassFileParser::mangle_hidden_class_name(InstanceKlass* const ik)
    

    Let's refer to the piece of source code: jdk18/classFileParser.cpp at jdk-18+37 · openjdk/jdk18:

    void ClassFileParser::mangle_hidden_class_name(InstanceKlass* const ik) {
      ResourceMark rm;
      // Construct hidden name from _class_name, "+", and &ik. Note that we can't
      // use a '/' because that confuses finding the class's package.  Also, can't
      // use an illegal char such as ';' because that causes serialization issues
      // and issues with hidden classes that create their own hidden classes.
      char addr_buf[20];
      if (DumpSharedSpaces) {
        // We want stable names for the archived hidden classes (only for static
        // archive for now). Spaces under default_SharedBaseAddress() will be
        // occupied by the archive at run time, so we know that no dynamically
        // loaded InstanceKlass will be placed under there.
        static volatile size_t counter = 0;
        Atomic::cmpxchg(&counter, (size_t)0, Arguments::default_SharedBaseAddress()); // initialize it
        size_t new_id = Atomic::add(&counter, (size_t)1);
        jio_snprintf(addr_buf, 20, SIZE_FORMAT_HEX, new_id);
      } else {
        jio_snprintf(addr_buf, 20, INTPTR_FORMAT, p2i(ik));
      }
    

    Please, note that the + character is used as the separator.

    Get hidden class name

    The java.lang.Class#getName() method includes the character replacement: + is replaced with /.

    Let's consider the following execution path:

    String java.lang.Class.getName()
    String java.lang.Class.initClassName()
    
    // Native calls below.
    JNIEXPORT jstring JNICALL JVM_InitClassName(JNIEnv *env, jclass cls)
    oop java_lang_Class::name(Handle java_class, TRAPS)
    const char* java_lang_Class::as_external_name(oop java_class)
    const char* Klass::external_name() const
    static char* convert_hidden_name_to_java(Symbol* name)
    

    Let's refer to the piece of source code: jdk18/klass.cpp at jdk-18+37 · openjdk/jdk18:

    // Replace the last '+' char with '/'.
    static char* convert_hidden_name_to_java(Symbol* name) {