2010-01-17 5 views
10

Je suis en train de publier un gros fichier vidéo/image du système de fichiers local à un chemin http, mais je lance dans une erreur de dépassement de mémoire après un certain temps ...OutputStream OutOfMemoryError lors de l'envoi HTTP

ici est le code

public boolean publishFile(URI publishTo, String localPath) throws Exception { 
    InputStream istream = null; 
    OutputStream ostream = null; 
    boolean isPublishSuccess = false; 

    URL url = makeURL(publishTo.getHost(), this.port, publishTo.getPath()); 
    HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 


    if (conn != null) { 

     try { 

      conn.setDoOutput(true); 
      conn.setDoInput(true); 
      conn.setRequestMethod("PUT"); 
      istream = new FileInputStream(localPath); 
      ostream = conn.getOutputStream(); 

      int n; 
      byte[] buf = new byte[4096]; 
      while ((n = istream.read(buf, 0, buf.length)) > 0) { 
       ostream.write(buf, 0, n); //<--- ERROR happens on this line.......??? 
      } 

      int rc = conn.getResponseCode(); 

      if (rc == 201) { 
       isPublishSuccess = true; 
      } 

     } catch (Exception ex) { 
      log.error(ex); 
     } finally { 
      if (ostream != null) { 
       ostream.close(); 
      } 

      if (istream != null) { 
       istream.close(); 
      } 
     } 
    } 

    return isPublishSuccess; 

} 

VOICI l'erreur que je reçois ...

Exception in thread "Thread-8773" java.lang.OutOfMemoryError: Java heap space 
    at java.util.Arrays.copyOf(Arrays.java:2786) 
    at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:94) 
    at sun.net.www.http.PosterOutputStream.write(PosterOutputStream.java:61) 
    at com.test.HTTPClient.publishFile(HTTPClient.java:110) 
    at com.test.HttpFileTransport.put(HttpFileTransport.java:97) 
+0

Certains (y compris moi) considèrent qu'il est impoli de crosspost: http://forums.sun.com/thread.jspa?threadID=5424210 Surtout quand vous ne mentionnez même pas le fait. –

+0

S'il vous plaît ne vous offusquez pas à l'édition où j'ai critiqué votre code. C'est mieux que la moyenne, mais il y a place à l'amélioration. Tout le code non-trivial fait. Et il est facile de gâcher la gestion des exceptions: j'ai obtenu un -1 bien mérité il y a environ une semaine quand j'ai juste tapé un exemple try/catch/finally sans laisser mon compilateur le vérifier. – kdgregory

Répondre

13

Le HttpUrlConnection est mise en mémoire tampon les données de façon à pouvoir régler l'en-tête Content-Length (par HTTP spec).

Une alternative, si votre serveur de destination le prend en charge, consiste à utiliser les transferts "chunked". Cela ne mettra en mémoire tampon qu'une petite partie des données à la fois. Cependant, tous les services ne le supportent pas (Amazon S3, par exemple, ne le supporte pas). Une autre alternative (et une meilleure) est d'utiliser Jakarta HttpClient. Vous pouvez définir "l'entité" dans une demande à partir d'un fichier et le code de connexion définira les en-têtes de requête de manière appropriée.


Edit: nos ont indiqué que l'OP pourrait appeler HttpURLConnection.setFixedLengthStreamingMode(long length). Je n'étais pas au courant de cette méthode; il a été ajouté en 1.5, et je n'ai pas utilisé cette classe depuis.

Toutefois, je encore suggère d'utiliser Jakarta HttpClient, pour la simple raison qu'il réduit la quantité de code que l'OP doit maintenir. Le code qui est standard, mais qui a toujours le potentiel d'erreurs:

  • L'OP gère correctement la boucle pour copier entre l'entrée et la sortie. Habituellement, quand je vois un exemple de ceci, l'affiche ne vérifie pas correctement la taille de la mémoire tampon retournée, ou continue de réaffecter les tampons. Félicitations, mais vous devez maintenant veiller à ce que vos successeurs prennent autant de soin.
  • La gestion des exceptions est moins bonne. Oui, l'OP se souvient de fermer les connexions dans un bloc finally, et encore une fois, félicitations à ce sujet. Sauf que l'un des appels close() pouvait lancer IOException, empêchant l'autre de s'exécuter. Et la méthode dans son ensemble lance Exception, de sorte que le compilateur ne va pas aider à attraper des erreurs similaires.
  • Je compte 31 lignes de code pour configurer et exécuter la réponse (excluant la vérification du code de réponse et le calcul de l'URL, mais en incluant le try/catch/finally). Avec HttpClient, ce serait quelque part dans la fourchette d'une demi-douzaine de LOC.

Même si l'OP avait écrit ce code parfaitement et refondus dans des méthodes similaires à celles de Jakarta Commons IO, s/il ne devrait pas faire. Ce code a été écrit et testé par d'autres.Je sais que c'est une perte de temps pour le réécrire, et je soupçonne que c'est aussi une perte de temps pour l'OP.

+4

Dans ce cas, HttpURLConnection.setFixedLengthStreamingMode peut également être appelé, en fonction de la taille du fichier. Cela provoquera le HttpUrlConnection à renvoyer un flux direct car il n'a pas à tampon pour comprendre la longueur du contenu. – nos

+0

nos est juste. Il n'est absolument pas nécessaire d'utiliser une requête en bloc ou d'utiliser des bibliothèques tierces pour cela. Appelez simplement setFixedLengthStreamingMode avec la longueur du fichier sur le HttpURLConnection avant de vous connecter et getOutputStream retournera un wrapper direct non-buffering au OutputStream du socket. – jarnbjo

+1

@jarnbjo - Je suppose que vous êtes la personne qui m'a downvoted et tout le monde qui a répondu. Et même si vos commentaires sont précieux, il aurait été plus constructif de les afficher en réponse. – kdgregory

-1

Votre problème est que vous essayez de fixer octets vidéo X en octets X/N de RAM, lorsque N> 1.

Vous devez soit lire la vidéo dans un tampon plus petit et l'écrire comme vous allez ou réduire le fichier ou augmenter la mémoire disponible pour votre processus.

Vérifiez la taille de votre segment. Vous pouvez utiliser -Xmx pour l'augmenter si vous avez pris la valeur par défaut.

2

Le problème est que la classe HttpURLConnection utilise un tableau d'octets pour stocker vos données. Vraisemblablement, cette vidéo que vous poussez prend plus de mémoire que disponible. Vous avez quelques options ici:

  1. Augmentez la mémoire de votre application. Vous pouvez utiliser l'option -Xmx1024m pour donner 1 Go de mémoire à votre application. Cela augmentera la quantité de données que vous pouvez stocker en mémoire.

  2. Si vous n'avez toujours pas assez de mémoire, vous pouvez envisager d'essayer une autre bibliothèque pour pousser la vidéo vers le haut et ne pas stocker toutes les données en une seule fois. Le Apache Commons HttpClient a une telle fonctionnalité. Voir ce site pour plus d'informations: http://hc.apache.org/httpclient-3.x/features.html. Voir cette section pour le téléchargement de formulaires en plusieurs parties de fichiers volumineux: http://hc.apache.org/httpclient-3.x/methods/multipartpost.html

2

Pour tout autre que les opérations GET de base, le java.net stuff HTTP intégré est pas très bon. L'utilisation de Apache Commons HttpClient est recommandée pour cela. Il vous permet de faire des choses beaucoup plus intuitives comme ceci:

PutMethod put = new PutMethod(url); 
put.setRequestEntity(new FileRequestEntity(localFile, contentType)); 
int responseCode = put.executeMethod(); 

qui remplace une grande partie de votre code de plaque de chaudière.

3
conn.setFixedLengthStreamingMode((int) new File(localpath).length()); 

Et pour vous tamponner pouvez couvrir vos flux dans le BufferedOutputStream et BufferedInputStream

Bon exemple de CHUNKED téléchargement, vous pouvez y trouver: gdata-java-client

1

HttpsURLConnection # setChunkedStreamingMode (1024 * 1024 * dix); // Morceau de 10 Mo Cela garantit que tout fichier (de toute taille) est diffusé sur une connexion https, sans mise en mémoire tampon interne. Cela devrait être utilisé lorsque la taille du fichier ou la longueur du contenu est inconnue.

Questions connexes