当前位置:  开发笔记 > 编程语言 > 正文

如何在没有JVM参数的情况下隐藏java 9中的"非法反射访问"警告?

如何解决《如何在没有JVM参数的情况下隐藏java9中的"非法反射访问"警告?》经验,为你挑选了4个好方法。

我只是尝试用Java 9运行我的服务器并得到下一个警告:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by io.netty.util.internal.ReflectionUtil (file:/home/azureuser/server-0.28.0-SNAPSHOT.jar) to constructor java.nio.DirectByteBuffer(long,int)
WARNING: Please consider reporting this to the maintainers of io.netty.util.internal.ReflectionUtil
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

我想隐藏此警告而不--illegal-access=deny在启动期间添加JVM选项.就像是:

System.setProperty("illegal-access", "deny");

有没有办法做到这一点?

建议使用JVM选项的所有相关答案,我想从代码中关闭它.那可能吗?

澄清 - 我的问题是关于从代码中转发此警告而不是通过类似问题中所述的JVM参数/标志.



1> apangin..:

有一些方法可以禁用非法访问警告,但我不建议这样做.

1.简单的方法

由于警告打印到默认错误流,您只需关闭此流并重定向stderrstdout.

public static void disableWarning() {
    System.err.close();
    System.setErr(System.out);
}

笔记:

此方法合并错误和输出流.在某些情况下,这可能并不理想.

您不能仅通过调用重定向警告消息System.setErr,因为错误流的引用IllegalAccessLogger.warningStream在JVM引导程序的早期保存在字段中.

2.复杂的方法而不改变stderr

一个好消息是sun.misc.Unsafe仍然可以在没有警告的情况下在JDK 9中访问.解决方案是在IllegalAccessLoggerUnsafe API的帮助下重置内部.

public static void disableWarning() {
    try {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe u = (Unsafe) theUnsafe.get(null);

        Class cls = Class.forName("jdk.internal.module.IllegalAccessLogger");
        Field logger = cls.getDeclaredField("logger");
        u.putObjectVolatile(cls, u.staticFieldOffset(logger), null);
    } catch (Exception e) {
        // ignore
    }
}


这是一个可能在未来更新中破解的黑客攻击.对这样的问题做正确的事情是向违规库提交一个错误(你可能已经完成了).您可以通过使用被黑客攻击的类精确打开包来临时解决它.有几种方法可以传递`--add-opens`选项(CLI,可执行JAR的JAR清单,env变量).
WTF?无害反射,例如记录官方API的"受保护"成员,会收到警告,但访问`sun.misc.Unsafe`不会?好的,得到消息,停止使用Reflection(除了获取`theUnsafe`)并始终使用`Unsafe`.然后,我们甚至不需要关闭警告......
@nullpointer它不会被导出,但它不需要让技巧工作.
@Holger是的,具有讽刺意味的是,所有这些围绕"封装内部API"的大惊小怪可能会导致更广泛地使用Unsafe.

2> Rafael Winte..:

还有另一个选项不需要流抑制,也不依赖于未记录或不支持的API.使用Java代理,可以重新定义模块以导出/打开所需的包.这个代码看起来像这样:

void exportAndOpen(Instrumentation instrumentation) {
  Set unnamed = 
    Collections.singleton(ClassLoader.getSystemClassLoader().getUnnamedModule());
  ModuleLayer.boot().modules().forEach(module -> instrumentation.redefineModule(
        module,
        unnamed,
        module.getPackages().stream().collect(Collectors.toMap(
          Function.identity(),
          pkg -> unnamed
        )),
        module.getPackages().stream().collect(Collectors.toMap(
           Function.identity(),
           pkg -> unnamed
         )),
         Collections.emptySet(),
         Collections.emptyMap()
  ));
}

您现在可以在没有警告的情况下运行任何非法访问,因为您的应用程序包含在未命名的模块中,例如:

Method method = ClassLoader.class.getDeclaredMethod("defineClass", 
    byte[].class, int.class, int.class);
method.setAccessible(true);

为了掌握Instrumentation实例,你可以编写一个非常简单的Java代理,并在命令行(而不是类路径)上使用它来指定它-javaagent:myjar.jar.代理只包含premain如下方法:

public class MyAgent {
  public static void main(String arg, Instrumentation inst) {
    exportAndOpen(inst);
  }
}

或者,你可以动态地连接使用由制造方便访问的API附加的字节好友代理的项目(这是我创作的):

exportAndOpen(ByteBuddyAgent.install());

在非法访问之前您需要调用它.请注意,这仅适用于JDK和Linux VM,而如果您需要在其他VM上使用,则需要在命令行上提供Byte Buddy代理作为Java代理.当您希望在通常安装JDK的测试和开发机器上进行自我附件时,这很方便.

正如其他人所指出的那样,这应该只是一个中间解决方案,但我完全理解当前的行为通常会破坏日志记录爬虫和控制台应用程序,这就是为什么我自己在生产环境中使用它作为使用Java 9的短期解决方案和这么久我没有遇到任何问题.

然而,好处是这个解决方案对于未来的更新是强大的,因为任何操作,甚至动态附件都是合法的.使用帮助程序,Byte Buddy甚至可以解决通常禁止的自我附着问题.


这个讨论发生在邮件列表的许多个月,并被拒绝.如果您需要在Java 9上运行系统并且预算有限,有时您需要寻求中间解决方案.因为它不使用非官方API,所以这既不属于黑客.

3> Nicolai..:

我知道无法实现你的要求.正如您所指出的,您需要向JVM启动添加命令行选项(--add-opens但不是--illegal-access=deny).

你写了:

我的目标是避免为最终用户提供额外的说明.我们有许多用户安装了我们的服务器,这将给他们带来很大的不便.

从它的外观来看,你的要求只能得出项目还没有为Java 9做好准备的结论.应该诚实地向用户报告,完全兼容Java 9需要花费更多的时间.这在发布后的早期完全没问题.



4> 小智..:
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main {
    @SuppressWarnings("unchecked")
    public static void disableAccessWarnings() {
        try {
            Class unsafeClass = Class.forName("sun.misc.Unsafe");
            Field field = unsafeClass.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Object unsafe = field.get(null);

            Method putObjectVolatile = unsafeClass.getDeclaredMethod("putObjectVolatile", Object.class, long.class, Object.class);
            Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class);

            Class loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
            Field loggerField = loggerClass.getDeclaredField("logger");
            Long offset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
            putObjectVolatile.invoke(unsafe, loggerClass, offset, null);
        } catch (Exception ignored) {
        }
    }

    public static void main(String[] args) {
        disableAccessWarnings();
    }
}

它在JAVA 11中对我有用。

推荐阅读
手机用户2502852037
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有