作为隐藏类加载时,Lambda 表达式和匿名类不起作用

2024-01-12

我正在尝试在运行时编译和加载动态生成的 Java 代码。由于 ClassLoader::defineClass 和 Unsafe::defineAnonymousClass 在这种情况下都有严重的缺点,我尝试使用隐藏类 https://openjdk.java.net/jeps/371 via 查找::defineHiddenClass https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/invoke/MethodHandles.Lookup.html#defineHiddenClass(byte%5B%5D,boolean,java.lang.invoke.MethodHandles.Lookup.ClassOption...)反而。这对于我尝试加载的所有类都适用,但调用 lambda 表达式或包含匿名类的类除外。

调用 lambda 表达式会引发以下异常:

Exception in thread "main" java.lang.NoClassDefFoundError: tests/HiddenClassLambdaTest$LambdaRunner/0x0000000800c04400
    at tests.HiddenClassLambdaTest.main(HiddenClassLambdaTest.java:22)
Caused by: java.lang.ClassNotFoundException: tests.HiddenClassLambdaTest$LambdaRunner.0x0000000800c04400
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:636)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:182)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519)
    ... 1 more

执行实例化匿名类的代码会引发以下错误:

Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    tests/HiddenClassLambdaTest$LambdaRunner+0x0000000800c00400.run()V @5: invokespecial
  Reason:
    Type 'tests/HiddenClassLambdaTest$LambdaRunner+0x0000000800c00400' (current frame, stack[2]) is not assignable to 'tests/HiddenClassLambdaTest$LambdaRunner'
  Current Frame:
    bci: @5
    flags: { }
    locals: { 'tests/HiddenClassLambdaTest$LambdaRunner+0x0000000800c00400' }
    stack: { uninitialized 0, uninitialized 0, 'tests/HiddenClassLambdaTest$LambdaRunner+0x0000000800c00400' }
  Bytecode:
    0000000: bb00 1159 2ab7 0013 4cb1               

    at java.base/java.lang.ClassLoader.defineClass0(Native Method)
    at java.base/java.lang.System$2.defineClass(System.java:2193)
    at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2446)
    at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClassAsLookup(MethodHandles.java:2427)
    at java.base/java.lang.invoke.MethodHandles$Lookup.defineHiddenClass(MethodHandles.java:2133)
    at tests.HiddenClassLambdaTest.main(HiddenClassLambdaTest.java:25)

这是重现问题的简短示例:

import java.lang.invoke.MethodHandles;

public class HiddenClassLambdaTest {
    /** This class is to be loaded and executed as hidden class */
    public static final class LambdaRunner implements Runnable {
        @Override public void run() {
            Runnable runnable = () -> System.out.println("Success");
            runnable.run();
        }
    }
    
    public static void main(String[] args) throws Throwable {
        // Path to the class file of the nested class defined above
        String nestedClassPath = HiddenClassLambdaTest.class.getTypeName().replace('.','/') + "$LambdaRunner.class";
        // Class file content of the LambdaRunner class
        byte[] classFileContents = HiddenClassLambdaTest.class.getClassLoader().getResourceAsStream(nestedClassPath).readAllBytes();
        Class<?> lambdaRunnerClass = MethodHandles.lookup().defineHiddenClass(classFileContents, true).lookupClass();
        Runnable lambdaRunnerInstance = (Runnable) lambdaRunnerClass.getConstructor().newInstance();
        lambdaRunnerInstance.run();
    }
}

我已经尝试使用不同的 JDK 编译和运行代码,使用不同的方式创建隐藏类的新实例,在以下位置搜索错误https://bugs.openjdk.java.net/ https://bugs.openjdk.java.net/,弄乱字节码本身和其他一些东西。我不是Java内部的专家,所以我不确定我是否没有理解正确引入隐藏类的JEP。

我做错了什么吗?这是不可能的还是这是一个错误?

编辑:JEP https://openjdk.java.net/jeps/371 states

迁移应考虑以下因素: 要从隐藏类中的代码调用私有嵌套实例方法,请使用invokevirtual 或invokeinterface 而不是invokespecial。生成的使用 invokespecial 调用私有 Nestmate 实例方法的字节码将无法通过验证。 invokespecial 只能用于调用私有嵌套构造函数。

这可能是匿名类的问题。有没有一种方法可以编译代码,从而避免在字节码中调用特殊调用?


您不能将任意类转换为隐藏类。

The 的文档defineHiddenClass https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/invoke/MethodHandles.Lookup.html#defineHiddenClass(byte%5B%5D,boolean,java.lang.invoke.MethodHandles.Lookup.ClassOption...)包含句子

  • 任何尝试解析运行时常量池中由this_class,符号引用被认为解析为C并且决议总是立即成功。

它没有明确说明的是,这是类型解析在隐藏类中结束的唯一位置。

但文中已经明确表示错误报告 JDK-8222730 https://bugs.openjdk.java.net/browse/JDK-8222730:

对于隐藏类,其指定的隐藏名称只能通过隐藏类的“this_class”常量池条目访问。

即使在隐藏类中,也不应该通过在方法或字段签名中指定其原始名称来访问该类。

我们可以检查一下。即使像这样简单的案例

public class HiddenClassLambdaTest {

    public static void main(String[] args) throws Throwable {
        byte[] classFileContents = HiddenClassLambdaTest.class
            .getResourceAsStream("HiddenClassLambdaTest$LambdaRunner.class")
            .readAllBytes();
        var hidden = MethodHandles.lookup()
            .defineHiddenClass(classFileContents, true, ClassOption.NESTMATE);
        Runnable lambdaRunnerInstance = (Runnable)hidden.findConstructor(
            hidden.lookupClass(), MethodType.methodType(void.class)).invoke();
        lambdaRunnerInstance.run();
    }

    static class LambdaRunner implements Runnable {
        LambdaRunner field = this;

        @Override
        public void run() {
        }
    }
}

已经会失败了。请注意,尝试解析原始类名是一种特殊情况LambdaRunner隐藏类中不会失败,因为您使用现有类作为模板。所以你得到一个IncompatibleClassChangeError or a VerifierError由于隐藏类和现有类之间不匹配LambdaRunner班级。当你不使用现有类的类定义时,你会得到一个NoClassDefFoundError.

这同样适用于

    static class LambdaRunner implements Runnable {
        static void method(LambdaRunner arg) {
        }

        @Override
        public void run() {
            method(this);
        }
    }

正如引用的错误报告所述,字段和方法都不能在其签名中引用隐藏类。

一个不太直观的例子是

    static class LambdaRunner implements Runnable {
        @Override
        public void run() {
            System.out.println("" + this);
        }
    }

根据编译器和选项,这将失败,就像当StringConcatFactory使用时,其行为就像调用一个方法,该方法将所有非常量部分作为参数并返回一个String。这是在方法签名中包含隐藏类的另一种情况。


Lambda 表达式很特殊,就像这样的类

    static class LambdaRunner implements Runnable {
        @Override
        public void run() {
            Runnable runnable = () -> System.out.println("Success");
            runnable.run();
        }
    }

编译类似于

    static class LambdaRunner implements Runnable {
        @Override
        public void run() {
            Runnable runnable = LambdaRunner::lambdaBody;
            runnable.run();
        }
        private static void lambdaBody() {
            System.out.println("Success");
        }
    }

它的方法签名中没有隐藏类,但必须将包含 lambda 表达式主体的方法引用为MethodReference。在常量池中,该方法的描述引用其声明类,使用this_class入口。因此它被重定向到隐藏类,如文档中所述。

但该项目的建设MethodType作为MethodReference不使用此信息来加载Class就像类文字一样。相反,它尝试通过定义类加载器加载隐藏类,该类加载器失败并显示NoClassDefFoundError你已经发布了。

这似乎与JDK-8130087 https://bugs.openjdk.java.net/browse/JDK-8130087这表明普通方法解析与方法不同,MethodType作品,可以使MethodType在仅调用该方法可以工作的地方失败。

但有可能证明,即使解决这个问题也不能解决普遍问题:

    static class LambdaRunner implements Runnable {
        @Override
        public void run() {
            var lookup = MethodHandles.lookup();
            var noArgVoid = MethodType.methodType(void.class);
            try {
                MethodHandle mh = LambdaMetafactory.metafactory(lookup, "run",
                    MethodType.methodType(Runnable.class), noArgVoid,
                    lookup.findStatic(LambdaRunner.class, "lambdaBody", noArgVoid),
                    noArgVoid).getTarget();
                System.out.println("got factory");
                Runnable runnable = (Runnable)mh.invokeExact();
                System.out.println("got runnable");
                runnable.run();
            }
            catch(RuntimeException|Error e) {
                throw e;
            }
            catch(Throwable e) {
                throw new AssertionError(e);
            }
        }
        private static void lambdaBody() {
            System.out.println("Success");
        }
    }

这绕过了上述问题并调用LambdaMetafactory手动。当被重新定义为隐藏类时,它将打印:

got factory
got runnable
Exception in thread "main" java.lang.NoClassDefFoundError: test/HiddenClassLambdaTest$LambdaRunner/0x0000000800c01400
    at test/test.HiddenClassLambdaTest.main(HiddenClassLambdaTest.java:15)
Caused by: java.lang.ClassNotFoundException: test.HiddenClassLambdaTest$LambdaRunner.0x0000000800c01400
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    ... 1 more

这表明所有障碍都已被绕过,但是当涉及到生成的实际调用时Runnable对于持有 lambda 主体的方法,由于目标类是hidden。急切解析符号引用的 JVM 可能会更早失败,即该示例可能无法打印got runnable then.

与旧的 JVM 匿名类不同,无法链接到隐藏类,甚至无法链接到另一个隐藏类。


正如一开始所说,底线是,你不能将任意类变成隐藏类。 Lambda 表达式并不是唯一不适用于隐藏类的功能。尝试并感到惊讶并不是一个好主意。隐藏类只能与字节码生成器结合使用,并且仅使用已知可用的功能。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

作为隐藏类加载时,Lambda 表达式和匿名类不起作用 的相关文章

  • 使用 Guice 注入类集合

    我正在尝试用 Google Guice 2 0 注入东西 我有以下结构 FooAction implements Action BarAction implements Action 然后我有一个带有以下构造函数的 ActionLibrar
  • 如何将变量的全部内容发送/导出到文本文件/xml 文件/剪贴板?

    我想将实例的内容 最好以树形形式 发送给某人 打印屏幕是不行的 因为类太复杂了 您需要将输出转回实例吗 在这种情况下 其他答案都是正确的 如果您只想手动检查实例的内容 理想情况下您的类都将实现toString 你可以将其重定向到一个文件 如
  • 如何从 Play WSClient 的字符串创建 WSResponse 对象

    文档 https www playframework com documentation 2 4 x JavaTestingWebServiceClients Mock the web service建议使用模拟Web服务测试基于WSCli
  • 项目缺少所需的注释处理库

    我的 Eclipse IDE 突然在问题视图中显示 xxxx 项目缺少所需的注释处理库 xxxx M2 REPO 中的一些旧 jar 我用谷歌搜索 没有找到任何答案 为什么我的项目使用旧的 jar 以及错误来自哪里 To remove th
  • 如何将现有的 SQLite3 数据库导入 Room?

    好吧 我在桌面上使用 SQLite3 创建了一个只需要读取的某些信息的数据库 我正在制作的应用程序不需要在此表中插入或删除信息 我在 Room 数据库层上做了相当多的谷歌搜索 所有文档都需要在构建应用程序时在 Room 中创建一个新的数据库
  • 如何为小程序提供对文件系统写入的访问权限

    我在设置小程序的策略文件时遇到问题 我是第一次这样做 不知道如何在java中设置小程序的策略文件 实际上我想授予小程序在文件系统上写入的权限 为此我必须向小程序授予文件权限 所以我创建了一个名为 java policy 的文件 并将以下代码
  • 具有 CRUD 功能的基于 Spring Web 的管理工具

    在 PHP Symfony 世界里有一个工具叫 Sonata Adminhttps sonata project org https sonata project org 基于 AdminLTE 模板 这是一款一体化管理工具 具有登录 菜单
  • 使用 JSch 分别为各个提示提供输入

    问题是 SSH 连接需要在常规登录后提供另一个用户 ID 和密码信息 我正在使用 JSch 连接到远程服务器 它接受以下形式的输入InputStream 和这个InputStream只能通过一次 由于会话是交互式的 这会导致问题 我尝试将输
  • OpenNLP 与斯坦福 CoreNLP

    我一直在对这两个包进行一些比较 但不确定该往哪个方向走 我简单地寻找的是 命名实体识别 人 地点 组织等 性别识别 一个不错的训练 API 据我所知 OpenNLP 和斯坦福 CoreNLP 提供了非常相似的功能 然而 Stanford C
  • 关于Java泛型的一些问题

    假设我有以下接口和实现类 interface Foo
  • 正则表达式在 Velocity 模板中不起作用

    我在 Test java 中尝试过这个 String regex lt s br s s gt String test1 lt br gt System out println test replaceAll regex 但是当我在速度模板
  • Java - JPanel 内有边距和 JTextArea

    我想创建这样的东西 主面板有其边距 x 并且 TextArea 位于该面板的中心 几乎填满了面板 底部是另一个具有自定义尺寸 高度 y 的面板 可以使用某些快捷方式将其切换为可见和不可见 底部面板有 FlowLayout 和几个元素 问题是
  • Java 中的 ExecuteUpdate sql 语句不起作用

    我正在学习如何将 SQL 与 Java 结合使用 我已成功安装 JDBC 驱动程序 并且能够从数据库读取记录并将其打印在屏幕上 我的问题发生在尝试执行更新或插入语句时 没有任何反应 这是我的代码 问题所在的方法 public static
  • 更改 JComboBox 中滚动条的大小

    有谁知道如何手动更改 jComboBox 中的滚动条大小 我已经尝试了一大堆东西 但没有任何效果 好吧 我明白了 您可以实现 PopUpMenuListener 并使用它 public void popupMenuWillBecomeVis
  • 如何在 Bean Validation 1.0 中构造 ConstraintViolationException?

    我对 javax validation API 感到困惑 我正在编写一个简单的测试来理解它 Sample sample new Sample Set
  • JAXB 编组器无参数默认构造函数

    我想从 java 库中编组一个 java 对象 当使用 JAXB marschaller 编组 java 对象时 我遇到了一个问题 A 类没有无参数默认构造函数 我使用Java Decompiler来检查类的实现 它是这样的 public
  • Java8:流映射同一流中的两个属性

    我有课Model带有以下签名 class Model private String stringA private String stringB public Model String stringA String stringB this
  • Java和手动执行finalize

    如果我打电话finalize 在我的程序代码中的一个对象上 JVM当垃圾收集器处理这个对象时仍然再次运行该方法吗 这是一个大概的例子 MyObject m new MyObject m finalize m null System gc 是
  • @Embeddable 中的 @GenerateValue

    我已将实体的 id 分离到一个单独的 Embeddable 类中 该实体如下 Entity Table name users public class Users EmbeddedId private Users pk id private
  • 如何使用 Jest 从 ElasticSearch 获取索引列表

    我正在尝试使用 Jest 检索索引列表 但我只得到 Stats statistics new Stats Builder build result client execute statistics 如何从结果中检索索引列表 除了统计之外

随机推荐

  • Python HTML 编码 \xc2\xa0

    我已经为此苦苦挣扎了一段时间 我正在尝试将字符串写入 HTML 但是在清理它们后 格式出现问题 这是一个例子 paragraphs Grocery giant and household name Woolworths is battere
  • 如何创建不可选择的上下文菜单项?

    我有一个简单的上下文菜单 我想添加一个标题项到它 它无法被选择 甚至不能用鼠标光标突出显示 当我设定Enabled false 我仍然可以标记它 但感觉很愚蠢 因为它显然已被禁用并且文本是灰色的 Example 像这样 我无法标记或选择 菜
  • 从 FirebaseListAdapter 中的 onItemClick 检索 Firebase 密钥

    我有一个 Firebase DB 我正在使用push 方法将条目添加到数据库中 从而生成随机密钥作为每个条目的参考 在应用程序的一部分中 我使用 FirebaseListAdapter 将一些数据库条目填充到列表视图中 然后我正在实施set
  • 将嵌套组分配给 Azure AD 应用程序中的角色 用户和组

    我在 Azure AD 应用程序清单中配置了一些角色类型 当我将组 X 分配给角色时 我可以看到 X 中的用户已分配 现在假设组 Y 是 X 内部的组 Y 中的用户未分配 为什么 有没有办法让所有 内部 用户也被分配 Thanks 今天不行
  • 我如何强制 MediaWiki 尊重单行中断?

    默认情况下 MediaWiki 会忽略单个换行符 将它们视为单个空格 要在页面中插入新行 必须在源代码中插入两个连续的换行符 这会产生一个新段落 p 也可以插入 br 直接进入源码 如何修改 MediaWiki 解析引擎 以便源代码中的单行
  • Hibernate:集合的集合

    这是我一直遇到的一个问题 我想让 hibernate 管理一个代表集合集合的表 例如 地图中的地图 套装清单 列表地图 例如 我希望能够表示这一点 class OwningClass Long entityId Map
  • 如何正确使用matchedGeometry?

    当我使用匹配的几何视图修改器时 我总是收到警告 Multiple inserted views in matched geometry group Pair
  • 获取文件夹的树形结构

    我的数据库中的 文件夹 表中有以下文件夹 Folder1 Folder2 Folder3 Folder4 文件夹的路径 Folder1 Folder2 Folder3 Folder4 当我单击Folder3时 我必须获取路径为 Folder
  • 使用通用 List 线程安全吗

    我有一个System Collections Generic List
  • 两个张量的双点积

    我有两个张量 A是二阶张量并且B是四阶张量 我知道当计算双点积 http en wikipedia org wiki Dyadics Product of dyadic and dyadic两个张量的结果张量的秩将减少两倍 因此在我的示例中
  • 我如何知道 .NET Standard 需要哪些平台扩展?

    我跑了 NET 可移植性分析器 https marketplace visualstudio com items itemName ConnieYau NETPortabilityAnalyzer针对我想从我自己的 NET Standard
  • 将徽章添加到选项卡

    如何向选项卡添加徽章 我正在使用这个代码 protected void onCreate Bundle savedInstanceState super onCreate savedInstanceState setContentView
  • 如何更改作为参数传递的变量的值?

    如何更改 C 中作为参数传递的变量的值 我试过这个 void foo char foo int baa if baa foo ab else foo cb 并致电 char x baa foo x 1 printf s n x 但它打印ba
  • 如何在 Netbeans 平台上获取项目类型?

    有没有办法知道所选项目的类型 我想根据项目类型 如 J2SE 项目 执行一些特定操作 以下是我发现的唯一方法 public final class MyAction extends CookieAction Override public
  • 如何从 JavaScript 中的 foreach 循环中删除特定数组元素

    var fruit apple pear pear pear banana 如何从该数组中删除所有 梨 水果 我尝试了以下方法 但仍然留下一个梨 for var f in fruit if fruit f pear fruit splice
  • 动态添加边缘 visjs

    谁能帮我在这个 visjs 网络中动态添加边 实际上 我正在尝试使用拖放将节点添加到画布 但是当我单击节点并将边缘动态添加到画布上现有的另一个节点时 我需要帮助添加边缘 您可以使用 vis js 的 更新 函数动态添加节点或边 您只需传入一
  • 禁用 WebAPI 的 Windows 身份验证

    我正在使用 MVC4 应用程序并使用 WebAPI 来获取 发送我的所有数据 在控制器中 我使用 HttpClient 请求来获取数据 一切正常 我面临的问题是 当在项目中启用 Windows 身份验证时 Web API 调用将返回 401
  • 如何解决错误: getSharedPreferences(String, int) 对于 new View.OnClickListener(){} 类型未定义

    我在编码中遇到此错误 但不完全确定如何解决此问题 我已经尝试尝试解决此问题 但似乎找不到任何有效的方法 我以前做过这个 但从来没有在片段中做过 所以也许是因为这个 我正在关注exception new View OnClickListene
  • 如何在无服务器框架中为多个 dynamodb 表定义 iamrolestatements 资源?

    我想在我的无服务器项目中使用多个 dynamodb 表 如何在 iamrolestatements 中正确定义多个资源 我有一个例子serverless yml service serverless expense tracker fram
  • 作为隐藏类加载时,Lambda 表达式和匿名类不起作用

    我正在尝试在运行时编译和加载动态生成的 Java 代码 由于 ClassLoader defineClass 和 Unsafe defineAnonymousClass 在这种情况下都有严重的缺点 我尝试使用隐藏类 https openjd