2011/08/04

[Java] 小心 substring 的 memory leak

簡單的來說,假設我們有一段程式碼:
public class SubstringMemoryLeak {
private String str = new String(new byte[10000]);
public String substring() {
return this.str.substring(0, 2);
}
/**
*@param args
*/
public static void main(String[] args) {
List<String> substringList = new ArrayList<String>(10000);
for (int i=0, n=10000; i<n; i++) {
substringList.add(new SubstringMemoryLeak().substring());
}
}
}

如果執行上面的程式碼,你就會出現 Exception:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at
但是如果將 substring() method 修改成:

public String substring() {
   return new String(this.str.substring(0, 2)); 
}


神奇的事情發生了!程式竟然可以如願的執行!問題出現在哪?上面的程式碼最主要是建立一堆很佔記憶體的 String 物件,然後取其中的一小段!重點就在於 String 的 substring() method 的實做方式。
首先,String 物件在記憶體中會以 char array 方式呈現,當我們每次建立一個 String 時,記憶體就會長出一塊 char array 來存放。String class 有一個 non-public constructor:

String (int offset, int count, char[] value)

當我們使用 substring 時,實際是用這個 constructor 來完成,也就是原先建立很佔記憶體的 String 物件並不會縮小,而是保持原來的 char array 所佔的大小。所以 Java 的 GC 就無法對此 char array 進行回收的動作。所以使用 substring() method 對字串內容來說,我們看到的是部份的,但是在記憶體中卻是佔有原先的大小!

後來,我們改用 new String() constructor 來包裝 substring 的內容,這就會讓 Java 重新建一個 char array 來放置 substring 的內容,相對的,所佔得記憶體就小了,而且原先較大的 char array 就沒有任何 reference ,所以 GC 就可以直接回收了!

老實說,這種情況可能不太常見,至少對我來說啦!我對於 String 的建立都會盡量改用 StringBuffer 物件,這樣不僅可以提供較好得效能,對於記憶體的利用也比較不會有問題!


2011/06/23

JAVA下載的幾種方式:

jsp下載的幾種方式:
1、運用struts的下載類DownloadAction
2、直接流

1、運用struts的下載類DownloadAction
  1. package cn.finefuture.common.faxserver.struts;
  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import org.apache.struts.action.ActionForm;
  7. import org.apache.struts.action.ActionForward;
  8. import org.apache.struts.action.ActionMapping;
  9. import org.apache.struts.actions.DownloadAction;
  10. import cn.finefuture.common.faxserver.service.FaxServerService;
  11. import cn.finefuture.common.faxserver.service.impl.FaxServerServiceImp;
  12. public class FaxDownAction extends DownloadAction {
  13. private FaxServerService faxServerService = new FaxServerServiceImp();
  14. public void setFaxServerService(FaxServerService faxServerService) {
  15. this .faxServerService = faxServerService;
  16. }
  17. @Override
  18. protected StreamInfo getStreamInfo(ActionMapping mapping, ActionForm form,
  19. HttpServletRequest request, HttpServletResponse response)
  20. throws Exception {
  21. // 傳真編號
  22. String faxId = request.getParameter( "faxId" );
  23. InputStream inStream = null ;
  24. if (faxId != null && !(faxId.equals( "" ))) {
  25. //獲取InputStream
  26. inStream = this .faxServerService.getFaxInAtt(faxId);
  27. }
  28. final InputStream in = inStream;
  29. final String contentType = "application/file" ;
  30. // 建
  31. // 無法在保存文件對話框中顯示正確的文件名
  32. response.setHeader( "content-disposition" , "attachment; filename="
  33. + faxId);
  34. return new DownloadAction.StreamInfo() //使用隱式的方法實現了StreamInfo接口
  35. {
  36. public String getContentType() {
  37. return contentType;
  38. }
  39. public InputStream getInputStream() throws IOException {
  40. return in;
  41. }
  42. };
  43. }
  44. // 如果Struts動作不加file請求參數,則通過execute方法將指定目錄中文件列表輸出到客戶端
  45. public ActionForward execute(ActionMapping mapping, ActionForm form,
  46. HttpServletRequest request, HttpServletResponse response)
  47. throws Exception {
  48. // 當file參數存在時,則調用DownloadAction中的execute方法
  49. // 實際上,在DownloadAction類中的execute方法調用了getStreamInfo方法
  50. // 這條語句就相當於調用了getStreamInfo方法
  51. return super .execute(mapping, form, request, response);
  52. }
  53. }






2、第二種自己寫輸出流

  1. /**
  2. * 下載附件
  3. *
  4. * @param mapping
  5. * @param form
  6. * @param request
  7. * @param response
  8. * @return
  9. */
  10. public ActionForward doDownLoadAttachment(ActionMapping mapping,
  11. ActionForm form, HttpServletRequest request,
  12. HttpServletResponse response) {
  13. // 傳真編號
  14. String faxId = request.getParameter( "faxId" );
  15. InputStream inStream = null ;
  16. if (faxId != null && !(faxId.equals( "" ))) {
  17. inStream = this .faxServerService.getFaxInAtt(faxId);
  18. }
  19. // 流不等於null
  20. if (inStream != null ) {
  21. try {
  22. // 設置輸出的格式
  23. response.reset();
  24. response.setContentType( "bin" );
  25. // 給中文字符轉碼
  26. faxId = new String(faxId.getBytes( "GBK" ), "iso8859-1" );
  27. // 設置文件名
  28. response.setHeader( "Content-Disposition" ,
  29. "attachment;filename=" + faxId);
  30. // 循環取出流中的數據
  31. byte [] b = new byte [ 1024 ];
  32. int len;
  33. while ((len = inStream.read(b)) > 0 ) {
  34. response.getOutputStream().write(b, 0 , len);
  35. }
  36. } catch (IOException e) {
  37. e.printStackTrace();
  38. } finally {
  39. try {
  40. inStream.close();
  41. } catch (IOException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. }
  46. return null ;
  47. }




參考:


http://blog.csdn.net/chenyanbo/archive/2011/01/20/6154260.aspx