Hidden Classes in Java 15 – Java 15中的隐藏类

最后修改: 2022年 1月 16日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Java 15 introduced a whole lot of features. In this article, we’ll discuss one of the new features called Hidden Classes under JEP-371. This feature is introduced as an alternative to Unsafe API, which isn’t recommended outside JDK.

Java 15引入了一大批特性。在本文中,我们将讨论JEP-371下的一个名为隐藏类的新功能。这个功能是作为Unsafe API的替代品而引入的,在JDK之外不推荐使用。

The hidden classes feature is helpful for anyone who works with dynamic bytecode or JVM languages.

隐藏类的功能对任何使用动态字节码或JVM语言的人都有帮助。

2. What Is a Hidden Class?

2.什么是隐藏类?

Dynamically generated classes provide efficiency and flexibility for low-latency applications. They’re only needed for a limited time. Retaining them for the lifetime of statically generated classes increases memory footprint. Existing solutions for this situation, such as per-class loaders, are cumbersome and inefficient.

动态生成的类为低延迟的应用提供了效率和灵活性。它们只在有限的时间内需要。在静态生成类的生命周期内保留它们会增加内存占用。对于这种情况,现有的解决方案,如每类加载器,是繁琐和低效的。

Since Java 15, hidden classes have become a standard way to generate dynamic classes.

从Java 15开始,隐藏类已经成为生成动态类的标准方式。

Hidden classes are classes that cannot be used directly by the bytecode or other classes. Even though it’s mentioned as a class, it should be understood to mean either a hidden class or interface. It can also be defined as a member of the access control nest and can be unloaded independently of other classes.

隐藏类是不能被字节码或其他类直接使用的类。即使它被提到是一个类,也应该理解为是指隐藏类或接口。它也可以被定义为访问控制嵌套的成员,并且可以独立于其他类而被卸载。

3. Properties of Hidden Classes

3.隐性类的属性

Let’s take a look at the properties of these dynamically generated classes:

让我们来看看这些动态生成的类的属性。

  • Non-discoverable – a hidden class is not discoverable by the JVM during bytecode linkage, nor by programs making explicit use of class loaders. The reflective methods Class::forName, ClassLoader::findLoadedClass, and Lookup::findClass will not find them.
  • We can’t use the hidden class as a superclass, field type, return type, or parameter type.
  • Code in the hidden class can use it directly, without relying on the class object.
  • final fields declared in hidden classes are not modifiable regardless of their accessible flags.
  • It extends the access control nest with non-discoverable classes.
  • It may be unloaded even though its notional defining class loader is still reachable.
  • Stack traces don’t show the methods or names of hidden classes by default, however, tweaking JVM options can show them.

4. Creating Hidden Classes

4.创建隐藏类

The hidden class isn’t created by any class loader. It has the same defining class loader, runtime package, and protection domain of the lookup class.

隐藏类不是由任何类加载器创建的。它有相同的定义类加载器、运行时包和查找类的保护域。

First, let’s create a Lookup object:

首先,让我们创建一个Lookup对象。

MethodHandles.Lookup lookup = MethodHandles.lookup();

The Lookup::defineHiddenClass method creates the hidden class. This method accepts an array of bytes.

Lookup::defineHiddenClass方法创建隐藏类。这个方法接受一个字节数组。

For simplicity, we’ll define a simple class with the name HiddenClass that has a method to convert a given string to uppercase:

为了简单起见,我们将定义一个名为HiddenClass的简单类,它有一个将给定的字符串转换为大写的方法。

public class HiddenClass {
    public String convertToUpperCase(String s) {
        return s.toUpperCase();
    }
}

Let’s get the path of the class and load it into the input stream. After that, we’ll convert this class into bytes using IOUtils.toByteArray():

让我们得到这个类的路径并将其加载到输入流中。之后,我们将使用IOUtils.toByteArray()将这个类转换成字节数。

Class<?> clazz = HiddenClass.class;
String className = clazz.getName();
String classAsPath = className.replace('.', '/') + ".class";
InputStream stream = clazz.getClassLoader()
    .getResourceAsStream(classAsPath);
byte[] bytes = IOUtils.toByteArray();

Lastly, we pass these constructed bytes into Lookup::defineHiddenClass:

最后,我们将这些构建的字节传递给Lookup::defineHiddenClass

Class<?> hiddenClass = lookup.defineHiddenClass(IOUtils.toByteArray(stream),
  true, ClassOption.NESTMATE).lookupClass();

The second boolean argument true initializes the class. The third argument ClassOption.NESTMATE specifies that the created hidden class will be added as a nestmate to the lookup class so that it has access to the private members of all classes and interfaces in the same nest.

第二个boolean参数true初始化了这个类。第三个参数ClassOption.NESTMATE指定创建的隐藏类将被添加为查找类的巢主,这样它就可以访问同一巢中所有类和接口的private成员。

Suppose we want to bind the hidden class strongly with its class loader, ClassOption.STRONG. This means that the hidden class can only be unloaded if its defining loader is not reachable.

假设我们想将隐藏类与它的类加载器强绑定,ClassOption.STRONG。这意味着隐藏类只有在其定义的加载器无法到达时才能被卸载。

5. Using Hidden Classes

5.使用隐藏类

Hidden classes are used by frameworks that generate classes at runtime and use them indirectly via reflection.

隐藏的类被那些在运行时生成类的框架所使用,并通过反射间接地使用它们。

In the previous section, we looked at creating a hidden class. In this section, we’ll see how to use it and create an instance.

在上一节中,我们研究了创建一个隐藏类。在本节中,我们将看到如何使用它并创建一个实例。

Since casting the classes obtained from Lookup.defineHiddenClass is not possible with any other class object, we use Object to store the hidden class instance. If we wish to cast the hidden class, we can define an interface and create a hidden class that implements the interface:

由于从Lookup.defineHiddenClass获得的类不可能用任何其他的类对象来铸造,我们使用Object来存储隐藏类的实例。如果我们希望铸造隐藏类,我们可以定义一个接口并创建一个实现该接口的隐藏类。

Object hiddenClassObject = hiddenClass.getConstructor().newInstance();

Now, let’s get the method from the hidden class. After getting the method, we’ll invoke it as any other standard method:

现在,让我们从隐藏类中获得这个方法。得到方法后,我们将像其他标准方法一样调用它。

Method method = hiddenClassObject.getClass()
    .getDeclaredMethod("convertToUpperCase", String.class);
Assertions.assertEquals("HELLO", method.invoke(hiddenClassObject, "Hello"));

Now, we can verify a few properties of a hidden class by invoking some of its methods:

现在,我们可以通过调用隐藏类的一些方法来验证它的一些属性。

The method isHidden() will return true for this class:

方法isHidden()将为这个类返回true

Assertions.assertEquals(true, hiddenClass.isHidden());

Also, since there’s no actual name for a hidden class, its canonical name will be null:

另外,由于隐藏类没有实际的名称,它的规范名称将是null

Assertions.assertEquals(null, hiddenClass.getCanonicalName());

The hidden class will have the same defining loader as the class that does the lookup. Since the lookup happens in the same class, the following assertion will be successful:

这个隐藏的类将拥有与进行查找的类相同的定义加载器。由于查找发生在同一个类中,下面的断言将是成功的。

Assertions.assertEquals(this.getClass()
    .getClassLoader(), hiddenClass.getClassLoader());

If we try to access the hidden class through any methods, they’ll throw ClassNotFoundException. This is obvious, as the hidden class name is sufficiently unusual and unqualified to be visible to other classes. Let’s check a couple of assertions to prove that the hidden class is not discoverable:

如果我们试图通过任何方法来访问这个隐藏类,它们会抛出ClassNotFoundException。这是显而易见的,因为隐藏类的名字很不寻常,而且不符合条件,对其他类来说是可见的。让我们检查一下几个断言,以证明隐藏类是不可发现的。

Assertions.assertThrows(ClassNotFoundException.class, () -> Class.forName(hiddenClass.getName()));
Assertions.assertThrows(ClassNotFoundException.class, () -> lookup.findClass(hiddenClass.getName()));

Note that the only way other classes can use a hidden class is via its Class object.

请注意,其他类可以使用一个隐藏类的唯一方法是通过其Class对象。

6. Anonymous Class vs. Hidden Class

6.匿名班与隐藏班

We created a hidden class in previous sections and played with some of its properties. Now, let’s elaborate on the differences between anonymous classes – inner classes without explicit names – and hidden classes:

我们在前面的章节中创建了一个隐藏类,并玩弄了它的一些属性。现在,让我们来阐述一下匿名类–没有明确名称的内部类–和隐藏类之间的区别。

  • Anonymous class has a dynamically generated name with a $ in-between, while a hidden class derived from com.baeldung.reflection.hiddenclass.HiddenClass would be com.baeldung.reflection.hiddenclass.HiddenClass/1234.
  • Anonymous class is instantiated using Unsafe::defineAnonymousClass, which is deprecated, whereas Lookup::defineHiddenClass instantiates a hidden class.
  • Hidden classes don’t support constant-pool patching. It helps define anonymous classes with their constant pool entries already resolved to concrete values.
  • Unlike a hidden class, an anonymous class can access protected members of a host class even though it’s in a different package and not a subclass.
  • An anonymous class can enclose other classes to access its members, but a hidden class cannot enclose other classes.

Although the hidden class isn’t a replacement for an anonymous class, they are replacing some of the usages of anonymous classes in the JDK. From Java 15, lambda expressions use hidden classes.

尽管隐藏类并不是匿名类的替代品,但它们正在取代JDK中匿名类的一些用法。从Java 15开始,lambda表达式使用隐藏类

7. Conclusion

7.结语

In this article, we discussed a new language feature called Hidden Classes in detail. As always, the code is available over on GitHub.

在这篇文章中,我们详细地讨论了一个新的语言特性,即隐藏类。一如既往,代码可在GitHub上获得。