95 votes

Comment masquer l'avertissement "Illegal reflective access" en java 9 sans argument JVM ?

Je viens d'essayer d'exécuter mon serveur avec Java 9 et j'ai reçu l'avertissement suivant :

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

Je voudrais masquer cet avertissement sans ajouter --illegal-access=deny aux options de la JVM lors du démarrage. Quelque chose comme :

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

Y a-t-il un moyen de le faire ?

Toutes les réponses relatives suggèrent d'utiliser les options de la JVM, je voudrais désactiver cela à partir du code. Est-ce possible ?

Pour clarifier - ma question concerne la désactivation de cet avertissement à partir du code et non via les arguments/flags de la JVM comme indiqué dans des questions similaires.

0 votes

Je connais cette façon de faire et le problème est déjà signalé. Cependant, j'aimerais désactiver cet avertissement dès maintenant. La correction de ce problème prendra un certain temps.

1 votes

Une fois de plus, je sais que je peux utiliser les drapeaux de la JVM, mais je dois le faire par code. N'ai-je pas été clair ?

3 votes

El HotSpotDiagnosticMXBean permet de modifier certaines options de la JVM. Je ne suis pas sûr que vous puissiez l'utiliser pour cette opération, et si vous le pouviez, il est un peu difficile de le faire en production.

73voto

apangin Points 4693

Il existe des moyens de désactiver l'avertissement d'accès illégal, mais je ne le recommande pas.

1. Une approche simple

Puisque l'avertissement est imprimé dans le flux d'erreurs par défaut, vous pouvez simplement fermer ce flux et rediriger l'information vers l'adresse suivante stderr à stdout .

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

Notes :

  • Cette approche fusionne les flux d'erreur et de sortie. Cela peut ne pas être souhaitable dans certains cas.
  • Vous ne pouvez pas rediriger le message d'avertissement simplement en appelant System.setErr puisque la référence au flux d'erreurs est sauvegardée dans le fichier IllegalAccessLogger.warningStream au début de l'amorçage de la JVM.

2. Approche compliquée sans modification de stderr

Une bonne nouvelle est que sun.misc.Unsafe est toujours accessible dans le JDK 9 sans avertissement. La solution consiste à réinitialiser le code interne IllegalAccessLogger avec l'aide de l'API non sécurisée.

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 votes

jdk.internal.module n'est pas exporté par java.base cependant. Vous pourriez vouloir mettre à jour sa source également.

2 votes

@nullpointer Il n'est pas exporté, mais il n'a pas besoin de l'être pour que l'astuce fonctionne.

0 votes

@apangin merci ! Cela fonctionne effectivement. C'est une bonne solution temporaire pour l'instant (la deuxième, qui cache le logger).

16voto

raphw Points 6008

Il existe une autre option qui ne nécessite pas la suppression des flux et qui ne repose pas sur des API non documentées ou non prises en charge. En utilisant un agent Java, il est possible de redéfinir les modules pour exporter/ouvrir les paquets requis. Le code pour cela ressemblerait à quelque chose comme ceci :

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

Vous pouvez maintenant exécuter n'importe quel accès illégal sans l'avertissement puisque votre application est contenue dans le module sans nom comme par exemple :

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

Afin de mettre la main sur le Instrumentation vous pouvez soit écrire un Agent Java ce qui est assez simple et de le spécifier en ligne de commande (plutôt que dans le classpath) en utilisant -javaagent:myjar.jar . L'agent ne contiendrait qu'un premain comme suit :

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

Alternativement, vous pouvez attacher dynamiquement en utilisant l'API attach qui est rendue accessible de manière pratique par le site byte-buddy-agent projet (dont je suis l'auteur) :

exportAndOpen(ByteBuddyAgent.install());

que vous devrez appeler avant l'accès illégal. Notez que cette fonction n'est disponible que sur les JDK et sur les VM Linux, tandis que vous devrez fournir l'agent Byte Buddy sur la ligne de commande en tant qu'agent Java si vous en avez besoin sur d'autres VM. Cela peut être pratique lorsque vous voulez l'auto-attachement sur les machines de test et de développement où les JDK sont généralement installés.

Comme d'autres l'ont souligné, cela ne devrait servir que de solution intermédiaire, mais je comprends parfaitement que le comportement actuel casse souvent les robots d'exploration et les applications de console. C'est pourquoi je l'ai moi-même utilisé dans des environnements de production comme solution à court terme pour utiliser Java 9 et je n'ai rencontré aucun problème jusqu'à présent.

Ce qui est bien, cependant, c'est que cette solution est robuste face aux futures mises à jour car toute opération, même l'attachement dynamique, est légale. En utilisant un processus auxiliaire, Byte Buddy contourne même l'auto-attachement normalement interdit.

0 votes

Des solutions comme celle-ci, en plus d'être compliquées à mettre en pratique, vont à l'encontre des objectifs de "forte encapsulation" du nouveau système de modules de Java 9. Au lieu de promouvoir de tels bidouillages, nous devrions soit travailler avec les développeurs du JDK d'Oracle pour résoudre les problèmes réels de rétrocompatibilité, soit travailler avec les utilisateurs de nos outils pour qu'ils acceptent les exigences plus strictes du JDK 9+. Par exemple, nous pourrions demander à Oracle d'ajouter une (très raisonnable) java.lang.instrument.InstrumentationFactory afin que l'attachement dynamique ne soit plus nécessaire.

11 votes

Cette discussion a eu lieu pendant plusieurs mois sur la liste de diffusion et a été rejetée. Si vous avez besoin de faire fonctionner un système sous Java 9 et que vous avez un budget limité, vous devez parfois opter pour une solution intermédiaire. Comme elle n'utilise pas d'API non officielles, cette solution n'est pas non plus considérée comme un hack.

0 votes

@Rogério Vous avez tout faux. Les problèmes doivent être résolus un jour, mais il n'y a rien qu'un développeur ordinaire puisse ou doive faire. L'accès illégal se produit généralement dans les bibliothèques, qui fonctionnent bien maintenant et qui ont probablement déjà pris soin de cela : Elles font l'accès illégal pour des raisons d'efficacité et retomber à des moyens légaux quand il échoue. Ils ne seront pas mis à jour à cet égard tant qu'il y aura une chance non négligeable que l'accès illégal fonctionne, c'est-à-dire peut-être pendant quelques décennies. A moins qu'ils ne trouvent un moyen légal efficace....

14voto

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

Cela fonctionne pour moi dans JAVA 11.

1 votes

@AlexByrth Trouvez un moyen de mettre le logger domaine de jdk.internal.module.IllegalAccessLogger à zéro. Jetez un coup d'œil au code source de IllegalAccessLogger.

0 votes

Cela fonctionne pour moi aussi, en utilisant OpenJDK 14 sur ArchLinux, merci beaucoup !

12voto

Nicolai Points 17516

Je ne connais aucun moyen de réaliser ce que vous demandez. Comme vous l'avez souligné, il faudrait ajouter options de ligne de commande ( --add-opens mais pas --illegal-access=deny ) au lancement de la JVM.

Vous avez écrit :

Mon objectif est d'éviter les instructions supplémentaires pour les utilisateurs finaux. Nous avons de nombreux utilisateurs avec nos serveurs installés et cela serait un gros inconvénient pour eux.

À première vue, vos exigences ne permettent que de conclure que le projet n'est pas prêt pour Java 9. Il devrait honnêtement signaler à ses utilisateurs qu'il lui faut un peu plus de temps pour être entièrement compatible avec Java 9. C'est tout à fait normal si tôt après la sortie de la version.

1voto

TheKojuEffect Points 3977

Vous pouvez open paquets dans module-info.java ou créer un open module .

Par exemple : Vérifier les étapes 5 et 6 de Migration de votre projet vers Jigsaw, étape par étape

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

0 votes

Le lien mentionne à nouveau l'utilisation de la ligne de commande, ce que le PO a spécifiquement nié.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X