Warum gibt es sun.misc.Unsafe und wie kann es in der realen Welt verwendet werden? [geschlossen]

267

Ich bin neulich auf das Paket sun.misc.Unsafe gestoßen und war erstaunt, was es tun kann.

Natürlich ist die Klasse nicht dokumentiert, aber ich habe mich gefragt, ob es jemals einen guten Grund gibt, sie zu verwenden. Welche Szenarien könnten auftreten, wenn Sie es verwenden müssten? Wie könnte es in einem realen Szenario verwendet werden?

Wenn Sie es brauchen, bedeutet das nicht, dass wahrscheinlich etwas mit Ihrem Design nicht stimmt?

Warum enthält Java diese Klasse überhaupt?

pdeva
quelle
7
Die JDK-Entwickler überprüfen diese API derzeit auf mögliche Umwandlung in eine öffentliche API in Java 9. Wenn Sie sie verwenden, lohnt es sich, 5 Minuten zum Ausfüllen der Umfrage zu benötigen : Surveymonkey.com/s/sun-misc-Unsafe .
Andy Lynch
2
Dieser Beitrag wird auf Meta diskutiert: meta.stackoverflow.com/questions/299139/…
Jon Clements

Antworten:

159

Beispiele

  1. VM "Intrinsification". dh CAS (Compare-And-Swap), das in sperrenfreien Hash-Tabellen verwendet wird, z. B.: sun.misc.Unsafe.compareAndSwapInt, kann echte JNI-Aufrufe in nativen Code ausführen, der spezielle Anweisungen für CAS enthält

    Lesen Sie hier mehr über CAS http://en.wikipedia.org/wiki/Compare-and-swap

  2. Die sun.misc.Unsafe-Funktionalität der Host-VM kann verwendet werden, um nicht initialisierte Objekte zuzuweisen und den Konstruktoraufruf dann wie jeden anderen Methodenaufruf zu interpretieren.

  3. Man kann die Daten von der nativen Adresse aus verfolgen. Es ist möglich, die Speicheradresse eines Objekts mit der Klasse java.lang.Unsafe abzurufen und seine Felder direkt über unsichere get / put-Methoden zu bearbeiten!

  4. Kompilieren Sie Zeitoptimierungen für JVM. Hochleistungs-VM mit "Magie", die Operationen auf niedriger Ebene erfordert. Beispiel: http://en.wikipedia.org/wiki/Jikes_RVM

  5. Zuweisen von Speicher, sun.misc.Unsafe.allocateMemory zB: - Der DirectByteBuffer-Konstruktor ruft ihn intern auf, wenn ByteBuffer.allocateDirect aufgerufen wird

  6. Verfolgen des Aufrufstapels und Wiedergeben mit Werten, die von sun.misc.Unsafe instanziiert wurden, nützlich für die Instrumentierung

  7. Mit sun.misc.Unsafe.arrayBaseOffset und arrayIndexScale können Arraylets entwickelt werden, eine Technik zum effizienten Aufteilen großer Arrays in kleinere Objekte, um die Echtzeitkosten für Scan-, Aktualisierungs- oder Verschiebungsvorgänge an großen Objekten zu begrenzen

  8. http://robaustin.wikidot.com/how-to-write-to-direct-memory-locations-in-java

Mehr zu Referenzen hier - http://bytescrolls.blogspot.com/2011/04/interesting-uses-of-sunmiscunsafe.html

zudokod
quelle
1
Wenn Sie die Adresse eines Felds mit Unsafe erhalten, kann diese jederzeit vom GC geändert werden. Ist diese Operation also nicht ziemlich nutzlos?
Pdeva
Holen
Was genau meinst du mit dem, den ich zugewiesen habe? Dies scheint an Orten verwendet zu werden, an denen Objekte mit dem Operator 'new' erstellt wurden, daher meine Frage.
Pdeva
1
unsafe.allocateMemory und setzen Sie den Wert
zudokod
1
In Bezug auf Punkt 2 möchte ich wissen, wie Sie den Konstruktor wie jeden anderen Methodenaufruf aufrufen können. Weil ich keine Möglichkeit gefunden habe, dies zu tun, außer in Bytecodes.
Miguel Gamboa
31

Allein durch das Ausführen einer Suche in einer Codesuchmaschine erhalte ich die folgenden Beispiele:

  • Java-Objektnotation - Verwenden Sie sie für eine effizientere Array-Verarbeitung unter Angabe des Javadoc

Einfache Klasse, um Zugriff auf das Objekt {@link Unsafe} zu erhalten. {@link Unsafe} * ist erforderlich, um effiziente CAS-Operationen auf Arrays zu ermöglichen. Beachten Sie, dass die Versionen in {@link java.util.concurrent.atomic}, wie z. B. {@link java.util.concurrent.atomic.AtomicLongArray}, zusätzliche Garantien für die Speicherreihenfolge erfordern, die in diesen Algorithmen im Allgemeinen nicht benötigt werden und auch teuer sind auf den meisten Prozessoren.

  • SoyLatte - Java 6 für Osx Javadoc Auszug

/ ** Basisklasse für sun.misc.Unsafe-basierte FieldAccessors für statische Felder. Die Beobachtung ist, dass es vom Standpunkt des Reflexionscodes nur neun Arten von Feldern gibt: die acht primitiven Typen und das Objekt. Die Verwendung der Klasse Unsafe anstelle von generierten Bytecodes spart Speicher und Ladezeit für die dynamisch generierten FieldAccessors. * /

  • SpikeSource

/ * FinalFields, die über die Leitung gesendet werden. Wie kann ich das Objekt auf der Empfangsseite entfernen und neu erstellen? Wir möchten den Konstruktor nicht aufrufen, da er Werte für endgültige Felder festlegen würde. Wir müssen das endgültige Feld genau so neu erstellen, wie es auf der Absenderseite war. Die sun.misc.Unsafe erledigt das für uns. * /

Es gibt viele andere Beispiele, folgen Sie einfach dem obigen Link ...

Asaf
quelle
25

Interessanterweise hatte ich noch nie von dieser Klasse gehört (was wahrscheinlich wirklich gut ist).

Eine Sache, die mir in den Sinn kommt, ist die Verwendung von Unsafe # setMemory , um Puffer auf Null zu setzen, die an einem Punkt vertrauliche Informationen enthielten (Passwörter, Schlüssel, ...). Sie könnten dies sogar für Felder mit "unveränderlichen" Objekten tun (andererseits könnte auch hier eine einfache alte Reflexion den Trick machen). Ich bin allerdings kein Sicherheitsexperte, also nimm das mit einem Körnchen Salz.

Mike Daniels
quelle
4
I'd never even heard of this class... Ich habe dir so oft davon erzählt! Seufzer + :(
Tim Bender
7
Es hätte keinen Sinn, da Java einen Garbage Collector für das Kopieren von Generationen verwendet und Ihre vertraulichen Informationen wahrscheinlich bereits an einer anderen Stelle im "freien" Speicher gespeichert sind, die darauf wartet, überschrieben zu werden.
Daniel Cassidy
39
Ich habe auch noch nie davon gehört, aber ich liebe ihre park()Dokumentation: "Blockiere den aktuellen Thread und kehre zurück, wenn ein Ausgleichs-Unpark auftritt oder ein Ausgleichs-Unpark bereits aufgetreten ist oder der Thread unterbrochen ist oder, wenn nicht absolut und die Zeit nicht Null ist, der gegebene Zeit Nanosekunden sind abgelaufen, oder wenn absolut, die gegebene Frist in Millisekunden seit dem Ende der Epoche oder falsch (dh ohne "Grund" zurückzukehren) ". Fast so gut wie "Speicher wird freigegeben, wenn das Programm beendet wird, oder in zufälligen Intervallen, je nachdem, was zuerst eintritt".
aroth
1
@ Daniel, interessant, das hatte ich nicht bedacht. Jetzt können Sie sehen, warum ich kein Sicherheitsexperte bin. :)
Mike Daniels
22

Basierend auf einer sehr kurzen Analyse der Java 1.6.12-Bibliothek unter Verwendung von Eclipse zur Referenzverfolgung scheint es, als ob jede nützliche Funktionalität von Unsafeauf nützliche Weise verfügbar gemacht wird.

CAS-Operationen werden über die Atomic * -Klassen verfügbar gemacht. Speichermanipulationsfunktionen werden über DirectByteBuffer-Synchronisierungsanweisungen (Parken, Entparken) verfügbar gemacht, die über den AbstractQueuedSynchronizer verfügbar gemacht werden, der wiederum von Lock-Implementierungen verwendet wird.

Tim Bender
quelle
Die AtomicXXXUpdaters sind zu langsam und wenn Sie sie wirklich brauchen: CAS - Sie können es sich nicht leisten, sie tatsächlich zu verwenden. Wenn Sie das Metall machen wollen, werden Sie nicht die Abstraktionsebenen und zahlreiche Prüfungen verwenden. Das Versagen des CAS ist ein schlechtes in einer Schleife, insb. Wenn die Hardware beschließt, den Zweig falsch vorherzusagen (aufgrund hoher Konflikte), aber nur wenige Vergleiche / Zweige mehr haben, tut das einfach weh. Park / Unpark sind LockSupportnicht durch AQS ausgesetzt (letzteres ist eher eine Schleuse als Park / Unpark)
Bests
21

Unsafe.throwException - Ermöglicht das Auslösen einer aktivierten Ausnahme, ohne sie zu deklarieren.

Dies ist in einigen Fällen nützlich, in denen Sie sich mit Reflexion oder AOP befassen.

Angenommen, Sie erstellen einen generischen Proxy für eine benutzerdefinierte Schnittstelle. Der Benutzer kann angeben, welche Ausnahme in einem speziellen Fall von der Implementierung ausgelöst wird, indem er die Ausnahme in der Benutzeroberfläche deklariert. Dann ist dies der einzige Weg, den ich kenne, um eine aktivierte Ausnahme in der dynamischen Implementierung der Schnittstelle auszulösen.

import org.junit.Test;
/** need to allow forbidden references! */ import sun.misc.Unsafe;

/**
 * Demonstrate how to throw an undeclared checked exception.
 * This is a hack, because it uses the forbidden Class {@link sun.misc.Unsafe}.
 */
public class ExceptionTest {

    /**
     * A checked exception.
     */
    public static class MyException extends Exception {
        private static final long serialVersionUID = 5960664994726581924L;
    }

    /**
     * Throw the Exception.
     */
    @SuppressWarnings("restriction")
    public static void throwUndeclared() {
        getUnsafe().throwException(new MyException());
    }

    /**
     * Return an instance of {@link sun.misc.Unsafe}.
     * @return THE instance
     */
    @SuppressWarnings("restriction")
    private static Unsafe getUnsafe() {
        try {

            Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
            singleoneInstanceField.setAccessible(true);
            return (Unsafe) singleoneInstanceField.get(null);

        } catch (IllegalArgumentException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (SecurityException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (NoSuchFieldException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (IllegalAccessException e) {
            throw createExceptionForObtainingUnsafe(e);
        }
    }

    private static RuntimeException createExceptionForObtainingUnsafe(final Throwable cause) {
        return new RuntimeException("error while obtaining sun.misc.Unsafe", cause);
    }


    /**
     * scenario: test that an CheckedException {@link MyException} can be thrown
     * from an method that not declare it.
     */
    @Test(expected = MyException.class)
    public void testUnsingUnsaveToThrowCheckedException() {
        throwUndeclared();
    }
}
Ralph
quelle
14
Sie können das gleiche tun, Thread.stop(Throwable)ohne unsichere zu benötigen, im gleichen Thread können Sie sowieso alles werfen (es gibt keine Kompilierungsprüfung)
bestsss
Sie können dies nur durch Bytecode tun (oder Lomboc verwenden, um es für Sie zu tun)
Antimon
1
@bestsss Diese Methode wurde ausgeblendet und wirft ab UnsupportedOperationExceptionJava 8 einen in den aktuellen Thread. Die Version ohne Argumente, die ausgelöst wird, ThreadDeathfunktioniert jedoch weiterhin.
Gparyani
@damryfbfnetsi, ich habe seit einiger Zeit keine Kern-JDK-Diskussionen mehr verfolgt und keine Pläne, auf Java 8 umzusteigen. Dies ist jedoch eine ziemlich rätselhafte Idee, da es sowieso trivial ist, sie durch Bytecode-Generierung zu implementieren, es sei denn, der Prüfer prüft jetzt tatsächlich, ob sie tatsächlich vorhanden sind Methode deklariert die Throwables ... aber das könnte abwärtskompatibel sein, da Metadaten über ausgelöste Ausnahmen frei verworfen werden konnten.
Bests
10

Klasse unsicher

Eine Sammlung von Methoden zum Ausführen unsicherer Operationen auf niedriger Ebene. Obwohl die Klasse und alle Methoden öffentlich sind, ist die Verwendung dieser Klasse begrenzt, da nur vertrauenswürdiger Code Instanzen davon erhalten kann.

Eine Verwendung davon ist in java.util.concurrent.atomicKlassen:

Margus
quelle
6

Für eine effiziente Speicherkopie (schneller zu kopieren als System.arraycopy (), zumindest für kurze Blöcke); wie von Java LZF- und Snappy- Codecs verwendet. Sie verwenden 'getLong' und 'putLong', die schneller sind als das byteweise Kopieren. Besonders effizient beim Kopieren von 16/32/64-Byte-Blöcken.

StaxMan
quelle
1
Doh, Arraycopy verwendet SSE-Schleifen auf x86-64, die besser sind als getLong/putLong(und Sie müssen auch die Adresse berechnen)
bestsss
Hast du das tatsächlich gemessen? Für kürzere Blöcke sehe ich auf x86-64 eine konstant bessere Leistung, wenn ich eine Kombination von getLong/ verwende putLong: Idealerweise würde ich es der System.arraycopy()Einfachheit halber und allem vorziehen ; Die tatsächlichen Tests zeigten jedoch etwas anderes für Fälle, die ich getestet habe.
StaxMan
ja mit unsicher konnte ich keine sinnvolle leistung aus deflate impl. Bei mehreren Bytes langen Kopien über große Arrays funktioniert get / putLong möglicherweise, wenn der Compiler die Länge überprüfen muss. Einige impl. Fügen Sie nach System.arrayCopy einen Speicherzaun hinzu (kann jedoch deaktiviert / aktiviert werden), damit dies der eigentliche Schuldige sein kann.
Bests
OK. Möglicherweise haben neuere JDKs dies geändert. Als ich ursprünglich einen schnelleren Betrieb beobachtete (mit JDK 1.6), war ich auch überrascht. Oder vielleicht vergesse ich einen bestimmten Unterschied in der Verwendung. Dies sind knifflige (und möglicherweise instabile) Optimierungen, selbst wenn sie funktionieren, und es ist wichtig, die Auswirkungen zu messen.
StaxMan
5

Ich habe kürzlich an der Neuimplementierung der JVM gearbeitet und festgestellt, dass eine überraschende Anzahl von Klassen in Bezug auf implementiert ist Unsafe. Die Klasse wurde hauptsächlich für Java-Bibliotheksimplementierer entwickelt und enthält Funktionen, die grundsätzlich unsicher sind, aber zum Erstellen schneller Grundelemente erforderlich sind. Beispielsweise gibt es Methoden zum Abrufen und Schreiben von Rohfeld-Offsets, zum Synchronisieren auf Hardwareebene, zum Zuweisen und Freigeben von Speicher usw. Es ist nicht für die Verwendung durch normale Java-Programmierer vorgesehen. Es ist nicht dokumentiert, implementierungsspezifisch und von Natur aus unsicher (daher der Name!). Darüber hinaus denke ich, dass der SecurityManagerZugang in fast allen Fällen nicht erlaubt ist.

Kurz gesagt, es besteht hauptsächlich darin, Bibliotheksimplementierern den Zugriff auf den zugrunde liegenden Computer zu ermöglichen, ohne jede Methode in bestimmten Klassen wie AtomicIntegernative deklarieren zu müssen . Sie sollten es in der routinemäßigen Java-Programmierung nicht verwenden oder sich darum kümmern müssen, da der springende Punkt darin besteht, den Rest der Bibliotheken so schnell zu machen, dass Sie keinen solchen Zugriff benötigen.

templatetypedef
quelle
amara
@ sparkleshy- Kannst du das näher erläutern?
Templatetypedef
Während das Abrufen einer Instanz von getUnsafe ziemlich strenge Anforderungen hat, Unsafe.class.getDeclaredField("theUnsafe")mit .setAccessible(true)und dann .get(null)auch
amara
@ sparkleshy- Ich bin überrascht, dass das funktioniert - der Sicherheitsmanager sollte das melden.
Templatetypedef
5

Verwenden Sie es, um effizient auf große Speichermengen zuzugreifen und diese zuzuweisen, z. B. in Ihrer eigenen Voxel-Engine! (dh Spiel im Minecraft-Stil.)

Nach meiner Erfahrung ist die JVM oft nicht in der Lage, die Überprüfung von Grenzen an den Stellen zu beseitigen, an denen Sie sie wirklich benötigen. Wenn Sie beispielsweise über ein großes Array iterieren, der tatsächliche Speicherzugriff jedoch unter einem nicht virtuellen * Methodenaufruf in der Schleife versteckt ist, führt die JVM möglicherweise weiterhin eine Überprüfung der Grenzen bei jedem Arrayzugriff durch und nicht nur einmal zuvor die Schleife. Für potenziell große Leistungssteigerungen können Sie daher die Überprüfung der JVM-Grenzen innerhalb der Schleife mithilfe einer Methode eliminieren, bei der sun.misc.Unsafe verwendet wird, um direkt auf den Speicher zuzugreifen, und sicherstellen, dass Sie die Grenzen an den richtigen Stellen überprüfen. (Sie sind Gonna Grenzen überprüfen auf einer bestimmten Ebene, nicht wahr?)
* Mit nicht virtuell meine ich, dass die JVM nicht dynamisch auflösen muss, was auch immer Ihre spezielle Methode ist, da Sie korrekt garantiert haben, dass Klasse / Methode / Instanz eine Kombination aus statisch / endgültig / was-haben-Sie sind.

Für meine selbstgebaute Voxel-Engine führte dies zu einem dramatischen Leistungsgewinn während der Chunk-Generierung und -Serialisierung (an Orten, an denen ich das gesamte Array gleichzeitig las / schrieb). Die Ergebnisse können variieren, aber wenn ein Mangel an Grenzbeseitigung Ihr Problem ist, wird dies das Problem beheben.

Hierbei gibt es einige potenziell schwerwiegende Probleme: Insbesondere wenn Sie Clients Ihrer Schnittstelle die Möglichkeit bieten, auf den Speicher zuzugreifen, ohne die Grenzen zu überprüfen, werden sie ihn wahrscheinlich missbrauchen. (Vergessen Sie nicht, dass Hacker auch Clients Ihrer Schnittstelle sein können ... insbesondere im Fall einer in Java geschriebenen Voxel-Engine.) Daher sollten Sie Ihre Schnittstelle entweder so gestalten, dass der Speicherzugriff nicht missbraucht werden kann, oder Sie sollten sehr vorsichtig sein , Benutzerdaten zu validieren , bevor es überhaupt kann, je mit gefährlicher Schnittstelle mische. Angesichts der katastrophalen Dinge, die ein Hacker mit ungeprüftem Speicherzugriff tun kann, ist es wahrscheinlich am besten, beide Ansätze zu wählen.

Philip Guin
quelle
4

Off-Heap-Sammlungen können nützlich sein, um große Speichermengen zuzuweisen und die Zuweisung unmittelbar nach der Verwendung ohne GC-Interferenz aufzuheben. Ich habe eine Bibliothek für die Arbeit mit Off-Heap-Arrays / Listen geschrieben, die auf basieren sun.misc.Unsafe.

alexkasko
quelle
4

Wir haben riesige Sammlungen wie Arrays, HashMaps, TreeMaps mit Unsafe implementiert.
Um die Fragmentierung zu vermeiden / zu minimieren, haben wir den Speicherzuweiser mithilfe der Konzepte von dlmalloc over unsicher implementiert .
Dies hat uns geholfen, die Leistung bei gleichzeitiger Verwendung zu steigern.

Pradipmw
quelle
3

Unsafe.park()und Unsafe.unpark()für den Aufbau von benutzerdefinierten Parallelitätskontrollstrukturen und kooperativen Planungsmechanismen.

andersoj
quelle
24
öffentlich verfügbar alsjava.util.concurrent.locks.LockSupport
bestsss
1

Ich habe es selbst nicht verwendet, aber ich nehme an, wenn Sie eine Variable haben, die nur gelegentlich von mehr als einem Thread gelesen wird (damit Sie sie nicht wirklich flüchtig machen möchten), können Sie sie verwenden, putObjectVolatilewenn Sie sie in den Hauptthread und schreiben readObjectVolatilebeim Ausführen der seltenen Lesevorgänge aus anderen Threads.

Matt Crinklaw-Vogt
quelle
1
Aber laut der Diskussion im Thread unten sind unzufriedene flüchtige Stoffe fast so schnell wie nichtflüchtige Stoffe. stackoverflow.com/questions/5573782/…
pdeva
Sie können die flüchtige Semantik nicht durch einfache Schreibvorgänge und flüchtige Lesevorgänge ersetzen. Dies ist ein Rezept für eine Katastrophe, da es in einer Einstellung möglicherweise funktioniert, in einer anderen jedoch nicht. Wenn Sie eine flüchtige Semantik mit einem einzelnen Writer-Thread wünschen, können Sie AtomicReference.lazySet für den Schreibthread und get () für die Leser verwenden ( eine Diskussion zum Thema finden Sie in diesem Beitrag ). Flüchtige Lesevorgänge sind relativ billig, aber nicht kostenlos, siehe hier .
Nitsan Wakart
"... Sie könnten putObjectVolatile beim Schreiben verwenden ..." Ich schlug keine einfachen Schreibvorgänge vor.
Matt Crinklaw-Vogt
1

Sie benötigen es, wenn Sie die Funktionen einer der Klassen ersetzen müssen, die es derzeit verwenden.

Dies kann eine benutzerdefinierte / schnellere / kompaktere Serialisierung / Deserialisierung, eine schnellere / größere Puffer- / Größenänderungsversion von ByteBuffer oder das Hinzufügen einer atomaren Variablen sein, z. B. eine derzeit nicht unterstützte.

Ich habe es irgendwann für all dies benutzt.

Peter Lawrey
quelle
0

Das Objekt scheint die Verfügbarkeit zu sein, um auf einer niedrigeren Ebene zu arbeiten, als es Java-Code normalerweise zulässt. Wenn Sie eine Anwendung auf hoher Ebene codieren, abstrahiert die JVM die Speicherbehandlung und andere Vorgänge von der Codeebene weg, sodass die Programmierung einfacher ist. Durch die Verwendung der unsicheren Bibliothek führen Sie effektiv Operationen auf niedriger Ebene aus, die normalerweise für Sie ausgeführt werden.

Wie woliveirajr sagte, verwendet "random ()" Unsafe, um zu setzen, genauso wie viele andere Operationen die in Unsafe enthaltene Funktion allocateMemory () verwenden.

Als Programmierer könnten Sie wahrscheinlich davonkommen, diese Bibliothek nie zu benötigen, aber eine strikte Kontrolle über Elemente auf niedriger Ebene ist praktisch (aus diesem Grund gibt es in wichtigen Produkten immer noch Assembly- und (in geringerem Maße) C-Code).

Grambot
quelle