jvm 在Java中,在对象引用(如$Lambda $15/0x00000008000a9440@32e6e9c3)中,/(即正斜杠)是什么意思?

wkftcu5l  于 2022-11-07  发布在  Java
关注(0)|答案(1)|浏览(80)

在JShell中,如果我这样做:

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

我得到

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

从下面的研究中,我知道了以下几点:
$λ = an in-memory reference, as opposed to one persisted to disk by an anonymous inner class (AIC), to the generated bytecode
$15 =对AIC的对象引用
@32e6e9c3 =所创建对象的序列号--至少在IntelliJ中是这样
但是,/(斜线)表示什么,例如/0x00000008000a9440

zsohkypk

zsohkypk1#

总结

$Lambda$15/0x00000008000a9440是创建的隐藏类的名称。
如下所示,0x00000008000a9440被称为后缀。
类的名称可以通过调用java.lang.Class.getName()方法来检索。因此:

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

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

Program

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: Hidden Classes
下面是从JEP中摘录的有关隐藏类名的内容:
隐藏类创建方式的主要区别在于它的名称。**隐藏类不是匿名的。**它有一个可通过Class::getName获得的名称,并可能在诊断中显示(例如java -verbose:class的输出)、在JVM TI类加载事件中、在JFR事件中以及在堆栈跟踪中。但是,这个名字有一个非常不寻常的形式,它有效地使这个类对所有其他类不可见。2这个名字是以下内容的连接:
1.由ClassFile结构中的this_class指定的内部形式(JVMS 4.2.1)的二进制名称,例如A/B/C;

  1. '.'字符;和
    1.由JVM实现选择的非限定名称(JVMS 4.2.2)。
    例如,如果this_class指定com/example/Foo(二进制名称com.example.Foo的内部形式),则从ClassFile结构衍生的隐藏类别可能会命名为com/example/Foo.1234。这个字串既不是二进制名称,也不是二进制名称的内部形式。
    给定一个名为A/B/C.x的隐藏类,Class::getName的结果是以下内容的串联:
    1.二进制名称A.B.C(通过取A/B/C并将每个'/'替换为'.'来获得);
    1.“/”字符;和
    1.不合格名称x
    例如,如果隐藏类别的名称为com/example/Foo.1234,则Class::getName的结果为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, ...)将拒绝字符串参数,因为它不是二进制名称。

Javadoc:java.lang.Class#getName()方法

让我们参考方法文档:类(Java SE 15和JDK 15)。
文档摘录:
公共String获取名称()
返回此Class对象表示的实体(类、接口、数组类、基元类型或void)的名称。
如果此Class对象表示类或接口,而不是数组类,则:

  • 如果类或接口未隐藏,则返回类或接口的二进制名称。
  • 如果类或接口是隐藏的,则结果是以下形式的字符串:N + '/' + <suffix>,其中N是由传递给Lookup::defineHiddenClassclass文件指示的二进制名称,<suffix>是非限定名称。

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

简介
让我们考虑一下OpenJDK 18的源代码。
让我们参考标记:openjdk/jdk18 at jdk-18+37
请注意:

  • 以下执行路径是理论上的:我使用的是上面提到的源代码标记。
  • 下面的调用堆栈是真实的:我使用的是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)

让我们参考这段源代码:在jdk-18+37上的jdk18/classFileParser.cpp文件解析器.打开jdk/jdk 18:

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));
  }

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

获取隐藏类名

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)

让我们参考这段源代码:在jdk-18+37上的jdk18/klass.cpp.打开jdk/jdk 18:

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

相关问题