Vorwort
Die unsichere Klasse wird in mehreren Klassen des JDK -Quellcodes verwendet. Diese Klasse bietet einige zugrunde liegende Funktionen, um die JVM zu umgehen, und ihre Implementierung kann die Effizienz verbessern. Es handelt sich jedoch um ein zweischneidiges Schwert: Wie sein Name vorliegt, ist es unsicher, und das Speicher, das es zuteilt, muss manuell frei sein (nicht von GC recycelt). Die unsichere Klasse bietet eine einfache Alternative zu bestimmten Merkmalen von JNI: Gewährleistung der Effizienz und gleichzeitig einfacher.
Diese Klasse gehört zur Klasse in der Sonne.
In diesem Artikel geht es hauptsächlich um die Zusammenstellung und Übersetzung der folgenden Artikel.
http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
1. Die meisten Methoden der unsicheren API sind native Implementierungen, die aus 105 Methoden bestehen, hauptsächlich die folgenden Kategorien:
(1) Info verwandte. Geben Sie hauptsächlich einige Speicherinformationen auf niedriger Ebene zurück: adressize (), pageSize ()
(2) Objekte verwandt. Geben Sie hauptsächlich Objekte und seine Domänenmanipulationsmethoden an: AllocalInstance (), ObjectFieldOffset ()
(3) Klassenbezogen. Bieten Sie hauptsächlich Klassen- und statische Manipulationsmethoden an: staticfieldOffset (), Definition (), DefineanonynousClass (), sealeclassinitialized ()
(4) Arrays verwandt. Array Manipulationsmethode: ArrayBaseOffset (), ArrayIndexscale ()
(5) Synchronisationsbezogen. Bieten Sie hauptsächlich Synchronisation-Primitive mit niedrigem Niveau (z.
(6) Speicherbezogen. Direkter Speicherzugriffsmethode (umgehen Sie den JVM -Heap und manipulieren Sie den lokalen Speicher direkt): allocatememory (), copymemory (), freememory (), getAddress (), getInt (), putint ()
2. Eine unsichere Klasseninstanz bekommen
Das Design der unsicheren Klassen wird nur dem JVM Contraded Startup Class Loader zur Verfügung gestellt und ist eine typische Singleton -Musterklasse. Die Instanzerfassungsmethode lautet wie folgt:
public static unsicherer GetunSafe () {Klasse cc = sun.reflect.reflection.getCallerClass (2); if (cc.getClassloader ()! = null) werfen neue securityException ("unsicher"); theunsafe zurückgeben;}Ein Klassenlader ohne Start ruft die Methode unsicher.getunsafe () direkt auf und wirft eine Sicherheitsexzeption aus (der spezifische Grund beinhaltet den übergeordneten Lademechanismus der JVM-Klasse).
Es gibt zwei Lösungen. Eine soll die Klasse angeben, die als Startklasse über den JVM -Parameter - XbootClassPath verwendet wird. Die andere Methode ist die Java -Reflexion.
Feld f = unsicher.class.getDeclaredfield ("theunsafe");Indem Sie brutal für die private Singleton -Instanz zugänglich einstellen und dann direkt ein Objekt erhalten, um durch Field's Get -Methode zu unsicher. In der IDE werden diese Methoden als Fehler markiert und können durch die folgenden Einstellungen aufgelöst werden:
Einstellungen -> Java -> Compiler -> Fehler/Warnungen -> veraltete und eingeschränkte API -> Verbotene Referenz -> Warnung
3. "Interessante" Anwendungsszenarien der unsicheren Klasse
(1) Umgehen Sie die Klasseninitialisierungsmethode. Die AllocalInStance () -Methode wird sehr nützlich, wenn Sie Objektkonstruktoren, Sicherheitsprüfer oder Konstrukteure ohne Öffentlichkeit umgehen möchten.
Klasse A {private long a; // kein initialisierter Wert öffentlich a () {this.a = 1; // Initialisierung} public Long a () {return this.a; }}Das Folgende ist ein Vergleich der Konstruktionsmethode, Reflexionsmethode und AllocalInstance ()
A o1 = neu a (); // constructoro1.a (); // druckt 1 a o2 = a.class.newinstance (); // reflectiono2.a (); // druckt 1 a o3 = (a) unsicher.alcocateInstance (a.class); // unsicher. // Drucke 0
AllocalInstance () betritt die Konstruktor -Methode überhaupt nicht, und im Singleton -Modus scheinen wir eine Krise zu sehen.
(2) Speicheränderung
Die Speicheränderung ist in der C -Sprache relativ häufig. In Java kann es verwendet werden, um den Sicherheitsprüfer zu umgehen.
Betrachten Sie die folgenden Regeln für einfache Zugriffsprüfung:
Klasse Guard {private int access_lowed = 1; public boolean giveAccess () {return 42 == Access_alowed; }}Unter normalen Umständen kehrt Giveaccess immer falsch zurück, aber es passiert nicht immer
Guard Guard = New Guard (); Guard.giveAccess (); // false, kein Zugriff // BypasUnsafe unafe = getunSafe (); Feld f = guard.getClass (). getDeclaredfield ("Access_Allowed"); unafe. // Memory Corruption Guard.giveAccess (); // wahr, Zugang gewährtDurch die Berechnung des Speicherversatzes und der Verwendung der Putint () -Methode wird der Access_alow der Klasse geändert. Wenn eine Klassenstruktur bekannt ist, kann der Datenversatz immer berechnet werden (im Einklang mit der Berechnung des Datenversatzberechnung in der Klasse in C ++).
(3) Implementieren Sie die sizeof () -Funktion ähnlich der C -Sprache
Implementieren Sie eine C-ähnliche sizeof () -Funktion, indem Sie die Funktion Java Reflexion und ObjectFof.Offset () kombinieren.
public static Long Sizeof (Objekt o) {unsicher u = getunSafe (); Hashset Fields = new Hashset (); Klasse C = O.GetClass (); while (c! = Object.class) {für (Feld f: cGetDeclaredfields ()) {if ((f.getModifiers () & modifier.static) == 0) {fields.add (f); }} c = c.GetSuperClass (); } // Offset long maxSize = 0; für (Feld f: fields) {long offset = u.ObjectfieldOffset (f); if (offset> maxSize) {maxSize = offset; }} return ((maxSize/8) + 1) * 8; // padding}Die Idee des Algorithmus ist sehr klar: Beginnen Sie von der zugrunde liegenden Unterklasse, nehmen Sie die nicht statischen Domänen von sich selbst und alle ihre Superklassen wieder, platzieren Sie sie in einem Hashset (die wiederholten Berechnungen sind nur einmal, Java ist ein einzelnes Vererbung) und verwenden dann ObjectFieldOffset (), um einen maximalen Offseting zu erhalten, und schließlich in Betracht zu ziehen, und schließlich in Betracht zu ziehen.
In einem 32-Bit-JVM kann die Größe erhalten werden, indem ein langer Longe mit einem Klassendateiversatz von 12 gelesen wird.
public static Long Sizeof (Objektobjekt) {return getunSafe (). getAddress (normalisieren (getunsafe (). getint (Objekt, 4L)) + 12L);}Die Normalize () -Funktion ist eine Methode, die signierte int in unsigned long konvertiert
private static long normalize (intwert) {if (value> = 0) Rückgabewert; return (0L >>> 32) & value;}Die Größe der Zweigröße () berechnet ist gleich. Die am meisten standardmäßige Größe () implementiert ist die Verwendung von Java.lang.Instrument. Es müssen jedoch den Befehlszeilenparameter -Javaagent angegeben werden.
(4) Umsetzung einer flachen Java -Replikation
Das Standard-sachliche Replikationsschema besteht darin, die klonbare Schnittstelle oder die von selbst implementierten Replikationsfunktionen zu implementieren, und es handelt sich nicht um Mehrzweckfunktionen. Durch die Kombination der Größe der Größe () kann das flache Kopieren erreicht werden.
statische Objekt flachkopie (Objekt obj) {Long Size = sizeof (obj); langer Start = Toaddress (OBJ); lange Adresse = GetunSafe (). Allocatememory (Größe); GetunSafe (). Copymemory (Start, Adresse, Größe); Kehren Sie von Address (Adresse) zurück;}Die folgenden Toaddress () und vonaddress () konvertieren das Objekt in seine Adresse bzw. den umgekehrten Betrieb.
static Long Toaddress (Objekt obj) {Object [] array = new Object [] {obj}; long baseOffset = getunSafe (). arrayBaseOffset (Objekt []. Klasse); return normalize (getunsafe (). getint (array, baseOffset));} statisches Objekt vonaddress (lange Adresse) {Objekt [] array = new Object [] {null}; long baseOffset = getunSafe (). arrayBaseOffset (Objekt []. Klasse); GetunSafe (). Putlong (Array, BaseOffset, Adresse); Array zurückgeben [0];}Die obige flache Kopiefunktion kann auf jedes Java -Objekt angewendet werden, und ihre Größe wird dynamisch berechnet.
(5) Kennwörter im Speicher beseitigen
Kennwortfelder werden in String gespeichert, das String -Recycling wird jedoch vom JVM verwaltet. Der sicherste Weg besteht darin, das Feld Passwort nach der Verwendung zu überschreiben.
Field StringValue = string.class.getDeclaredField ("value"); StringValue.setAccessible (true); char [] mem = (char []) StringValue.get (Passwort); für (int i = 0; i <mem.Length; i ++) {mem [i] = ';(6) Dynamische Belastung von Klassen
Die Standardmethode zum dynamischen Laden von Klassen ist Class.forname () (beim Schreiben von JDBC -Programmen erinnere ich mich tief daran). Unsicher kann auch Java -Klassendateien dynamisch laden.
byte [] classcontents = getClassContent (); Klasse C = getunSafe (). Definition (Null, Klassenkontents, 0, Klassenkontents.Length); C.GetMethod ("a"). Invoke (C.Newinstance (), NULL); // 1GetClassContent () -Methode liest eine Klassendatei zu einem Byte -Array. private static byte [] getClassContent () löst eine Ausnahme aus (Datei f = neue Datei ("/home/mishadoff/tmp/a.class"); FileInputStream input = new FileInputStream (f); byte [] content = new byte [(int) f.length ()]; input.read (Inhalt); input.close (); Inhalt zurückgeben;}Es kann in dynamischer Belastung, Proxyung, Schneiden und anderen Funktionen angewendet werden.
(7) Die Ausnahme von Paketerkennung ist eine Laufzeitausnahme.
GetunSafe (). ThrowException (new ioException ());
Dies kann erfolgen, wenn Sie die geprüfte Ausnahme nicht fangen möchten (nicht empfohlen).
(8) Schnelle Serialisierung
Der serialisierbare Standard -Java ist sehr langsam und begrenzt auch, dass die Klasse einen öffentlichen parameterlosen Konstruktor haben muss. Externalisierbar ist besser, es muss ein Schema für die Serialisierung der Klasse angeben. Populäre effiziente Serialisierungsbibliotheken wie Kryo, die sich auf Bibliotheken von Drittanbietern verlassen, erhöhen den Speicherverbrauch. Sie können den tatsächlichen Wert der Domäne in der Klasse über getInt (), getlong (), getObject () und andere Methoden und anhaltende Informationen wie Klassenname in der Datei zusammen erhalten. Kryo hat versucht, unsicher zu verwenden, aber es gibt keine spezifischen Daten zur Leistungsverbesserung. (http://code.google.com/p/kryo/issues/detail?id=75)
(9) Speicher in Nicht-Java-Haufen Speicher zuweisen
Neu verwenden Java wird Speicher für Objekte im Haufen zuweisen, und der Lebenszyklus des Objekts wird von JVM GC verwaltet.
Klasse SuperArray {private endgültige statische int byte = 1; private lange Größe; private lange Adresse; public SuperArray (lange Größe) {this.size = Größe; address = getunsafe (). allocatememory (Größe * byte); } public void set (long i, byte value) {getunSafe (). PutByte (Adresse + i * Byte, Wert); } public int get (Long idx) {return getunSafe (). getByte (Adresse + idx * byte); } public Long Size () {Return Size; }}Der von UnSafe zugewiesene Speicher ist nicht durch Integer.max_value begrenzt und wird im Nicht-Haap-Speicher zugeordnet. Wenn Sie es verwenden, müssen Sie sehr vorsichtig sein: Wenn Sie vergessen, es manuell zu recyceln, treten Speicherlecks auf. Wenn Sie illegaler Zugang zu Adressen haben, wird der JVM zum Absturz gebracht. Es kann verwendet werden, wenn Sie große kontinuierliche Bereiche und eine Echtzeitprogrammierung (nicht tolerieren JVM-Latenz) zuweisen müssen. Java.nio verwendet diese Technologie.
(10) Anwendungen in der Parallelität von Java
Durch die Verwendung von unafe.comPareAndswap () kann es zur Implementierung effizienter lock-freie Datenstrukturen verwendet werden.
Klasse Cascounter implementiert Zähler {private flüchtige Longzählung = 0; privat unsicher unsicher; Privat langer Offset; public Cascounter () löst eine Ausnahme aus {unafe = getunsafe (); offset = unafe.objectfieldOffset (cascounter.class.getDeclaredfield ("counter")); } @Override public void Increment () {lange vor = counter; while (! unafe.comPareAndswaplong (dies, Offset vor, vor, vor + 1)) {vor = counter; }} @Override public long getcounter () {return counter; }}Durch Tests entspricht die obige Datenstruktur im Grunde genommen der Effizienz von Java -Atomvariablen. Java -Atomvariablen verwenden auch die Vergleichs -Vergleichsmethode von Unsicherheit, und diese Methode entspricht schließlich den entsprechenden Primitiven der CPU, so dass sie sehr effizient ist. Hier finden Sie eine Lösung zum Implementieren von Lock-Free-HashMap (http://www.azulsystems.com/about_us/presentations/lock-free-hash. Die Idee dieser Lösung lautet: Analysieren Sie jeden Zustand, erstellen Sie Kopien, ändern Sie Kopien, verwenden Sie CAS-Primitive, Spin-Locken). In gewöhnlichen Servermaschinen (Core <32) wurde mithilfe von Concurrenthashmap (vor JDK8 wurde das Standard-16-Kanal-Trennungsschloss implementiert, und die ConcurrenthashMap wurde mithilfe von lock-frei implementiert) offensichtlich ausreichend.
Zusammenfassen
Das obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels einen gewissen Referenzwert für das Studium oder die Arbeit eines jeden hat. Wenn Sie Fragen haben, können Sie eine Nachricht zur Kommunikation überlassen. Vielen Dank für Ihre Unterstützung bei Wulin.com.