2011-12-11 14 views
19

JDK fornisce capacità di allocazione dei cosiddetti ByteBuffer diretti, in cui la memoria viene allocata all'esterno dell'heap Java. Questo può essere utile in quanto questa memoria non viene toccata dal garbage collector e come tale non contribuisce al sovraccarico di GC: questo è molto utile per la proprietà di cose che vivono a lungo come cache.Esempi di forzatura della liberazione della memoria nativa diretta da ByteBuffer è stata allocata, utilizzando sun.misc.Unsafe?

Tuttavia, vi è un problema critico con l'implementazione esistente: la memoria sottostante viene allocata in modo asincrono solo quando il proprietario ByteBuffer è sottoposto a raccolta dei dati inutili; non c'è modo di forzare una deallocazione anticipata. Questo può essere problematico poiché il ciclo GC non è influenzato dalla gestione di ByteBuffers e dato che i ByteBuffer risiedono probabilmente nell'area di memoria di vecchia generazione, è possibile che GC venga chiamato ore dopo che ByteBuffer non è più in uso.

Ma in teoria dovrebbe essere possibile utilizzare direttamente i metodi sun.misc.Unsafe (freeMemory, allocateMemory): questo è ciò che JDK utilizza per allocare/deallocare la memoria nativa. Guardando al codice, una potenziale preoccupazione che vedo è la possibilità di una doppia liberazione della memoria, quindi vorrei assicurarmi che lo stato venga pulito correttamente.

Qualcuno può indicarmi il codice che esegue questa operazione? Idealmente vorrebbe usare questo invece di JNA.

NOTA: Ho visto this question che è un po 'correlato.

Sembra che le risposte indicate siano valide: here è un esempio di codice di Ricerca elastica che utilizza l'idea. Grazie a tutti!

+0

E 'possibile, trovato in SO anni fa ... guardando ora. (In passato ho finito usando solo una coda circolare di buffer, che nel mio caso ha mantenuto bassa la pressione.) –

+1

Wow, non riesco a trovare nessun buon articolo da nessuna parte in questo momento :( –

+0

Devo ancora vedere un System.gc() essere ignorato, almeno su Sun, che sembra essere comunque l'obiettivo – nilskp

risposta

5

L'utilizzo di sun.misc.Unsafe è difficilmente possibile perché l'indirizzo di base della memoria nativa allocata è una variabile locale del costruttore java.nio.DirectByteBuffer.

In realtà si può forzare liberazione della memoria nativa con il seguente codice:

import sun.misc.Cleaner; 

import java.lang.reflect.Field; 
import java.nio.ByteBuffer; 

... 

public static void main(String[] args) throws Exception { 
    ByteBuffer direct = ByteBuffer.allocateDirect(1024); 
    Field cleanerField = direct.getClass().getDeclaredField("cleaner"); 
    cleanerField.setAccessible(true); 
    Cleaner cleaner = (Cleaner) cleanerField.get(direct); 
    cleaner.clean(); 
} 
+0

Giusto, questo è il motivo per cui ho chiesto; l'intera orchestrazione di Cleaner abbinata a callback su Deallocator è complessa. Proverò questo approccio, assumendo che il tipo Cleaner sia accessibile (ho notato che la cosa che chiama non lo è). – StaxMan

2

Fondamentalmente quello che vuoi è seguire la stessa semantica dell'uso di flussi IO. Proprio come hai bisogno di chiudere uno stream una volta, devi liberare la memoria una volta. Quindi è possibile scrivere il proprio wrapper attorno alle chiamate native rendendo possibile la liberazione anticipata della memoria

+0

Sì, a un livello molto alto - ma hai guardato i dettagli di come esattamente? Ecco dove inizia il problema: non esiste un metodo per farlo con ByteBuffer (diretto), nessun metodo close(). E anche sotto il cofano, le cose sono piuttosto complicate, purtroppo. – StaxMan

+0

Sì, ma è possibile modificare il gioco implementando il proprio buffer utilizzando il non sicuro. (Che probabilmente funziona davvero bene con la memoria imbottita per evitare false condivisioni, ma questo è un gioco completamente diverso) –

+0

Abbastanza vero. E nel mio caso, ho il pieno controllo di tutti gli accessi, quindi l'archiviazione raw sottostante non è né esposta né dovrebbe costituire un pericolo di riferimenti ciondolanti. – StaxMan

25

C'è un modo molto più semplice per pulire la memoria.

public static void clean(ByteBuffer bb) { 
    if(bb == null) return; 
    Cleaner cleaner = ((DirectBuffer) bb).cleaner(); 
    if (cleaner != null) cleaner.clean(); 
} 

L'utilizzo può fare una grande differenza se si scartano ByteBuffer diretti o memorizzati in memoria abbastanza rapidamente.

Uno dei motivi per cui si utilizza il pulitore per eseguire questa operazione è che è possibile avere più copie delle risorse di memoria sottostanti, ad es. con slice(). e il Pulitore ha un conteggio delle risorse di questi.

+1

Hmmh. Stavo guardando più pulito, ma non ero sicuro che i diritti di accesso mi avrebbero permesso di chiamarlo direttamente ... ma se così fosse, sarebbe sicuramente un buon modo per andare. Grazie - forse ero troppo sbrigativo nello scartare questo approccio! – StaxMan

+0

Ho utilizzato questo approccio in numerosi progetti. È meglio che usare la riflessione IMHO. –

+0

Giusto: finché le classi sono accessibili, sono d'accordo. Il codice stesso può quindi essere caricato dinamicamente, se è necessario supportare il caso potenziale di Unsafe stesso mancante. Grazie ancora! – StaxMan

Problemi correlati