Ich habe gerade versucht, meinen Server mit Java 9 auszuführen, und habe die nächste Warnung erhalten:
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
Ich möchte diese Warnung ausblenden, ohne sie --illegal-access=deny
beim Start zu den JVM-Optionen hinzuzufügen . Etwas wie:
System.setProperty("illegal-access", "deny");
Gibt es eine Möglichkeit, das zu tun?
Alle zugehörigen Antworten, die die Verwendung von JVM-Optionen vorschlagen, möchte ich aus dem Code deaktivieren. Ist das möglich?
Zur Verdeutlichung: Bei meiner Frage geht es darum, diese Warnung aus dem Code zu entfernen und nicht über JVM-Argumente / Flags, wie in ähnlichen Fragen angegeben.
HotSpotDiagnosticMXBean
ermöglicht es, einige JVM - Optionen zu ändern. Ich bin mir nicht sicher, ob Sie es für dieses verwenden können, und wenn Sie könnten, ist es etwas zweifelhaft, dies in der Produktion zu tun.Antworten:
Es gibt Möglichkeiten, die Warnung vor illegalem Zugriff zu deaktivieren, obwohl ich dies nicht empfehle.
1. Einfacher Ansatz
Da die Warnung an den Standardfehlerstrom gedruckt wird, können Sie einfach schließen dieser Strom und Umleitung
stderr
zustdout
.public static void disableWarning() { System.err.close(); System.setErr(System.out); }
Anmerkungen:
System.setErr
, da der Verweis auf den FehlerstromIllegalAccessLogger.warningStream
frühzeitig beim JVM-Bootstrap im Feld gespeichert wird .2. Komplizierter Ansatz ohne Änderung von stderr
Eine gute Nachricht ist, dass
sun.misc.Unsafe
in JDK 9 weiterhin ohne Warnungen zugegriffen werden kann. Die Lösung besteht darin, das interneIllegalAccessLogger
mithilfe der unsicheren API zurückzusetzen.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 } }
quelle
--add-opens
Option zu übergeben (CLI, JAR-Manifest von ausführbaren JARs, env-Variablen).protected
Mitglieder der offiziellen API, erhalten eine Warnung, der Zugriffsun.misc.Unsafe
jedoch nicht? Ok, habe die Nachricht erhalten, benutze Reflection nicht mehr (außer zum ErhaltentheUnsafe
) und benutze immerUnsafe
. Dann müssen wir die Warnung nicht einmal ausschalten ...--add-opens
programmgesteuert ausführen , z. B. müssenTarget.class.getModule().addOpens(Target.class.getPackage(), MyClass.class.getModule());
Sie dies für jedes Paket wiederholen, auf das Sie zugreifen möchten.Es gibt eine weitere Option, für die keine Stream-Unterdrückung erforderlich ist und die nicht auf undokumentierten oder nicht unterstützten APIs basiert. Mit einem Java-Agenten können Module neu definiert werden, um die erforderlichen Pakete zu exportieren / zu öffnen. Der Code dafür würde ungefähr so aussehen:
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() )); }
Sie können jetzt jeden illegalen Zugriff ohne Warnung ausführen, da Ihre Anwendung im unbenannten Modul enthalten ist, wie zum Beispiel:
Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class); method.setAccessible(true);
Um an die
Instrumentation
Instanz zu gelangen, können Sie entweder einen recht einfachen Java-Agenten schreiben und ihn in der Befehlszeile (anstelle des Klassenpfads) angeben-javaagent:myjar.jar
. Der Agent würde nur einepremain
Methode wie folgt enthalten:public class MyAgent { public static void main(String arg, Instrumentation inst) { exportAndOpen(inst); } }
Alternativ können Sie befestigen dynamisch die anhängen API , die bequem durch zugänglich gemacht wird die Byte-Buddy-Agent - Projekt (die ich verfasst):
die Sie vor dem illegalen Zugriff anrufen müssten. Beachten Sie, dass dies nur auf JDKs und auf Linux-VMs verfügbar ist, während Sie den Byte Buddy-Agenten in der Befehlszeile als Java-Agenten angeben müssten, wenn Sie ihn auf anderen VMs benötigen würden. Dies kann praktisch sein, wenn Sie die Selbstanbringung auf Test- und Entwicklungsmaschinen wünschen, auf denen normalerweise JDKs installiert sind.
Wie andere betonten, sollte dies nur als Zwischenlösung dienen, aber ich verstehe voll und ganz, dass das aktuelle Verhalten häufig die Protokollierung von Crawlern und Konsolen-Apps beeinträchtigt, weshalb ich dies selbst in Produktionsumgebungen als kurzfristige Lösung für die Verwendung von Java 9 und verwendet habe so lange hatte ich keine probleme.
Das Gute ist jedoch, dass diese Lösung gegenüber zukünftigen Updates robust ist, da jeder Vorgang, selbst der dynamische Anhang, legal ist. Mit einem Hilfsprozess umgeht Byte Buddy sogar die normalerweise verbotene Selbstanhaftung.
quelle
java.lang.instrument.InstrumentationFactory
Klasse hinzuzufügen, damit kein dynamischer Anhang mehr benötigt wird.Ich kenne keine Möglichkeit, das zu erreichen, wonach Sie fragen. Wie Sie bereits erwähnt haben, müssten Sie dem JVM-Start Befehlszeilenoptionen hinzufügen (
--add-opens
jedoch nicht--illegal-access=deny
).Sie schrieben:
Wie es aussieht, lassen Ihre Anforderungen nur den Schluss zu, dass das Projekt nicht für Java 9 bereit ist. Es sollte seinen Benutzern ehrlich mitteilen, dass es etwas länger dauert, bis es vollständig Java 9-kompatibel ist. Das ist so früh nach der Veröffentlichung völlig in Ordnung.
quelle
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(); } }
Es funktioniert für mich in JAVA 11.
quelle
logger
Feld vonjdk.internal.module.IllegalAccessLogger
auf null zu setzen. Schauen Sie sich den Quellcode von IllegalAccessLogger an.Es gibt eine andere Methode, die nicht auf Hacks basiert und in keiner der obigen Antworten erwähnt wurde. Es funktioniert jedoch nur für Code, der im Klassenpfad ausgeführt wird. Daher kann jede Bibliothek, die die Ausführung unter Java 9+ unterstützen muss, diese Technik verwenden, sofern sie über den Klassenpfad ausgeführt wird.
Es basiert auf der Tatsache, dass Code, der auf Klassenpfad (dh vom unbenannten Modul) ausgeführt wird, Pakete eines Moduls frei dynamisch öffnen kann (dies kann nur vom Zielmodul selbst oder vom unbenannten Modul aus erfolgen).
Wenn Sie beispielsweise diesen Code verwenden, greifen Sie auf ein privates
java.io.Console
Klassenfeld zu:Field field = Console.class.getDeclaredField("formatter"); field.setAccessible(true);
Um die Warnung nicht auszulösen, müssen wir das Paket des Zielmoduls für unser Modul öffnen:
if (!ThisClass.class.getModule().isNamed()) { Console.class.getModule().addOpens(Console.class.getPackageName(), ThisClass.class.getModule()); }
Wir haben auch eine Überprüfung hinzugefügt, dass wir tatsächlich auf Klassenpfad laufen.
quelle
Falls jemand die Protokollnachrichten umleiten möchte, anstatt sie zu verwerfen, funktioniert dies für mich in Java 11. Es ersetzt den Stream, in den der Logger für illegalen Zugriff schreibt.
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) { } } }
quelle
Sie können
open
Pakete einbindenmodule-info.java
oder ein erstellenopen module
.Zum Beispiel: Kasse Schritt 5 und 6 der schrittweisen Migration Ihres Projekts zu Jigsaw
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; }
quelle
Hier ist, was für mich funktioniert hat
-Djdk.module.illegalAccess=deny
quelle
Ich habe eine Möglichkeit gefunden, diese Warnung zu deaktivieren, ohne Unsafe zu verwenden oder auf undokumentierte APIs zuzugreifen. Es funktioniert mit Reflection, um das
FilterOutputStream::out
Feld vonSystem.err
auf null zu setzen.Wenn Sie versuchen, Reflection zu verwenden, wird natürlich die Warnung ausgegeben, die wir unterdrücken möchten, aber wir können die Parallelität ausnutzen, um dies zu umgehen:
System.err
damit kein anderer Thread darauf schreiben kann.setAccessible
dasout
Feld aufrufen . Einer von ihnen bleibt hängen, wenn versucht wird, die Warnung anzuzeigen, der andere wird abgeschlossen.out
Feld vonSystem.err
auf null und lassen Sie die Sperre aufSystem.err
. Der zweite Thread wird nun abgeschlossen, es wird jedoch keine Warnung angezeigt.out
Feld von wieder herSystem.err
.Der folgende Code demonstriert dies:
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 } }
Dieser Code funktioniert auch dann, wenn
--illegal-access
"warn" oder "debug" eingestellt ist, da diese Modi die Warnung für denselben Anrufer nur einmal anzeigen.Anstatt den ursprünglichen Status von wiederherzustellen
System.err
, können Sie dasout
Feld auch auf einen benutzerdefinierten OutputStream festlegen, um zukünftige Warnungen zu filtern.quelle