Préface
La classe dangereuse est utilisée dans plusieurs classes du code source JDK. Cette classe fournit des fonctions sous-jacentes pour contourner le JVM, et sa mise en œuvre peut améliorer l'efficacité. Cependant, c'est une épée à double tranchant: comme son nom préfigurait, il est dangereux et la mémoire qu'il alloue doit être manuellement libre (non recyclée par GC). Classe dangereuse, fournit une alternative simple à certaines caractéristiques de JNI: assurer l'efficacité tout en facilitant les choses.
Cette classe appartient à la classe du soleil. * API, et ce n'est pas une partie réelle de J2SE, vous ne trouverez donc pas de documentation officielle, et malheureusement, il n'a pas non plus de documentation de code.
Cet article concerne principalement la compilation et la traduction des articles suivants.
http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
1. La plupart des méthodes de l'API non sûre sont des implémentations natives, qui comprennent 105 méthodes, incluant principalement les catégories suivantes:
(1) Informations liées. Renvoie principalement certaines informations de mémoire de bas niveau: AddressSize (), PageSize ()
(2) les objets liés. Fournir principalement des méthodes de manipulation du domaine et de ses méthodes: allocateInstance (), objectFieldOffset ()
(3) lié à la classe. Fournir principalement la classe et ses méthodes de manipulation de domaine statique: staticFieldoffset (), definClass (), DefianonyMoSClass (), EnsureClassInitialized ()
(4) les tableaux liés. Méthode de manipulation du tableau: ArrayBaseOffSet (), ArrayIndexScale ()
(5) lié à la synchronisation. Fournissent principalement les primitives de synchronisation de faible niveau (comme les primitives CAS basées sur le processeur (compare-et-swap)): moniteur (), tryMonitable (), Monitorexit (), CompareAndSwapint (), putOrDreInt ()
(6) lié à la mémoire. Méthode d'accès à la mémoire directe (contourner le tas JVM et manipuler directement la mémoire locale): allocateMemory (), copymemory (), freeMemory (), getAddress (), getInt (), Pount ()
2. Obtenir une instance de classe dangereuse
La conception de classe dangereuse n'est fournie qu'au chargeur de classe de démarrage de confiance JVM et est une classe de motifs singleton typique. Sa méthode d'acquisition d'instance est la suivante:
public static dangetaSaSafe () {class cc = sun.reflect.reflection.getCallerClass (2); if (cc.GetClassOLODOader ()! = null) New SecurityException ("Usare"); retourner theunsafe;}Un chargeur de classe non starte appellera directement la méthode davantage.getUnSafe () et lancera une sécurité SecurityException (la raison spécifique implique le mécanisme de chargement parent de la classe JVM).
Il existe deux solutions. L'une consiste à spécifier la classe à utiliser comme classe de démarrage via le paramètre JVM - XbootClassPath. L'autre méthode est la réflexion Java.
Field f = unsetafe.class.getDeclaredField ("theunsafe"); f.setAccessible (true); dangetafe dangeret = (danget) f.get (null);En définissant brutalement accessible à TRUE pour l'instance Singleton privée, puis obtenez directement un moulage d'objet pour danger par la méthode Get de Field. Dans l'IDE, ces méthodes seront marquées comme une erreur et peuvent être résolues par les paramètres suivants:
Préférences -> Java -> Compiler -> Erreurs / avertissements -> API obsolète et restreint -> Référence interdite -> AVERTISSEMENT
3. Scénarios d'application "intéressants" de classe dangereuse
(1) contourner la méthode d'initialisation de la classe. La méthode AllocationInstance () devient très utile lorsque vous souhaitez contourner les constructeurs d'objets, les vérificateurs de sécurité ou les constructeurs sans public.
classe A {private long a; // non initialisé Valeur public a () {this.a = 1; // initialisation} public long a () {return this.a; }}Ce qui suit est une comparaison de la méthode de construction, de la méthode de réflexion et de l'allocationInstance ()
A o1 = new a (); // constructoro1.a (); // imprime 1 a o2 = a.class.newinstance (); // réflexiono2.a (); // imprime 1 a o3 = (a) danget. // dangeo3.a (); // imprime 0
AllocationInstance () n'entre pas du tout la méthode du constructeur, et en mode singleton, nous semblons voir une crise.
(2) modification de la mémoire
La modification de la mémoire est relativement courante dans le langage C. En Java, il peut être utilisé pour contourner le vérificateur de sécurité.
Considérez les règles de vérification d'accès simple suivantes:
classe Garde {private int Access_Allowed = 1; public boolean giveAccess () {return 42 == Access_allowed; }}Dans des circonstances normales, GiveAccess revient toujours faux, mais cela ne se produit pas toujours
Guard Guard = new Gard (); Gard.GiveAccess (); // false, pas d'accès // BYPASSUNSAFE UNSAFE = GetunSafe (); champ f = gard.getClass (). GetDeclaredField ("Access_Allowed"); Unsetafe.putin (gardien, unpare.ObjectFieldOffset (F), 42); // Memory Corruption Guard.GiveAccess (); // vrai, accès accordéEn calculant le décalage de la mémoire et en utilisant la méthode Pount (), l'accès_allowed de la classe est modifié. Lorsqu'une structure de classe est connue, le décalage des données peut toujours être calculé (cohérent avec le calcul du décalage des données dans la classe en C ++).
(3) implémenter la fonction SIZEOF () similaire à la langue C
Implémentez une fonction sizeof () de type C en combinant la réflexion Java et la fonction objectFieldOffset ().
SIME STATIQUE STATIQUE PUBLIQUE (Objet O) {UsAve U = GetunSafe (); HashSet Fields = new HashSet (); Classe c = o.getClass (); while (c! = object.class) {for (field f: c.getDeclaredFields ()) {if ((f.getModifiers () & modificier.Static) == 0) {Fields.Add (f); }} c = c.getsuperclass (); } // Get Offset Long MaxSize = 0; pour (champ f: fields) {long offset = u.objectFieldOffset (f); if (offset> maxSize) {maxSize = offset; }} return ((maxsize / 8) + 1) * 8; // rembourrage}L'idée de l'algorithme est très claire: commencez à partir de la sous-classe sous-jacente, retirez les domaines non statiques de lui-même et toutes ses superclasses à leur tour, placent-les dans un hashset (les calculs répétés ne sont qu'une seule fois, Java est un héritage unique), puis utilisez ObjectFieldoffset () pour obtenir un décalage maximum, et enfin considérer le alignement.
Dans un JVM 32 bits, la taille peut être obtenue en lisant un long avec un décalage de fichier de classe de 12.
public static long sizeof (objet objet) {return getunsafe (). getAddress (normaliser (getunsafe (). getInt (objet, 4l)) + 12l);}La fonction normalisée () est une méthode qui convertit INT signé en unsigne
Normalisation longue statique privée (Int Value) {if (valeur> = 0) Valeur de retour; return (0l >>> 32) & valeur;}La taille des deux taille () calculée est la même. L'implémentation de taille la plus standard () consiste à utiliser java.lang.instrument, cependant, il nécessite de spécifier le paramètre de ligne de commande -javaagent.
(4) Mise en œuvre de la réplication de Java peu profonde
Le schéma de réplication superficiel standard consiste à implémenter l'interface clonable ou les fonctions de réplication implémentées par elle-même, et ce ne sont pas des fonctions polyvalentes. En combinant la méthode Tailleof (), une copie peu profonde peut être réalisée.
objet statique ShallowCopy (objet obj) {Long Size = sizeof (obj); Long Start = Toaddress (OBJ); Adresse longue = getunsafe (). allocateMemory (taille); getunsafe (). Copymemory (démarrage, adresse, taille); Retour FromAddress (adresse);}Les Toaddress () et FromAddress () suivants convertissent l'objet à son adresse et à l'opération inverse respectivement.
Toaddress longue statique (objet obj) {objet [] array = nouvel objet [] {obj}; long basoffset = getunsafe (). arrayBaseOffSet (objet []. classe); return normalize (getunsafe (). getInt (array, basationoffset));} objet statique fromAddress (adresse longue) {objet [] array = nouvel objet [] {null}; long basoffset = getunsafe (). arrayBaseOffSet (objet []. classe); getunsafe (). putLong (tableau, baseoffset, adresse); Return tableau [0];}La fonction de copie peu profonde ci-dessus peut être appliquée à n'importe quel objet Java, et sa taille est calculée dynamiquement.
(5) Éliminer les mots de passe en mémoire
Les champs de mot de passe sont stockés en chaîne, cependant, le recyclage des chaînes est géré par le JVM. Le moyen le plus sûr consiste à écraser le champ de mot de passe après son utilisation.
Champ stringvalue = string.class.getDeclaredField ("Value"); stringValue.SetAccessible (true); char [] mem = (char []) stringValue.get (mot de passe); pour (int i = 0; i <mem.length; i ++) {mem [i] = '?';};(6) Chargement dynamique des classes
La méthode standard de chargement dynamiquement des classes est class.forname () (lors de l'écriture de programmes JDBC, je m'en souviens profondément). Dynamique peut également charger dynamiquement les fichiers de classe Java.
Byte [] classContents = getClassContent (); classe C = getunsafe (). DefinClass (null, classContents, 0, classContents.length); c.getMethod ("a"). invoke (c.newinstance (), null); // La méthode 1getClassContent () lit un fichier de classe sur un tableau d'octets. octet statique privé [] getClassContent () lève une exception {file f = new File ("/ home / mishadoff / tmp / a.class"); FileInputStream Input = new FileInputStream (F); Byte [] contenu = nouveau octet [(int) f.length ()]; input.read (contenu); input.close (); retourner le contenu;}Il peut être appliqué dans le chargement dynamique, la proxyation, le tranchage et d'autres fonctions.
(7) L'exception de détection du package est une exception d'exécution.
getunsafe (). ThrowException (new ioException ());
Cela peut être fait lorsque vous ne souhaitez pas attraper l'exception vérifiée (non recommandée).
(8) sérialisation rapide
La série Java standard est très lente et limite également que la classe doit avoir un constructeur public sans paramètre. L'externalisable est mieux, il doit spécifier un schéma pour que la classe soit sérialisée. Les bibliothèques de sérialisation efficaces populaires, telles que Kryo, qui s'appuient sur des bibliothèques tierces, augmenteront la consommation de mémoire. Vous pouvez obtenir la valeur réelle du domaine dans la classe via getInt (), getLong (), getObject () et d'autres méthodes, et persister des informations telles que le nom de classe dans le fichier ensemble. Kryo a tenté d'utiliser dangereux, mais il n'y a pas de données spécifiques d'amélioration des performances. (http://code.google.com/p/kryo/issues/detail?id=75)
(9) allouer la mémoire en tas non java
La nouvelle utilisation de Java alloue la mémoire aux objets dans le tas, et le cycle de vie de l'objet sera géré par JVM GC.
classe SuperArray {private final static int byte = 1; taille longue privée; Adresse longue privée; public superArray (taille longue) {this.size = size; adresse = getunsafe (). allocateMemory (taille * octet); } public void set (long i, valeur d'octet) {getunsafe (). putByte (adresse + i * octet, valeur); } public int get (long idx) {return getunsafe (). getByte (adresse + idx * byte); } public long size () {return size; }}La mémoire allouée par dangereuse n'est pas limitée par Integer.max_value et est allouée à la mémoire non tas. Lorsque vous l'utilisez, vous devez être très prudent: si vous oubliez de le recycler manuellement, des fuites de mémoire se produiront; Si votre accès à l'adresse illégale, cela entraînera le plantage de la JVM. Il peut être utilisé lorsque vous devez allouer de grandes zones continues, programmation en temps réel (et non tolérer la latence JVM). Java.nio utilise cette technologie.
(10) Applications en concurrence Java
En utilisant unpareAndWap (), il peut être utilisé pour implémenter des structures de données sans verrouillage efficaces.
classe Cascounter implémente le compteur {privé volatile long compteur = 0; privé dangereux dangereux; décalage long privé; public Cascounter () lève une exception {disAve = getunsafe (); offset = unsetafe.ObjectFieldOffset (Cascounter.class.getDeclaredField ("Counter")); } @Override public void incment () {bien avant = compteur; tandis que (! UnsAve.CompareAndWaplong (ceci, offset, avant, avant + 1)) {avant = compteur; }} @Override public long getCounter () {return compteur; }}Grâce aux tests, la structure de données ci-dessus est fondamentalement la même que l'efficacité des variables atomiques Java. Les variables atomiques de Java utilisent également la méthode comparative de comparaison de dangendswap (), et cette méthode correspondra finalement aux primitives correspondantes du CPU, il est donc très efficace. Voici une solution pour implémenter HashMap sans verrouillage (http://www.azulsystems.com/about_us/presentations/lock-free-hash. L'idée de cette solution est: analyser chaque état, créer des copies, modifier des copies, utiliser les primitives CAS, les verrous de spin). Dans les machines de serveur ordinaires (Core <32), en utilisant ConcurrentHashMap (avant JDK8, le verrouillage de séparation par défaut à 16 canaux a été implémenté, et ConcurrentHashMap a été implémenté à l'aide de Lock-Free) est évidemment suffisant.
Résumer
Ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article a une certaine valeur de référence pour l'étude ou le travail de chacun. Si vous avez des questions, vous pouvez laisser un message pour communiquer. Merci pour votre soutien à wulin.com.