2016-11-11 2 views
8

Je viens de rencontrer une erreur dans mon code de bibliothèque opensrc qui alloue un grand tampon pour apporter des modifications à un gros fichier flac, l'erreur survient sur une vieille machine PC avec 3 Go de mémoire en utilisant Java 1.8.0_74-b02 25,74 32bitComment éviter l'erreur mapFailed() lors de l'écriture dans un fichier volumineux sur un système avec une mémoire limitée

au départ, j'utilisé pour allouer juste un tampon

ByteBuffer audioData = ByteBuffer.allocateDirect((int)(fc.size() - fc.position())); 

Mais depuis quelque temps je l'ai comme

MappedByteBuffer mappedFile = fc.map(MapMode.READ_WRITE, 0, totalTargetSize); 

Ma (mauvaise) compréhension était que les tampons mappés utilisent moins de mémoire qu'un tampon direct car tout le tampon mappé n'a pas besoin d'être en mémoire en même temps que la partie utilisée. Mais cette réponse dit que l'utilisation de tampons d'octets mis en correspondance est une mauvaise idée si je ne suis pas qwuite clair comment cela fonctionne

Java Large File Upload throws java.io.IOException: Map failed

Le code complet peut être vu à here

+0

Il a échoué car il ne peut pas allouer autant d'espace _address_ sur 32 bits. Il ne s'agit pas d'insuffisance physique de RAM. –

+0

mais la taille du fichier est seulement 200mb, cela devrait être bon shoudnt –

+0

Vous ne pouvez pas être sûr, il a besoin d'un bloc contigu de cette taille. –

Répondre

2

Bien qu'un tampon mappé puisse utiliser moins de mémoire physique à un instant donné, il requiert toujours un espace d'adressage (logique) disponible égal à la taille totale (logique) du tampon. Pour aggraver les choses, il se peut (probablement) que cet espace d'adresse soit contigu. Pour une raison quelconque, cet ancien ordinateur semble incapable de fournir suffisamment d'espace d'adressage logique supplémentaire. Deux explications possibles sont (1) un espace d'adressage logique limité + une mémoire tampon importante et (2) une limitation interne que le système d'exploitation impose à la quantité de mémoire pouvant être mappée en tant que fichier pour les E/S. En ce qui concerne la première possibilité, considérons le fait que dans un système de mémoire virtuelle, chaque processus s'exécute dans son propre espace d'adressage logique (et a donc accès à l'adresse complète de 2^32 octets). Donc, si - au moment où vous essayez d'instancier le MappedByteBuffer - la taille actuelle du processus JVM plus la taille totale (logique) du MappedByteBuffer est supérieure à 2^32 octets (~ 4 gigaoctets), alors vous rencontreriez un (ou toute autre erreur/exception que cette classe a choisi de lancer à sa place, par exemple IOException: Map failed). En ce qui concerne la deuxième possibilité, la façon la plus simple d'évaluer ceci est de profiler votre programme/la JVM lorsque vous essayez d'instancier le MappedByteBuffer. Si la mémoire allouée du processus JVM + le totalTargetSize requis sont bien en deçà du plafond de 2^32 octets, mais que vous obtenez toujours une erreur "map failed", il est probable qu'une limite interne de l'OS sur la taille des fichiers mappés en mémoire est la cause-racine.

Alors qu'est-ce que cela signifie dans la mesure du possible?

  1. N'utilisez pas cet ancien PC.(préférable, mais probablement pas réalisable)
  2. Assurez-vous que tout le reste de votre JVM a une empreinte mémoire aussi faible que possible pour la durée de vie du MappedByteBuffer. (plausible, mais peut-être non pertinent et définitivement irréalisable)
  3. Brisez ce fichier en plus petits morceaux, puis travaillez sur un seul bloc à la fois. (peut dépendre de la nature du fichier)
  4. Utilisez un tampon différent/plus petit. ... et vient de supporter la baisse des performances. (ce qui est la solution la plus réaliste, même si elle est le plus frustrant)

En outre, ce qui est exactement le totalTargetSize pour votre cas de problème?


EDIT:

Après avoir fait quelques recherches, il semble clair que la IOException est due à running out of address space in a 32-bit environment. Cela peut se produire même lorsque le fichier lui-même est inférieur à 2^32 octets, soit en raison de l'espace d'adressage contigu insuffisante, ou en raison d'autres besoins d'espace d'adressage suffisamment importants dans la JVM simultanément combiné avec demande (see comments). Pour être clair, un IOE peut toujours être lancé plutôt qu'un MOO even if the original cause is ENOMEM. En outre, il semble y avoir des problèmes avec les environnements 32 bits plus anciens [insérer Microsoft OS ici] en particulier (example, example).

Ainsi, il semble que vous ayez trois choix principaux.

  1. Utilisez complètement "the 64-bit JRE or...another operating system".
  2. Utilisez un tampon plus petit d'un type différent et opérez sur le fichier en morceaux. (et prenez la performance en raison de ne pas utiliser un tampon mappé)
  3. Continuez à utiliser le MappedFileBuffer pour des raisons de performances, mais opérez également sur le fichier en plus petits morceaux afin de contourner les limitations d'espace d'adressage.

La raison pour laquelle je mets à l'aide MappedFileBuffer en petits morceaux comme troisième est à cause des problèmes bien établis et non résolus dans l'annulation du mappage d'un MappedFileBuffer (example), ce qui est quelque chose que vous auriez nécessairement à faire entre le traitement de chaque morceau dans afin d'éviter de toucher le plafond de 32 bits en raison de l'empreinte de l'espace d'adressage combiné des mappages accumulés. (NOTE: ceci s'applique uniquement si c'est le plafond de l'espace d'adressage 32 bits et pas certaines restrictions internes du système d'exploitation qui posent problème ... si ce dernier, ignorez ce paragraphe) Vous pouvez essayer this strategy (supprimer toutes les références puis exécuter le GC), mais vous seriez essentiellement à la merci de la façon dont le GC et votre système d'exploitation sous-jacent interagissent en ce qui concerne les fichiers mappés en mémoire. Et d'autres solutions de contournement potentielles qui tentent de manipuler plus ou moins directement le fichier mappé en mémoire sous-jacent (example) sont extrêmement dangereux et spécifiquement condamnés par Oracle (see last paragraph). Enfin, compte tenu du fait que le comportement GC n'est pas fiable de toute façon, et que la documentation officielle indique explicitement que "many of the details of memory-mapped files [are] unspecified", je voudrais pas recommander d'utiliser comme ceci sans tenir compte de toute solution de contournement que vous pourriez lire.Donc, sauf si vous êtes prêt à prendre le risque, je suggérerais soit de suivre le conseil explicite d'Oracle (point 1), soit de traiter le fichier comme une séquence de plus petits morceaux utilisant un type de tampon différent (point 2).

+0

Merci la solution que j'ai fait est 4>, la taille de la mémoire tampon que je créais précédemment était d'environ 200mb –

+0

@PaulTaylor Faites-moi savoir comment ça va. Voir mes modifications (deuxième section) pour mes informations détaillées + références sur le problème. En outre, voir le troisième paragraphe (édité dans) dans la première section si vous êtes assez curieux pour essayer de diagnostiquer le problème plutôt que de le contourner. – Travis

+0

Ok merci je ne peux pas utiliser seulement 64bit parce que j'ai besoin de continuer à supporter 32bit, Ill continuer à utiliser par bytebuffer dans les morceaux et oublier d'utiliser MappedByteBuffer. –

1

Lorsque vous allouez tampon, vous obtenez essentiellement morceau de mémoire virtuelle de votre système d'exploitation (et cette mémoire virtuelle est finie et théorique supérieure est votre RAM + quel que soit swap est configuré - tout ce qui a été saisi par d'autres programmes et OS)

La carte mémoire ajoute juste de l'espace occupé sur votre sur le disque à votre virtua l mémoire (ok, il y a un peu de frais généraux, mais pas beaucoup) - donc vous pouvez en avoir plus.

Aucun de ces éléments ne doit être présent en permanence dans la mémoire RAM, certaines de ses parties peuvent être échangées sur le disque à un moment donné.