在Java中,/(即正斜杠)在像$Lambda$15/0x00000008000a9440@32e6e9c3这样的对象引用中意味着什么?

mel*_*low 10 java jvm jshell

在 JShell 中,如果我这样做:

interface Foo { String foo(); }
(Foo) () -> "hi"
Run Code Online (Sandbox Code Playgroud)

我明白了

|  created interface Foo
$2 ==> $Lambda$15/0x00000008000a9440@32e6e9c3
Run Code Online (Sandbox Code Playgroud)

通过以下研究,我知道以下内容:

$Lambda = 对生成的字节码的内存中引用,而不是由匿名内部类 (AIC) 持久保存到磁盘的引用

$15 = AIC 的对象引用

@32e6e9c3 =创建的对象的序列号——至少在 IntelliJ 中

但是(斜线)表示什么/,如/0x00000008000a9440

Ser*_*nov 5

概括

\n

$Lambda$15/0x00000008000a9440是创建的隐藏类的名称。

\n

如下所示,0x00000008000a9440称为后缀。

\n

可以通过调用来检索类的名称java.lang.Class.getName()。\n因此:

\n
    \n
  • 例如,相同的类名可以通过 Java 程序(而不是通过 JShell)检索。
  • \n
  • 这个问题似乎不是关于JShell的,而是关于Java语言和Java虚拟机的。
  • \n
\n

显示隐藏类名称的示例程序

\n

Program班级

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

程序输出

\n
Java Virtual Machine specification name: Java Virtual Machine Specification\nJava Virtual Machine specification version: 18\nJava Virtual Machine specification vendor: Oracle Corporation\nJava Virtual Machine implementation name: OpenJDK 64-Bit Server VM\nJava Virtual Machine implementation version: 18.0.1-ea+10-Debian-1\nJava Virtual Machine implementation vendor: Debian\nSupplier 1: info.brunov.stackoverflow.question72804142.Program$$Lambda$18/0x0000000800c031f0\nSupplier 2: info.brunov.stackoverflow.question72804142.Program$$Lambda$19/0x0000000800c033f8\nSupplier 3: info.brunov.stackoverflow.question72804142.Program$$Lambda$20/0x0000000800c03600\n
Run Code Online (Sandbox Code Playgroud)\n

文档参考

\n

JEP 371:隐藏类

\n

自 JDK 15 起引入了隐藏类。\n有关其他详细信息,请参阅 JEP:JEP 371:隐藏类

\n

以下是 JEP 中有关隐藏类名称的摘录:

\n
\n

创建隐藏类的主要区别在于它所指定的名称。隐藏类不是匿名的。它有一个可用的名称,Class::getName并且可以在诊断中显示(例如java -verbose:class)、JVM TI 类加载事件、JFR 事件和堆栈跟踪中显示。然而,该名称的形式非常不寻常,它实际上使该类对所有其他类不可见。该名称是以下内容的串联:

\n
    \n
  1. this_class结构中指定的内部形式(JVMS 4.2.1)的二进制名称ClassFile,例如A/B/C
  2. \n
  3. \'.\'; 和
  4. \n
  5. 由 JVM 实现选择的非限定名称 (JVMS 4.2.2)。
  6. \n
\n

例如,如果this_class指定com/example/Foo(二进制名称的内部形式),则可以命名com.example.Foo从该结构派生的隐藏类ClassFilecom/example/Foo.1234。该字符串既不是二进制名称,也不是二进制名称的内部形式。

\n

给定一个名为 的隐藏类A/B/C.x,结果为Class::getName是以下各项的串联:

\n
    \n
  1. 二进制名称A.B.C(通过将每个名称A/B/C替换为\'/\'\'.\'为 来获得);
  2. \n
  3. 人物; 和
  4. \n
  5. 不合格名称x
  6. \n
\n

例如,如果隐藏类名为com/example/Foo.1234,则结果Class::getNamecom.example.Foo/1234。同样,该字符串既不是二进制名称,也不是二进制名称的内部形式。

\n

隐藏类的命名空间与普通类的命名空间不相交。给定一个ClassFile结构 wherethis_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, ...)它会拒绝字符串参数,因为它不是二进制名称。

\n
\n

Java文档:java.lang.Class#getName()方法

\n

让我们参考方法文档:Class (Java SE 15 & JDK 15)

\n

文档摘录:

\n
\n

公共\xc2\xa0字符串\xc2\xa0getName()

\n

返回由此表示的实体(类、接口、数组类、基本类型或 void)的名称Class

\n

如果这Class对象表示类或接口,而不是数组类,则:

\n\n
\n

实现细节:OpenJDK Java 虚拟机:隐藏类名

\n

介绍

\n

让我们考虑一下 OpenJDK 18 的源代码。

\n

让我们参考标签:openjdk/jdk18 at jdk-18+37

\n

请注意:

\n
    \n
  • 以下执行路径是理论上的:我正在使用提到的源代码标签。
  • \n
  • 下面的调用堆栈是真实的:我正在使用 OpenJDK 18.0.1-ea+10-Debian-1
  • \n
\n

隐藏类名修改

\n

隐藏类的创建(java.lang.invoke.MethodHandles.Lookup.defineHiddenClass()方法)包括对其名称的修改。

\n

让我们考虑以下调用堆栈:

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

然后让我们将以下执行路径视为调用堆栈的延续

\n
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)\n\n// Native calls below.\njclass Unsafe_DefineClass0(JNIEnv *env, jobject unsafe, jstring name, jbyteArray data, int offset, int length, jobject loader, jobject pd)\njclass Unsafe_DefineClass_impl(JNIEnv *env, jstring name, jbyteArray data, int offset, int length, jobject loader, jobject pd)\nJNIEXPORT jclass JNICALL\njclass JVM_DefineClass(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd)\njclass jvm_define_class_common(const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source, TRAPS)\nInstanceKlass* SystemDictionary::resolve_from_stream(ClassFileStream* st, Symbol* class_name, Handle class_loader, const ClassLoadInfo& cl_info, TRAPS)\nInstanceKlass* SystemDictionary::resolve_hidden_class_from_stream(ClassFileStream* st, Symbol* class_name, Handle class_loader, const ClassLoadInfo& cl_info, TRAPS)\nInstanceKlass* KlassFactory::create_from_stream(ClassFileStream* stream, Symbol* name, ClassLoaderData* loader_data, const ClassLoadInfo& cl_info, TRAPS)\nInstanceKlass* ClassFileParser::create_instance_klass(bool changed_by_loadhook, const ClassInstanceInfo& cl_inst_info, TRAPS)\nvoid ClassFileParser::mangle_hidden_class_name(InstanceKlass* const ik)\n
Run Code Online (Sandbox Code Playgroud)\n

让我们参考一下源代码:jdk18/classFileParser.cpp at jdk-18+37 \xc2\xb7 openjdk/jdk18

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

请注意,该+字符用作分隔符。

\n

获取隐藏类名

\n

java.lang.Class#getName()方法包括字符替换:+替换为/

\n

让我们考虑以下执行路径:

\n
String java.lang.Class.getName()\nString java.lang.Class.initClassName()\n\n// Native calls below.\nJNIEXPORT jstring JNICALL JVM_InitClassName(JNIEnv *env, jclass cls)\noop java_lang_Class::name(Handle java_class, TRAPS)\nconst char* java_lang_Class::as_external_name(oop java_class)\nconst char* Klass::external_name() const\nstatic char* convert_hidden_name_to_java(Symbol* name)\n
Run Code Online (Sandbox Code Playgroud)\n

让我们参考一下源代码:jdk18/klass.cpp at jdk-18+37 \xc2\xb7 openjdk/jdk18

\n
// Replace the last \'+\' char with \'/\'.\nstatic char* convert_hidden_name_to_java(Symbol* name) {\n
Run Code Online (Sandbox Code Playgroud)\n