2011-05-13 4 views
12

J'utilise des E/S mappées en mémoire pour un fichier d'index, mais le problème est que je ne suis pas en mesure de redimensionner le fichier s'il est presque vide.Tronquer le fichier mappé en mémoire

Quelque part avant:

MappedByteBuffer map = raf.getChannel().map(MapMode.READ_WRITE, 0, 1 << 30); 
raf.close(); 
// use map 
map.force(); 
map = null; 

Redimensionner:

for (int c = 0; c < 100; c++) { 
    RandomAccessFile raf = new RandomAccessFile(indexFile, "rw"); 
    try { 
     raf.setLength(newLen); 
     if (c > 0) LOG.warn("used " + c + " iterations to close mapped byte buffer"); 
     return; 
    } catch (Exception e) { 
     System.gc(); 
     Thread.sleep(10); 
     System.runFinalization(); 
     Thread.sleep(10); 
    } finally { 
     raf.close(); 
    } 
} 

Lorsque vous utilisez Windows ou Linux 32 bits J'ai souvent le problème démappage, mais dans l'environnement de production Linux 64 bits tout semble fonctionner sans avertissement, mais le fichier conserve la taille d'origine.

Quelqu'un peut-il expliquer pourquoi cela se produit et/ou comment résoudre le problème?

+0

Je crains que le problème ne dépende en quelque sorte de NFS, de la mise en cache ou du timing car il semble avoir été résolu sans intervention réelle (il suffit d'ajouter la journalisation et l'attente et maintenant cela fonctionne).Même les fichiers qui se sont apaisés après la troncatureg et qui n'ont pas été touchés depuis ont maintenant la bonne taille. Peut-être que la journalisation de la nouvelle taille de fichier après la mise à jour du cache nfs est tronquée. – rurouni

+0

Le problème discuté est similaire à [Comment démonter un fichier] (http://stackoverflow.com/questions/2972986), en particulier, voir [bug # 4724038] (http://bugs.sun.com/view_bug. do? bug_id = 4724038). –

Répondre

7

Votre problème est que vous utilisez une méthode non fiable pour fermer le tampon d'octets mappés (une centaine d'appels à System.gc() et System.runFinalization() ne vous garantissent rien). Malheureusement, il n'y a pas de méthode fiable en Java API pour le faire, mais sur la JVM Sun (et peut-être quelques autres aussi), vous pouvez utiliser le code suivant:

public void unmapMmaped(ByteBuffer buffer) { 
    if (buffer instanceof sun.nio.ch.DirectBuffer) { 
    sun.misc.Cleaner cleaner = ((sun.nio.ch.DirectBuffer) buffer).cleaner(); 
    cleaner.clean(); 
    } 
} 

Bien sûr, il dépend de la machine virtuelle Java et vous devriez être prêt à réparer votre code si jamais Sun décide de changer sun.nio.ch.DirectBuffer ou sun.misc.Cleaner d'une manière incompatible (mais en fait je ne crois pas que cela arrivera jamais).

3

Ceci est juste un complément à la réponse précédente, qui est complètement correcte.

JDK 1.7 se plaint de l'utilisation de sun.misc.Cleaner, indiquant que les classes de cet espace de noms ne font pas partie du JDK et risquent de disparaître dans le futur. Cependant, à partir du 1.7 ils sont toujours présents. Si la méthode .clean() n'est pas disponible, l'utilisation de System.gc() peut être utilisée comme méthode de repli, cependant cela doit être reconnu comme un "hack" et il faut donc faire attention.

Bien que System.gc() ne puisse pas forcer la fermeture d'un mappage non référencé, cela entraînera souvent un nettoyage. L'expérience sur Linux 32 bits (et Solaris) montre des tampons étant libérés pendant chaque test pendant le premier ou le deuxième appel à System.gc(). Cependant, le comportement sur Windows est différent. Dans la plupart des cas, tous les mappages sont libérés à la fin du second appel à System.gc(), mais parfois, ils nécessitent 3 appels. Il y a encore des occasions où plus d'appels sont requis, avec une exigence pour un plus grand nombre d'appels diminuant en fréquence. Cela peut être trompeur, dans la mesure où les tests peuvent indiquer que 4 appels sont tout ce dont vous avez besoin, seulement pour qu'il échoue sur vous un mois plus tard. 5 appels peuvent alors sembler adéquats, pour aboutir à un échec dans 6 mois.

Le test pour voir si une carte a été publiée peut être fait en utilisant un bloc try/catch autour de FileChannel.truncate(), avec une boucle pour tenter de nouveau l'opération en cas d'échec. La boucle ne peut pas être infinie, car il y a des cas pathologiques où une configuration de tas particulière conduira le garbage collector à ne jamais nettoyer un mapping. Cependant, une boucle d'environ 10 couvrira presque tous les cas. Si l'objet n'est pas parti à ce moment-là, alors il ne va nulle part et l'application devra abandonner. Cela peut sembler insuffisant, mais en pratique, il est extrêmement improbable et ne sera un problème que sur une machine virtuelle Java qui ne prend pas en charge les nettoyeurs.

Questions connexes