Summary
$Lambda$15/0x00000008000a9440
是创建的隐藏类的名称。
如下所示,0x00000008000a9440
称为后缀。
可以通过调用来检索类的名称java.lang.Class.getName()
方法。
所以:
- 例如,相同的类名可以通过 Java 程序(而不是通过 JShell)检索。
- 问题似乎不是关于JShell,而是关于Java语言和Java虚拟机。
显示隐藏类名称的示例程序
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")
)
);
}
}
程序输出
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
文档参考
JEP 371:隐藏类
隐藏类是从 JDK 15 开始引入的。
有关更多详细信息,请参阅 JEP:JEP 371:隐藏类 https://openjdk.org/jeps/371.
以下是 JEP 中有关隐藏类名称的摘录:
创建隐藏类的主要区别在于它所指定的名称。隐藏类不是匿名的。它有一个可通过以下方式获得的名称Class::getName
并且可能会显示在诊断中(例如输出java -verbose:class
)、JVM TI 类加载事件、JFR 事件和堆栈跟踪中。然而,该名称的形式非常不寻常,它实际上使该类对所有其他类不可见。该名称是以下内容的串联:
- 内部形式 (JVMS 4.2.1) 指定的二进制名称
this_class
in the ClassFile
结构,说A/B/C
;
- The
'.'
特点;和
- 由 JVM 实现选择的非限定名称 (JVMS 4.2.2)。
例如,如果this_class
指定com/example/Foo
(二进制名称的内部形式com.example.Foo
),然后派生出一个隐藏类ClassFile
结构可以命名为com/example/Foo.1234
。该字符串既不是二进制名称,也不是二进制名称的内部形式。
给定一个隐藏类,其名称为A/B/C.x
, 的结果Class::getName
是以下的串联:
- 二进制名称
A.B.C
(通过采取A/B/C
并替换每个'/'
with '.'
);
- 人物;和
- 不合格名称
x
.
例如,如果隐藏类被命名为com/example/Foo.1234
,那么结果是Class::getName
is com.example.Foo/1234
。同样,该字符串既不是二进制名称,也不是二进制名称的内部形式。
隐藏类的命名空间与普通类的命名空间不相交。给定一个ClassFile
结构其中this_class
指定com/example/Foo/1234
,调用cl.defineClass("com.example.Foo.1234", bytes, ...)
仅仅产生一个名为的普通类com.example.Foo.1234
,与名为的隐藏类不同com.example.Foo/1234
。不可能创建一个名为的普通类com.example.Foo/1234
因为cl.defineClass("com.example.Foo/1234", bytes, ...)
将拒绝字符串参数,因为它不是二进制名称。
Java文档:java.lang.Class#getName()
method
我们参考方法文档:类(Java SE 15 和 JDK 15) https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/Class.html#getName().
文档摘录:
public String https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/String.html获取名称()
返回由此表示的实体(类、接口、数组类、基本类型或 void)的名称Class
object.
If this Class
object 表示类或接口,而不是数组类,那么:
- 如果类或接口不是hidden https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/Class.html#isHidden(),那么二进制名称 https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/ClassLoader.html#binary-name返回类或接口的类型。
- 如果类或接口被隐藏,则结果是以下形式的字符串:
N + '/' + <suffix>
where N
is the 二进制名称 https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/ClassLoader.html#binary-name表示由class
文件传递到Lookup::defineHiddenClass https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/invoke/MethodHandles.Lookup.html#defineHiddenClass(byte%5B%5D,boolean,java.lang.invoke.MethodHandles.Lookup.ClassOption...), and <suffix>
是一个不合格的名称。
实现细节:OpenJDK Java 虚拟机:隐藏类名
介绍
让我们考虑一下 OpenJDK 18 的源代码。
我们参考一下标签:openjdk/jdk18 at jdk-18+37 https://github.com/openjdk/jdk18/tree/jdk-18%2B37.
请注意:
- 以下执行路径是理论上的:我正在使用提到的源代码标签。
- 下面的调用堆栈是真实的:我正在使用 OpenJDK
18.0.1-ea+10-Debian-1
.
隐藏类名修改
隐藏类的创建(java.lang.invoke.MethodHandles.Lookup.defineHiddenClass()
方法)包括对其名称的修改。
让我们考虑以下调用堆栈:
"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)
然后我们考虑以下执行路径调用堆栈的延续:
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)
我们参考一下源码:jdk18/classFileParser.cpp 位于 jdk-18+37 · openjdk/jdk18 https://github.com/openjdk/jdk18/blob/jdk-18+37/src/hotspot/share/classfile/classFileParser.cpp#L5920-L5938:
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));
}
请注意,+
字符用作分隔符。
获取隐藏类名
The java.lang.Class#getName()
方法包括字符替换:+
被替换为/
.
让我们考虑以下执行路径:
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)
我们参考一下源码:jdk18/klass.cpp 位于 jdk-18+37 · openjdk/jdk18 https://github.com/openjdk/jdk18/blob/jdk-18%2B37/src/hotspot/share/oops/klass.cpp#L665-L666:
// Replace the last '+' char with '/'.
static char* convert_hidden_name_to_java(Symbol* name) {