Perché sun.misc.Unsafe esiste e come può essere utilizzato nel mondo reale? [chiuso]


267

Mi sono imbattuto in sun.misc.Pacchetto insicuro l'altro giorno e sono rimasto sorpreso da ciò che poteva fare.

Certo, la classe non è documentata, ma mi chiedevo se ci fosse mai una buona ragione per usarla. Quali scenari potrebbero sorgere dove sarebbe necessario usarlo? Come potrebbe essere utilizzato in uno scenario del mondo reale?

Inoltre, se si fa bisogno, fa che non indica che qualcosa è probabilmente sbagliato con il vostro disegno?

Perché Java include anche questa classe?


7
Gli sviluppatori JDK stanno attualmente riesaminando questa API per la possibile trasformazione in un'API pubblica in Java 9. Se la usi, vale la pena impiegare 5 minuti per compilare il sondaggio: surveymonkey.com/s/sun-misc-Unsafe .
Andy Lynch,

2
Questo post è in discussione su meta: meta.stackoverflow.com/questions/299139/…
Jon Clements

Risposte:


159

esempi

  1. VM "intrinsification". vale a dire CAS (confronta e scambia) utilizzato nelle tabelle hash senza blocco, ad es. sun.misc.Unsafe.compareAndSwapInt può effettuare chiamate JNI reali in codice nativo che contiene istruzioni speciali per CAS

    leggi di più su CAS qui http://en.wikipedia.org/wiki/Compare-and-swap

  2. La funzionalità sun.misc.Unsafe della VM host può essere utilizzata per allocare oggetti non inizializzati e quindi interpretare la chiamata del costruttore come qualsiasi altra chiamata di metodo.

  3. È possibile tenere traccia dei dati dall'indirizzo nativo. È possibile recuperare l'indirizzo di memoria di un oggetto utilizzando la classe java.lang.Unsafe e operare sui suoi campi direttamente tramite metodi get / put non sicuri!

  4. Compilare ottimizzazioni dei tempi per JVM. VM ad alte prestazioni con "magia", che richiede operazioni di basso livello. ad es .: http://en.wikipedia.org/wiki/Jikes_RVM

  5. Allocazione della memoria, sun.misc.Unsafe.allocateMemory ad es .: - Il costruttore DirectByteBuffer lo chiama internamente quando viene richiamato ByteBuffer.allocateDirect

  6. Tracciare lo stack di chiamate e riprodurre con valori istanziati da sun.misc.Unsafe, utile per la strumentazione

  7. sun.misc.Unsafe.arrayBaseOffset e arrayIndexScale possono essere utilizzati per sviluppare arraylet, una tecnica per suddividere in modo efficiente array di grandi dimensioni in oggetti più piccoli per limitare il costo in tempo reale di scansione, aggiornamento o spostamento di operazioni su oggetti di grandi dimensioni

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

maggiori informazioni sui riferimenti qui - http://bytescrolls.blogspot.com/2011/04/interesting-uses-of-sunmiscunsafe.html


1
se si ottiene l'indirizzo di un campo utilizzando Unsafe, può sempre essere modificato dal GC, quindi tale operazione non è abbastanza inutile?
pdeva,

ottieni l'indirizzo per quelli che hai assegnato
zudokod,

cosa intendi esattamente con quello che ho assegnato? questo sembra essere usato in luoghi in cui gli oggetti sono stati creati usando il "nuovo" operatore, quindi la mia domanda.
pdeva,

1
unsafe.allocateMemory e inserisci il valore
zudokod

1
Per quanto riguarda il punto 2, vorrei sapere come si può invocare il costruttore come qualsiasi altra chiamata di metodo? Perché non ho trovato alcun modo per farlo se non in bytecode.
Miguel Gamboa,

31

Appena eseguendo una ricerca in alcuni motori di ricerca di codice, ottengo i seguenti esempi:

Classe semplice per ottenere l'accesso all'oggetto {@link Unsafe}. {@link Unsafe} * è necessario per consentire operazioni CAS efficienti sugli array. Tieni presente che le versioni in {@link java.util.concurrent.atomic}, come {@link java.util.concurrent.atomic.AtomicLongArray}, richiedono garanzie di ordinazione di memoria extra che generalmente non sono necessarie in questi algoritmi e sono anche costose sulla maggior parte dei processori.

  • SoyLatte - java 6 per estratto di osx javadoc

/ ** Classe base per sun.misc.Acessor FieldAccessors per campi statici. L'osservazione è che esistono solo nove tipi di campi dal punto di vista del codice di riflessione: gli otto tipi primitivi e l'Oggetto. L'uso della classe Unsafe anziché dei bytecode generati consente di risparmiare memoria e tempo di caricamento per i FieldAccessors generati dinamicamente. * /

  • SpikeSource

/ * FinalFields che vengono inviati attraverso il filo .. come smascherare e ricreare l'oggetto sul lato ricevente? Non vogliamo invocare il costruttore poiché stabilirebbe valori per i campi finali. Dobbiamo ricreare il campo finale esattamente come sul lato del mittente. Il sun.misc.Unsafe fa questo per noi. * /

Ci sono molti altri esempi, basta seguire il link sopra ...


25

Interessante, non avevo mai nemmeno sentito parlare di questa classe (che è probabilmente una buona cosa, davvero).

Una cosa che viene in mente è usare Unsafe # setMemory per azzerare i buffer che contenevano informazioni sensibili in un punto (password, chiavi, ...). Si potrebbe anche fare questo a campi di oggetti "immutabili" (quindi suppongo che anche qui la semplice vecchia riflessione potrebbe fare il trucco). Non sono un esperto di sicurezza, quindi prendilo con un po 'di sale.


4
I'd never even heard of this class... Te l'ho detto tante volte! sospiro + :(
Tim Bender,

7
Non ci sarebbe alcun motivo, dal momento che Java usa un garbage collector generazionale che copia e le tue informazioni sensibili saranno probabilmente già localizzate altrove nella memoria "libera" in attesa di essere sovrascritte.
Daniel Cassidy,

39
Nemmeno ne ho mai sentito parlare, ma adoro la loro park()documentazione: "Blocca il thread corrente, ritorna quando si verifica un unpark di bilanciamento, o si è già verificato un unpark di bilanciamento, oppure il thread viene interrotto o, se non assoluto e il tempo non è zero, il dato tempo i nanosecondi sono trascorsi, o se assoluti, la scadenza indicata in millisecondi da quando Epoch è passata, o in modo spurio (cioè, ritornando senza "motivo") ". Quasi buono come "la memoria viene liberata all'uscita dal programma o, a intervalli casuali, a seconda di quale si verifichi per prima".
aroth,

1
@Daniel, interessante, non l'avevo considerato. Ora puoi capire perché non sono un esperto di sicurezza. :)
Mike Daniels

22

Basato su una brevissima analisi della libreria Java 1.6.12 che utilizza eclipse per la traccia di riferimento, sembra che ogni utile funzionalità di Unsafe sia esposta in modo utile.

Le operazioni CAS sono esposte attraverso le classi Atomic *. Le funzioni di manipolazione della memoria sono esposte tramite DirectByteBuffer Le istruzioni di sincronizzazione (parcheggio, unpark) sono esposte tramite AbstractQueuedSynchronizer che a sua volta viene utilizzato dalle implementazioni di Lock.


Gli AtomicXXXUpdaters sono troppo lenti e quando ne hai davvero bisogno: CAS - non puoi permetterti di usarli effettivamente. Se hai intenzione di fare il metallo, non utilizzerai i livelli di astrazione e numerosi controlli. Fallire il CAS è un male in un ciclo esp. quando l'hardware decide di predire erroneamente il ramo (a causa di un'elevata contesa) ma avere pochi altri confronti / rami fa solo male. Park / Unpark sono esposti LockSupportnon tramite AQS (quest'ultimo è più un sistema di blocco che un parcheggio / unpark)
bestsss

21

Unsafe.throwException : consente di generare un'eccezione controllata senza dichiararle.

Questo è utile in alcuni casi in cui hai a che fare con la riflessione o AOP.

Si supponga di creare un proxy generico per un'interfaccia definita dall'utente. E l'utente può specificare quale eccezione viene generata dall'impianto in un caso speciale semplicemente dichiarando l'eccezione nell'interfaccia. Quindi questo è l'unico modo che conosco per sollevare un'eccezione verificata nell'implementazione dinamica dell'interfaccia.

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

14
puoi fare lo stesso con Thread.stop(Throwable)nessun bisogno di insicuro, nello stesso thread puoi comunque lanciare qualsiasi cosa (non c'è controllo di compilazione)
bestsss

Puoi farlo semplicemente attraverso il bytecode (O usa Lomboc per farlo per te)
Antimonio

1
@bestsss Questo metodo è stato cancellato e genera un UnsupportedOperationExceptionthread corrente nel Java corrente a partire da Java 8. Tuttavia, la versione senza argomento che genera ThreadDeathcontinua a funzionare.
gparyani,

@damryfbfnetsi, non ho seguito le discussioni jdk di base per un bel po 'di tempo e non ho intenzione di passare a java 8. Tuttavia, questa è un'idea piuttosto sconcertante poiché è banale essere implementato dalla generazione di bytecode comunque, a meno che ora il verificatore non controlli effettivamente se Il metodo dichiara i lanciabili ... ma potrebbe essere incompatibile con le versioni precedenti poiché i metadati relativi all'eccezione generata erano liberi di essere scartati.
bestsss

10

Classe non sicura

Una raccolta di metodi per eseguire operazioni a basso livello e non sicure. Sebbene la classe e tutti i metodi siano pubblici, l'uso di questa classe è limitato perché solo il codice attendibile può ottenerne istanze.

Un suo utilizzo è nelle java.util.concurrent.atomicclassi:


6

Per una copia efficiente della memoria (più veloce da copiare rispetto a System.arraycopy () almeno per i blocchi corti); come utilizzato dai codec Java LZF e Snappy . Usano 'getLong' e 'putLong', che sono più veloci delle copie byte per byte; particolarmente efficiente quando si copiano cose come blocchi di 16/32/64 byte.


1
Doh, arraycopy usa loop SSE su x86-64 che sono migliori di getLong/putLong(e devi anche calcolare l'indirizzo)
bestsss

Lo hai effettivamente misurato? Per i blocchi più corti vedo prestazioni costantemente migliori su x86-64 quando uso la combinazione di getLong/ putLong: idealmente preferirei System.arraycopy()per semplicità e tutto; ma i test effettivi hanno mostrato diversamente per i casi che ho testato.
StaxMan,

sì, usando non sicuro non ho potuto ottenere prestazioni significative da deflate impl. Per diversi byte copie lunghe su array di grandi dimensioni get / putLong potrebbero funzionare davvero quando il compilatore deve controllare le lunghezze. Alcuni impl. aggiungi la barriera di memoria oltre System.arrayCopy (può essere disabilitato / abilitato però), in modo che possa essere il vero colpevole.
bestsss

Ok. È possibile che i nuovi JDK abbiano cambiato questo; inizialmente quando ho osservato operazioni più veloci (con JDK 1.6) sono rimasto sorpreso. O forse sto dimenticando qualche differenza specifica nell'uso. Queste sono ottimizzazioni difficili (e forse instabili), anche quando funzionano, ed è essenziale misurare gli effetti.
StaxMan,

5

Recentemente stavo lavorando per reimplementare la JVM e ho scoperto che un numero sorprendente di classi è implementato in termini di Unsafe. La classe è progettata principalmente per gli implementatori di librerie Java e contiene funzionalità fondamentalmente non sicure ma necessarie per la creazione di primitive veloci. Ad esempio, ci sono metodi per ottenere e scrivere offset di campi grezzi, usare la sincronizzazione a livello hardware, allocare e liberare memoria, ecc. Non è previsto per essere utilizzato dai normali programmatori Java; è privo di documenti, specifico per l'implementazione e intrinsecamente pericoloso (da cui il nome!). Inoltre, penso che ciò SecurityManagerimpedirà l'accesso ad esso in quasi tutti i casi.

In breve, esiste principalmente per consentire agli implementatori di librerie l'accesso al computer sottostante senza dover dichiarare ogni metodo in determinate classi come AtomicIntegernativo. Non dovresti averne bisogno o preoccuparti nella normale programmazione Java, poiché il punto è rendere il resto delle librerie abbastanza veloce da non avere bisogno di quel tipo di accesso.


in realtà, il SecurityManager non può accedervi solo se la riflessione è disabilitata
amara,

@ sparkleshy- Puoi approfondire questo?
templatetypedef

mentre ottenere un'istanza da getUnsafe ha dei requisiti piuttosto rigidi, Unsafe.class.getDeclaredField("theUnsafe")con .setAccessible(true)e poi .get(null)lo otterrà anche
amara

@ sparkleshy- Sono sorpreso che funzioni: il responsabile della sicurezza dovrebbe segnalarlo.
templatetypedef

5

Usalo per accedere e allocare in modo efficiente grandi quantità di memoria, come nel tuo motore voxel! (es. gioco in stile Minecraft.)

Nella mia esperienza, la JVM spesso non è in grado di eliminare il controllo dei limiti laddove ne hai veramente bisogno. Ad esempio, se stai iterando su un array di grandi dimensioni, ma l'accesso effettivo alla memoria è nascosto sotto una chiamata di metodo * non virtuale nel ciclo, la JVM può comunque eseguire un controllo dei limiti con ogni accesso all'array, piuttosto che una volta poco prima il cappio. Pertanto, per guadagni di prestazioni potenzialmente elevati, è possibile eliminare il controllo dei limiti JVM all'interno del loop tramite un metodo che impiega sun.misc.Unsafe per accedere direttamente alla memoria, assicurandosi di eseguire qualsiasi controllo dei limiti nei punti corretti. (Lo sei gonna limiti controllare a un certo livello, giusto?)
* per non virtuale, intendo che JVM non dovrebbe risolvere dinamicamente qualunque sia il tuo metodo particolare, perché hai correttamente garantito che classe / metodo / istanza sono una combinazione di statico / finale / che-hai-te.

Per il mio motore voxel cresciuto in casa, ciò ha comportato un notevole aumento delle prestazioni durante la generazione e la serializzazione di blocchi (i posti dove stavo leggendo / scrivendo sull'intero array contemporaneamente). I risultati possono variare, ma se il problema è la mancanza di eliminazione dei limiti, questo risolverà il problema.

Ci sono alcuni problemi potenzialmente importanti con questo: in particolare, quando si fornisce la possibilità di accedere alla memoria senza controllo dei limiti ai client della propria interfaccia, probabilmente ne abuseranno. (Non dimenticare che gli hacker possono anche essere client della tua interfaccia ... specialmente nel caso di un motore voxel scritto in Java.) Quindi, dovresti progettare la tua interfaccia in modo tale da non poter abusare dell'accesso alla memoria, oppure dovresti essere estremamente attento a convalidare i dati utente prima che possano, sempre e comunque , mescolarsi con la tua interfaccia pericolosa. Considerando le cose catastrofiche che un hacker può fare con l'accesso alla memoria non controllato, probabilmente è meglio adottare entrambi gli approcci.


4

Le raccolte off-heap possono essere utili per allocare enormi quantità di memoria e deallocare immediatamente dopo l'uso senza interferenze GC. Ho scritto una libreria per lavorare con matrici / liste off-heap basate su sun.misc.Unsafe.


4

Abbiamo implementato enormi raccolte come Array, HashMaps, TreeMaps usando Unsafe.
E per evitare / minimizzare la frammentazione, abbiamo implementato l'allocatore di memoria usando i concetti di dlmalloc su non sicuro.
Questo ci ha aiutato a ottenere prestazioni in concorrenza.


3

Unsafe.park()e Unsafe.unpark()per la costruzione di strutture personalizzate di controllo della concorrenza e meccanismi di programmazione cooperativa.


24
pubblicamente disponibile comejava.util.concurrent.locks.LockSupport
bestsss,

1

Non l'ho usato da solo, ma suppongo che se hai una variabile che viene letta solo occasionalmente da più di un thread (quindi non vuoi renderlo volatile) potresti usarlo putObjectVolatilequando lo scrivi nel thread principale e readObjectVolatilequando si eseguono le letture rare da altri thread.


1
ma secondo la discussione sul thread qui sotto, i volatili non contenti sono quasi altrettanto veloci dei non volatili stackoverflow.com/questions/5573782/…
pdeva

non è possibile sostituire la semantica volatile con scritture semplici e letture volatili ... questa è una ricetta per il disastro in quanto potrebbe funzionare in un ambiente ma non in un altro. Se stai cercando di avere una semantica volatile con un singolo thread del writer, puoi usare AtomicReference.lazySet sul thread di scrittura e ottenere () sui lettori (vedi questo post per una discussione sull'argomento). Le letture volatili sono relativamente economiche, ma non gratuite, vedi qui .
Nitsan Wakart,

"... potresti usare putObjectVolatile durante la scrittura ..." Non stavo suggerendo semplici scritture.
Matt Crinklaw-Vogt,

1

Ne hai bisogno se devi sostituire la funzionalità fornita da una delle classi che la utilizza attualmente.

Può essere una serializzazione / deserializzazione personalizzata / più veloce / più compatta, un buffer più veloce / più grande / versione ridimensionabile di ByteBuffer o l'aggiunta di una variabile atomica, ad esempio una non attualmente supportata.

L'ho usato per tutti questi in qualche momento.



0

L'oggetto sembra essere disponibile per funzionare a un livello inferiore rispetto a quello normalmente consentito dal codice Java. Se stai codificando un'applicazione di alto livello, JVM estrae la gestione della memoria e altre operazioni dal livello del codice, in modo che sia più facile da programmare. Usando la libreria Unsafe stai effettivamente completando operazioni di basso livello che normalmente verrebbero eseguite per te.

Come ha affermato woliveirajr "random ()" usa Unsafe per eseguire il seeding proprio come molte altre operazioni useranno la funzione allocateMemory () inclusa in Unsafe.

Come programmatore potresti probabilmente evitare di non aver mai bisogno di questa libreria ma avere un controllo rigoroso sugli elementi di basso livello è utile (ecco perché c'è ancora Assembly e (in misura minore) il codice C che scorre nei principali prodotti)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.