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

时间:2017-09-27 18:38:18

标签: java jvm netty java-9

我只是尝试使用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

我想隐藏此警告而不在启动期间向JVM选项添加--illegal-access=deny。类似的东西:

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

有没有办法做到这一点?

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

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

9 个答案:

答案 0 :(得分:40)

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

1。简单的方法

由于警告会打印到默认错误流,因此您只需关闭此流并将stderr重定向到stdout

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

注意:

  • 此方法合并错误和输出流。在某些情况下,这可能是不可取的。
  • 您无法仅通过调用System.setErr重定向警告消息,因为错误流的引用在JVM引导程序的早期保存在IllegalAccessLogger.warningStream字段中。

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

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

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

答案 1 :(得分:11)

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

void exportAndOpen(Instrumentation instrumentation) {
  Set<Module> 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 agent并使用-javaagent:myjar.jar在命令行(而不是类路径)上指定它。代理程序只包含premain方法,如下所示:

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

或者,您可以使用附件API动态附加,这可以通过the byte-buddy-agent project(我创作)方便地访问:

exportAndOpen(ByteBuddyAgent.install());

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

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

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

答案 2 :(得分:8)

我知道无法实现你的要求。正如您所指出的,您需要在JVM启动时添加command line options--add-opens,而不是--illegal-access=deny)。

您写道:

  

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

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

答案 3 :(得分:2)

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中对我有效。

答案 4 :(得分:2)

万一有人想重定向日志消息而不是丢弃它们,这在Java 11中对我有用。它将替换非法访问记录器写入的流。

public class AccessWarnings {

  public static void redirectToStdOut() {
    try {

      // get Unsafe
      Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
      Field field = unsafeClass.getDeclaredField("theUnsafe");
      field.setAccessible(true);
      Object unsafe = field.get(null);

      // get Unsafe's methods
      Method getObjectVolatile = unsafeClass.getDeclaredMethod("getObjectVolatile", Object.class, long.class);
      Method putObject = unsafeClass.getDeclaredMethod("putObject", Object.class, long.class, Object.class);
      Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class);
      Method objectFieldOffset = unsafeClass.getDeclaredMethod("objectFieldOffset", Field.class);

      // get information about the global logger instance and warningStream fields 
      Class<?> loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
      Field loggerField = loggerClass.getDeclaredField("logger");
      Field warningStreamField = loggerClass.getDeclaredField("warningStream");

      Long loggerOffset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
      Long warningStreamOffset = (Long) objectFieldOffset.invoke(unsafe, warningStreamField);

      // get the global logger instance
      Object theLogger = getObjectVolatile.invoke(unsafe, loggerClass, loggerOffset);
      // replace the warningStream with System.out
      putObject.invoke(unsafe, theLogger, warningStreamOffset, System.out);
    } catch (Throwable ignored) {
    }
  }
}

答案 5 :(得分:1)

您可以在module-info.javaopen module个包或创建module shedlock.example { requires spring.context; requires spring.jdbc; requires slf4j.api; requires shedlock.core; requires shedlock.spring; requires HikariCP; requires shedlock.provider.jdbc.template; requires java.sql; opens net.javacrumbs.shedlockexample to spring.core, spring.beans, spring.context; } open module shedlock.example { requires spring.context; requires spring.jdbc; requires slf4j.api; requires shedlock.core; requires shedlock.spring; requires HikariCP; requires shedlock.provider.jdbc.template; requires java.sql; }

例如:结帐Migrating Your Project to Jigsaw Step by Step

的第5步和第6步
let snackbar = TTGSnackbar(message: "TTGSnackBar !",
                       duration: .middle,
                       actionText: "Action!",
                       actionBlock: { (snackbar) in
        print("Click action!")
})
snackbar.show()

答案 6 :(得分:0)

还有另一种方法(不基于任何黑客手段)在上述任何答案中均未提及。 但是,它仅适用于在类路径上运行的代码。因此,只要需要从classpath运行,任何需要支持在Java 9+上运行的库都可以使用此技术。

基于这样一个事实,即允许在类路径上运行的代码(即从未命名模块中运行)自由动态地打开任何模块的软件包(只能从目标模块本身或未命名模块中完成) )。

例如,给定此代码,访问java.io.Console类的私有字段:

Field field = Console.class.getDeclaredField("formatter");
field.setAccessible(true);

为了不引起警告,我们必须打开目标模块的程序包:

if (!ThisClass.class.getModule().isNamed()) {
    Console.class.getModule().addOpens(Console.class.getPackageName(), ThisClass.class.getModule());
}

我们还添加了一个检查,确认我们确实在类路径上运行。

答案 7 :(得分:0)

我想出了一种在不使用Unsafe也不访问任何未公开的API的情况下禁用该警告的方法。它通过使用反射将FilterOutputStream::out的{​​{1}}字段设置为null来工作。

当然,尝试使用反射实际上会发出我们要抑制的警告,但是我们可以利用并发性来解决此问题:

  1. 锁定System.err,以便其他任何线程都无法写入它。
  2. 生成两个线程,它们在System.err字段上调用setAccessible。尝试显示警告时,其中一个会挂起,而另一个会完成。
  3. out的{​​{1}}字段设置为null并释放对out的锁定。第二个线程现在将完成,但是不会显示警告。
  4. 等待第二个线程结束并恢复System.err的{​​{1}}字段。

以下代码对此进行了说明:

System.err

即使将out设置为“警告”或“调试”,此代码也将起作用,因为对于同一个呼叫者,这些模式不会多次显示警告。

此外,您也可以将其System.err字段设置为自定义OutputStream,而不是还原public void suppressWarning() throws Exception { Field f = FilterOutputStream.class.getDeclaredField("out"); Runnable r = () -> { f.setAccessible(true); synchronized(this) { this.notify(); }}; Object errorOutput; synchronized (this) { synchronized (System.err) //lock System.err to delay the warning { new Thread(r).start(); //One of these 2 threads will new Thread(r).start(); //hang, the other will succeed. this.wait(); //Wait 1st thread to end. errorOutput = f.get(System.err); //Field is now accessible, set f.set(System.err, null); // it to null to suppress the warning } //release System.err to allow 2nd thread to complete. this.wait(); //Wait 2nd thread to end. f.set(System.err, errorOutput); //Restore System.err } } 的原始状态,以便过滤以后的警告。

答案 8 :(得分:-1)

这是对我有用的

-Djdk.module.illegalAccess=deny