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:
$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
?
$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:
Program
classpackage 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")
)
);
}
}
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
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 ofjava -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:
- The binary name in internal form (JVMS 4.2.1) specified by
this_class
in theClassFile
structure, sayA/B/C
;- The
'.'
character; and- An unqualified name (JVMS 4.2.2) that is chosen by the JVM implementation.
For example, if
this_class
specifiescom/example/Foo
(the internal form of the binary namecom.example.Foo
), then a hidden class derived from theClassFile
structure may be namedcom/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 ofClass::getName
is the concatenation of:
- The binary name
A.B.C
(obtained by takingA/B/C
and replacing each'/'
with'.'
);- The '/' character; and
- The unqualified name
x
.For example, if a hidden class is named
com/example/Foo.1234
, then the result ofClass::getName
iscom.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 wherethis_class
specifiescom/example/Foo/1234
, invokingcl.defineClass("com.example.Foo.1234", bytes, ...)
merely results in a normal class namedcom.example.Foo.1234
, distinct from the hidden class namedcom.example.Foo/1234
. It is impossible to create a normal class namedcom.example.Foo/1234
becausecl.defineClass("com.example.Foo/1234", bytes, ...)
will reject the string argument as being not a binary name.
java.lang.Class#getName()
methodLet'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>
whereN
is the binary name indicated by theclass
file passed toLookup::defineHiddenClass
, and<suffix>
is an unqualified name.
Let's consider the source code of OpenJDK 18.
Let's refer to the tag: openjdk/jdk18 at jdk-18+37.
Please, note that:
18.0.1-ea+10-Debian-1
.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.
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) {