2010-01-17 12 views
10

que estoy tratando de publicar un archivo de vídeo/imagen grande desde el sistema de archivos local a una ruta http, pero me encuentro con un error de falta de memoria después de algún tiempo ...OutputStream OutOfMemoryError al enviar HTTP

aquí es el código

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; 

} 

aquí está el error que estoy recibiendo ...

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

Algunos (incluyéndome a mí) consideran que es grosero usar crosspost: http://forums.sun.com/thread.jspa?threadID=5424210 Especialmente cuando ni siquiera mencionas el hecho. –

+0

Por favor, no te ofendas en la edición donde critiqué tu código. Es mejor que la media, pero tiene margen de mejora. Todo el código no trivial lo hace. Y es fácil estropear el manejo de excepciones: obtuve un merecido -1 hace una semana más o menos cuando simplemente escribí un ejemplo try/catch/finally sin dejar que mi compilador lo comprobara. – kdgregory

Respuesta

13

El HttpUrlConnection está almacenando en el búfer los datos para que pueda establecer el encabezado Content-Length (por HTTP spec).

Una alternativa, si su servidor de destino lo admite, es usar transferencias "chunked". Esto amortiguará solo una pequeña porción de datos a la vez. Sin embargo, no todos los servicios lo admiten (Amazon S3, por ejemplo, no).

Otra alternativa (y una mejor) es usar Jakarta HttpClient. Puede establecer la "entidad" en una solicitud desde un archivo, y el código de conexión establecerá los encabezados de solicitud de manera apropiada.


Editar: nos comentaron que la OP podría llamar HttpURLConnection.setFixedLengthStreamingMode(long length). No estaba al tanto de este método; fue agregado en 1.5, y no he usado esta clase desde entonces.

Sin embargo, I aún sugieren utilizar Jakarta HttpClient, por la sencilla razón de que reduce la cantidad de código que el OP debe mantener. Código que es un texto estándar, pero que aún tiene el potencial de errores:

  • El OP maneja correctamente el bucle para copiar entre la entrada y la salida. Por lo general, cuando veo un ejemplo de esto, el póster no comprueba correctamente el tamaño del búfer devuelto o sigue reasignando los búferes. Felicidades, pero ahora debe asegurarse de que sus sucesores cuiden tanto.
  • El manejo de excepciones no es tan bueno. Sí, el OP recuerda cerrar las conexiones en un bloque finally, y nuevamente, felicidades por eso. Excepto que cualquiera de las llamadas close() podría lanzar IOException, evitando que la otra se ejecute. Y el método en su conjunto arroja Exception, por lo que el compilador no ayudará a detectar errores similares.
  • Cuento 31 líneas de código para configurar y ejecutar la respuesta (excluyendo la verificación del código de respuesta y el cálculo de URL, pero incluyendo el try/catch/finally). Con HttpClient, esto estaría en el rango de media docena de LOC.

Incluso si el PO había escrito el código perfectamente, y refactorizado en métodos similares a los de Jakarta Commons IO, s/él no debería hacer eso. Este código ha sido escrito y probado por otros.Sé que es un desperdicio de mi hora de reescribirlo, y sospecho que también es un desperdicio de tiempo del OP.

+4

En este caso, se podría llamar a HttpURLConnection.setFixedLengthStreamingMode, según el tamaño del archivo. Esto hará que HttpUrlConnection devuelva una transmisión directa ya que no tiene que almacenarse en búfer para determinar la longitud del contenido. – nos

+0

nos tiene razón. No es necesario utilizar una solicitud fragmentada o utilizar bibliotecas de terceros para esto. Simplemente llame a setFixedLengthStreamingMode con la longitud del archivo en HttpURLConnection antes de conectarse y getOutputStream devolverá un contenedor directo que no sea de almacenamiento en búfer al OutputStream del zócalo. – jarnbjo

+1

@jarnbjo - Supongo que usted es la persona que me votó negativamente a mí y a todos los demás que respondieron. Y aunque sus comentarios son valiosos, hubiera sido más constructivo publicarlos como respuesta. – kdgregory

-1

Su problema es que usted está tratando de arreglar bytes X de vídeo en x/n bytes de RAM, cuando N> 1.

Necesita leer el video en un búfer más pequeño y escribirlo sobre la marcha o reducir el tamaño del archivo o aumentar la memoria disponible para su proceso.

Compruebe su tamaño de pila. Puede usar -Xmx para aumentarlo si ha tomado el valor predeterminado.

2

El problema es que la clase HttpURLConnection usa una matriz de bytes para almacenar sus datos. Presumiblemente, este video que estás presionando está tomando más memoria que la disponible. Aquí tiene algunas opciones:

  1. Aumente la memoria de su aplicación. Puede usar la opción -Xmx1024m para proporcionar 1GB de memoria a su aplicación. Esto aumentará la cantidad de datos que puede almacenar en la memoria.

  2. Si aún se queda sin memoria, es posible que desee considerar probar con otra biblioteca para subir el video que no almacena todos los datos en la memoria a la vez. El Apache Commons HttpClient tiene esa característica. Consulte este sitio para obtener más información: http://hc.apache.org/httpclient-3.x/features.html. Consulte esta sección para el formulario de varias partes carga de archivos grandes: http://hc.apache.org/httpclient-3.x/methods/multipartpost.html

2

Para que las operaciones básicas conseguir cualquier cosa, la incorporada en java.net cosas HTTP no es muy buena. Se recomienda usar Apache Commons HttpClient para esto. Te permite hacer cosas mucho más intuitivo como esto:

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

que sustituye a una gran cantidad de su código caldera de la placa.

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

Y Para el almacenamiento intermedio se podría cubrir sus fuentes en el BufferedOutputStream y BufferedInputStream

buen ejemplo de chunked de subir se podía encontrar allí: gdata-java-client

1

HttpsURLConnection # setChunkedStreamingMode (1024 * 1024 * 10); // 10MB de fragmento Esto garantiza que cualquier archivo (de cualquier tamaño) se transmita por una conexión https, sin almacenamiento en búfer interno. Esto se debe usar cuando se desconoce el tamaño del archivo o la longitud del contenido.