Issue
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
?
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:
- For example, the same class names can be retrieved by a Java program (not through JShell).
- The question does not seem to be about JShell, but about the Java language and the Java Virtual Machine.
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 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.
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>
whereN
is the binary name indicated by theclass
file passed toLookup::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:
- The below execution paths are theoretical: I am using the mentioned source code tag.
- The below call stacks are real: I am using OpenJDK
18.0.1-ea+10-Debian-1
.
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) {
Answered By - Sergey Vyacheslavovich Brunov
Answer Checked By - Willingham (JavaFixing Volunteer)