Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to hide warning "Illegal reflective access" in java 9 without JVM argument?

I just tried to run my server with Java 9 and got next warning:

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

I would like to hide this warning without adding --illegal-access=deny to JVM options during start. Something like:

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

Is there any way to do that?

All related answers suggesting to use JVM options, I would like to turn off this from code. Is that possible?

To clarify - my question is about turning this warning from the code and not via JVM arguments/flags as stated in similar questions.

like image 223
Dmitriy Dumanskiy Avatar asked Sep 27 '17 18:09

Dmitriy Dumanskiy


People also ask

How can you avoid warning an illegal reflective access operation has occurred?

Avoid illegal reflective accessUse a compatible and certified version of your software. If the software is actively maintained, then the developers are now aware of the warning and a fix may come in the near future.

What is illegal reflective access?

The "an illegal reflective access operation has occurred" is merely a warning message and only indicating there is reflective access to JDK internal class without doing anything about it. In the meantime, you can either. You can specify the reflective access explicitly using java's --add-opens parameter.

What is -- illegal access permit?

The default mode, --illegal-access=permit , is intended to make you aware of code on the class path that reflectively accesses any JDK-internal APIs at least once. To learn about all such accesses, you can use the warn or the debug modes.


4 Answers

There are ways to disable illegal access warning, though I do not recommend doing this.

1. Simple approach

Since the warning is printed to the default error stream, you can simply close this stream and redirect stderr to stdout.

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

Notes:

  • This approach merges error and output streams. That may not be desirable in some cases.
  • You cannot redirect warning message just by calling System.setErr, since the reference to error stream is saved in IllegalAccessLogger.warningStream field early at JVM bootstrap.

2. Complicated approach without changing stderr

A good news is that sun.misc.Unsafe can be still accessed in JDK 9 without warnings. The solution is to reset internal IllegalAccessLogger with the help of Unsafe 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
    }
}
like image 102
apangin Avatar answered Oct 18 '22 22:10

apangin


There is another option that does not come with any need for stream suppression and that does not rely on undocumented or unsupported APIs. Using a Java agent, it is possible to redefine modules to export/open the required packages. The code for this would look something like this:

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

You can now run any illegal access without the warning as your application is contained in the unnamed module as for example:

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

In order to get hold of the Instrumentation instance, you can either write a Java agent what is quite simple and specify it on the command line (rather than the classpath) using -javaagent:myjar.jar. The agent would only contain an premain method as follows:

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

Alternatively, you can attach dynamically using the attach API which is made accessible conveniently by the byte-buddy-agent project (which I authored):

exportAndOpen(ByteBuddyAgent.install());

which you would need to call prior to the illegal access. Note that this is only available on JDKs and on Linux VM whereas you would need to supply the Byte Buddy agent on the command line as a Java agent if you needed it on other VMs. This can be convenient when you want the self-attachment on test and development machines where JDKs are typically installed.

As others pointed out, this should only serve as an intermediate solution but I fully understand that the current behavior often breaks logging crawlers and console apps which is why I have used this myself in production environments as a short-term solution to using Java 9 and so long I did not encounter any problems.

The good thing, however, is that this solution is robust towards future updates as any operation, even the dynamic attachment is legal. Using a helper process, Byte Buddy even works around the normally forbidden self-attachment.

like image 29
Rafael Winterhalter Avatar answered Oct 18 '22 22:10

Rafael Winterhalter


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

It works for me in JAVA 11.

like image 14
Gan Avatar answered Oct 18 '22 22:10

Gan


I know of no way to achieve what you are asking for. As you have pointed out, you would need to add command line options (--add-opens, though, not --illegal-access=deny) to the JVM launch.

You wrote:

My goal is to avoid the additional instructions for end users. We have many users with our servers installed and that would be a big inconvenience for them.

By the looks of it, your requirements only leave the conclusion that the project is not ready for Java 9. It should honestly report to its users that it takes a little more time to be fully Java 9 compatible. That's totally ok this early after the release.

like image 12
Nicolai Parlog Avatar answered Oct 19 '22 00:10

Nicolai Parlog