成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

動手探究Java內(nèi)存泄露問題

譯文
開發(fā) 后端
在本系列教程中,將帶大家動手探究Java內(nèi)存泄露之謎,并教授給讀者相關(guān)的分析方法。以下是一個案例。

在本系列教程中,將帶大家動手探究Java內(nèi)存泄露之謎,并教授給讀者相關(guān)的分析方法。以下是一個案例。

最近有一個服務(wù)器,經(jīng)常運(yùn)行的時(shí)候就出現(xiàn)過載宕機(jī)的現(xiàn)象。重啟腳本和系統(tǒng)后,該個問題還是會出現(xiàn)。盡管有大量的數(shù)據(jù)丟失,但因不是關(guān)鍵業(yè)務(wù),問題并不嚴(yán)重。不過還是決定作進(jìn)一步的調(diào)查,來看下問題到底出現(xiàn)在哪。首先注意到的是,服務(wù)器通過了所有的單元測試和完整的集成環(huán)境的測試。在測試環(huán)境下使用測試數(shù)據(jù)時(shí)運(yùn)行正常,那么為什么在生產(chǎn)環(huán)境中運(yùn)行會出現(xiàn)問題呢?很容易會想到,也許是因?yàn)閷?shí)際運(yùn)行時(shí)的負(fù)載大于測試時(shí)的負(fù)載,甚至超過了設(shè)計(jì)的負(fù)荷,從而耗盡了資源。但是到底是什么資源,在哪里耗盡了呢?下面我們就研究這個問題

為了演示這個問題,首先要做的是編寫一些內(nèi)存泄露的代碼,將使用生產(chǎn)-消費(fèi)者模式去實(shí)現(xiàn),以便更好說明問題。

例子中,假定有這樣一個場景:假設(shè)你為一個證劵經(jīng)紀(jì)公司工作,這個公司將股票的銷售額和股份記錄在數(shù)據(jù)庫中。通過一個簡單進(jìn)程獲取命令并將其存放在一個隊(duì)列中。另一個進(jìn)程從該隊(duì)列中讀取命令并將其寫入數(shù)據(jù)庫。命令的POJO對象十分簡單,如下代碼所示:
 

  1. public class Order { 
  2.   
  3.   private final int id; 
  4.   
  5.   private final String code; 
  6.   
  7.   private final int amount; 
  8.   
  9.   private final double price; 
  10.   
  11.   private final long time; 
  12.   
  13.   private final long[] padding; 
  14.   
  15.   /** 
  16.    * @param id 
  17.    *            The order id 
  18.    * @param code 
  19.    *            The stock code 
  20.    * @param amount 
  21.    *            the number of shares 
  22.    * @param price 
  23.    *            the price of the share 
  24.    * @param time 
  25.    *            the transaction time 
  26.    */ 
  27.   public Order(int id, String code, int amount, double price, long time) { 
  28.     super(); 
  29.     this.id = id; 
  30.     this.code = code; 
  31.     this.amount = amount; 
  32.     this.price = price; 
  33.     this.time = time; 
  34.     
  35.     //這里故意設(shè)置Order對象足夠大,以方便例子稍后在運(yùn)行的時(shí)候耗盡內(nèi)存 
  36.     this.padding = new long[3000]; 
  37.     Arrays.fill(padding, 0, padding.length - 1, -2); 
  38.   } 
  39.   
  40.   public int getId() { 
  41.     return id; 
  42.   } 
  43.   
  44.   public String getCode() { 
  45.     return code; 
  46.   } 
  47.   
  48.   public int getAmount() { 
  49.     return amount; 
  50.   } 
  51.   
  52.   public double getPrice() { 
  53.     return price; 
  54.   } 
  55.   
  56.   public long getTime() { 
  57.     return time; 
  58.   } 
  59.   

這個POJO對象是Spring應(yīng)用的一部分,該應(yīng)用有三個主要的抽象類,當(dāng)Spring調(diào)用它們的start()方法的時(shí)候?qū)⒎謩e創(chuàng)建一個新的線程。

第一個抽象類是OrderFeed。run()方法將生成一系列隨機(jī)的Order對象,并將其放置在隊(duì)列中,然后它會睡眠一會兒,又再接著生成一個新的Order對象,代碼如下:

  1. public class OrderFeed implements Runnable { 
  2.  
  3.  private static Random rand = new Random(); 
  4.  
  5.  private static int id = 0
  6.  
  7.  private final BlockingQueue<Order> orderQueue; 
  8.  
  9.  public OrderFeed(BlockingQueue<Order> orderQueue) { 
  10.    this.orderQueue = orderQueue; 
  11.  } 
  12.  
  13.  /** 
  14.   *在加載Context上下文后由Spring調(diào)用,開始生產(chǎn)order對象 
  15.   */ 
  16.  public void start() { 
  17.  
  18.    Thread thread = new Thread(this"Order producer"); 
  19.    thread.start(); 
  20.  } 
  21.  
  22.   @Override 
  23.  public void run() { 
  24.  
  25.    while (true) { 
  26.      Order order = createOrder(); 
  27.      orderQueue.add(order); 
  28.      sleep(); 
  29.    } 
  30.  } 
  31.  
  32.  private Order createOrder() { 
  33.  
  34.    final String[] stocks = { "BLND.L""DGE.L""MKS.L""PSON.L""RIO.L""PRU.L"
  35.        "LSE.L""WMH.L" }; 
  36.    int next = rand.nextInt(stocks.length); 
  37.    long now = System.currentTimeMillis(); 
  38.  
  39.    Order order = new Order(++id, stocks[next], next * 100, next * 10, now); 
  40.    return order; 
  41.  } 
  42.  
  43.  private void sleep() { 
  44.    try { 
  45.      TimeUnit.MILLISECONDS.sleep(100); 
  46.    } catch (InterruptedException e) { 
  47.      e.printStackTrace(); 
  48.    } 
  49.  } 

#p#

第二個類是OrderRecord,這個類負(fù)責(zé)從隊(duì)列中提取Order對象,并將它們寫入數(shù)據(jù)庫。問題是,將Order對象寫入數(shù)據(jù)庫的耗時(shí)比產(chǎn)生Order對象的耗時(shí)要長得多。為了演示,將在recordOrder()方法中讓其睡眠1秒。

  1. public class OrderRecord implements Runnable { 
  2.  
  3.   private final BlockingQueue<Order> orderQueue; 
  4.  
  5.   public OrderRecord(BlockingQueue<Order> orderQueue) { 
  6.     this.orderQueue = orderQueue; 
  7.   } 
  8.  
  9.   public void start() { 
  10.  
  11.     Thread thread = new Thread(this"Order Recorder"); 
  12.     thread.start(); 
  13.   } 
  14.  
  15.   @Override 
  16.   public void run() { 
  17.  
  18.     while (true) { 
  19.  
  20.       try { 
  21.         Order order = orderQueue.take(); 
  22.         recordOrder(order); 
  23.       } catch (InterruptedException e) { 
  24.         e.printStackTrace(); 
  25.       } 
  26.     } 
  27.  
  28.   } 
  29.  
  30.   /** 
  31.    * 模擬記錄到數(shù)據(jù)庫的方法,這里只是簡單讓其睡眠一秒  
  32.    */ 
  33.   public void recordOrder(Order order) throws InterruptedException { 
  34.     TimeUnit.SECONDS.sleep(1); 
  35.   } 
  36.  

為了證明這個效果,特意增加了一個監(jiān)視類 OrderQueueMonitor ,這個類每隔幾秒就打印出隊(duì)列的大小,代碼如下:

  1. public class OrderQueueMonitor implements Runnable { 
  2.  
  3.   private final BlockingQueue<Order> orderQueue; 
  4.  
  5.   public OrderQueueMonitor(BlockingQueue<Order> orderQueue) { 
  6.     this.orderQueue = orderQueue; 
  7.   } 
  8.  
  9.   public void start() { 
  10.  
  11.     Thread thread = new Thread(this"Order Queue Monitor"); 
  12.     thread.start(); 
  13.   } 
  14.  
  15.   @Override 
  16.   public void run() { 
  17.  
  18.     while (true) { 
  19.  
  20.       try { 
  21.         TimeUnit.SECONDS.sleep(2); 
  22.         int size = orderQueue.size(); 
  23.         System.out.println("Queue size is:" + size); 
  24.       } catch (InterruptedException e) { 
  25.         e.printStackTrace(); 
  26.       } 
  27.     } 
  28.   } 
  29.  

接下來配置Spring框架的相關(guān)配置文件如下:

  1. <?xml version="1.0" encoding="UTF-8"?> 
  2. <beans xmlns="http://www.springframework.org/schema/beans" 
  3. xmlns:p="http://www.springframework.org/schema/p" 
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  5. xmlns:context="http://www.springframework.org/schema/context" 
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 
  7. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd" 
  8. default-init-method="start" 
  9. default-destroy-method="destroy"
  10.   
  11. <bean id="theQueue" class="java.util.concurrent.LinkedBlockingQueue"/>  
  12. <bean id="orderProducer"
  13. <constructor-arg ref="theQueue"/> 
  14. </bean> 
  15.   
  16. <bean id="OrderRecorder"
  17. <constructor-arg ref="theQueue"/> 
  18. </bean> 
  19.   
  20. <bean id="QueueMonitor"
  21. <constructor-arg ref="theQueue"/> 
  22. </bean> 
  23.   
  24. </beans> 

接下來運(yùn)行這個Spring應(yīng)用,并且可以通過jConsole去監(jiān)控應(yīng)用的內(nèi)存情況,這需要作一些配置,配置如下:

  1. -Dcom.sun.management.jmxremote  
  2. -Dcom.sun.management.jmxremote.port=9010  
  3. -Dcom.sun.management.jmxremote.local.only=false  
  4. -Dcom.sun.management.jmxremote.authenticate=false  
  5. -Dcom.sun.management.jmxremote.ssl=false 

如果你看看堆的使用量,你會發(fā)現(xiàn)隨著隊(duì)列的增大,堆的使用量逐漸增大,如下圖所示,你可能不會發(fā)現(xiàn)1KB的內(nèi)存泄露,但當(dāng)達(dá)到1GB的內(nèi)存溢出就很明顯了。所以,接下來要做的事情就是等待其溢出,然后進(jìn)行分析。

#p#

接下來我們來看下如何發(fā)現(xiàn)并解決這類問題。在Java中,可以借助不少自帶的或第三方的工具幫助我們進(jìn)行相關(guān)的分析。

下面介紹分析程序內(nèi)存泄露問題的三個步驟:

  1. 提取發(fā)生內(nèi)存泄露的服務(wù)器的轉(zhuǎn)儲文件。
  2. 用這個轉(zhuǎn)儲文件生成報(bào)告。
  3. 分析生成的報(bào)告。

有幾個工具能幫你生成堆轉(zhuǎn)儲文件,分別是:

  • jconsole
  •  visualvm
  • Eclipse Memory Analyser Tool(MAT)

用jconsole提取堆轉(zhuǎn)儲文件

使用jconsole連接到你的應(yīng)用:單擊MBeans選項(xiàng)卡打開com.sun.management包,點(diǎn)擊HotSpotDiagnostic,點(diǎn)擊Operations,然后選擇dumpHeap。這時(shí)你將會看到dumpHeap操作:它接受兩個參數(shù)p0和p1。在p0的編輯框內(nèi)輸入一個堆轉(zhuǎn)儲的文件名,然后按下DumpHeap按鈕就可以了。如下圖:

用jvisualvm提取堆轉(zhuǎn)儲文件

首先使用jvisual vm連接示例代碼,然后右鍵點(diǎn)擊應(yīng)用,在左側(cè)的“application”窗格中選擇“Heap Dump”。

注意:如果需要分析的發(fā)生內(nèi)存泄露的是在遠(yuǎn)程服務(wù)器上,那么jvisualvm將會把轉(zhuǎn)存出來的文件保存在遠(yuǎn)程機(jī)器(假設(shè)這是一臺unix機(jī)器)上的/tmp目錄下。

用MAT來提取堆轉(zhuǎn)儲文件

jconsole和jvisualvm本身就是JDK的一部分,而MAT或被稱作“內(nèi)存分析工具”,是一個基于eclipse的插件,可以從eclipse.org下載。

最新版本的MAT需要你在電腦上安裝JDk1.6。如果你用的是Java1.7版本也不用擔(dān)心,因?yàn)樗鼤詣訛槟惆惭b1.6版本,并且不會和安裝好的1.7版本產(chǎn)生沖突。

使用MAT的時(shí)候,只需要點(diǎn)擊“Aquire Heap Dump”,然后按步驟操作就可以了,如下圖:

要注意的是,使用上面的三種方法,都需要配置遠(yuǎn)程JMX連接如下:

  1. -Dcom.sun.management.jmxremote 
  2. -Dcom.sun.management.jmxremote.port=9010 
  3. -Dcom.sun.management.jmxremote.local.only=false 
  4. -Dcom.sun.management.jmxremote.authenticate=false 
  5. -Dcom.sun.management.jmxremote.ssl=false 

何時(shí)提取堆轉(zhuǎn)存文件

那么在什么時(shí)候才應(yīng)該提取堆轉(zhuǎn)存文件呢?這需要耗費(fèi)點(diǎn)心思和碰下運(yùn)氣。如果過早提取了堆轉(zhuǎn)儲文件,那么將可能不能發(fā)現(xiàn)問題癥結(jié)所在,因?yàn)樗鼈儽缓戏ǎ切孤额惖膶?shí)例屏蔽了。不過也不能等太久,因?yàn)樘崛《艳D(zhuǎn)儲文件也需要占用內(nèi)存,進(jìn)行提取的時(shí)候可能會導(dǎo)致應(yīng)用崩潰。

最好的辦法是將jconsole連接到應(yīng)用程序并監(jiān)控堆的占用情況,知道它何時(shí)在崩潰的邊緣。因?yàn)闆]有發(fā)生內(nèi)存泄露時(shí),三個堆部分指標(biāo)都是綠色的,這樣很容易就能監(jiān)控到,如下圖:

分析轉(zhuǎn)儲文件

現(xiàn)在輪到MAT派上用場了,因?yàn)樗旧砭褪窃O(shè)計(jì)用來分析堆轉(zhuǎn)儲文件的。要打開和分析一個堆轉(zhuǎn)儲文件,可以選擇File菜單的Heap Dump選項(xiàng)。選擇了要打開的文件后,將會看到如下三個選項(xiàng):

#p#

選擇Leak Suspect Report選項(xiàng)。在MAT運(yùn)行幾秒后,會生成如下圖的頁面:

如餅狀圖顯示:疑似有一處發(fā)生了內(nèi)存泄露。也許你會想,這樣的做法只有在代碼受到控制的情況下才可取。畢竟這只是個例子,這又能說明什么呢?好吧,在這個例子里,所有的問題都是淺然易見的;線程a占用了98.7MB內(nèi)存,其他線程用了1.5MB。在實(shí)際情況中,得到的圖表可能是上圖那樣。讓我們繼續(xù)探究,會得到如下圖:

如上圖所示,報(bào)告的下一部分告訴我們,有一個LinkedBlockQueue占用了98.46%的內(nèi)存。想要進(jìn)一步的探究,點(diǎn)擊Details>>就可以了,如下圖:

可以看到,問題確實(shí)是出在我們的orderQueue上。這個隊(duì)列里存儲了所有生成的隨機(jī)生成的Order對象,并且可以被我們上篇博文里提到的三個線程OrderFeed、OrderRecord、OrderMonitor訪問。

那么一切都清楚了,MAT告訴我們:示例代碼中有一個LinkedBlockQueue,這個隊(duì)列用盡了所有的內(nèi)存,從而導(dǎo)致了嚴(yán)重的問題。不過我們不知道這個問題為什么會產(chǎn)生,也不能指望MAT告訴我們。

本文代碼可以在:https://github.com/roghughe/captaindebug/tree/master/producer-consumer中下載。

原文鏈接:http://www.javacodegeeks.com/2013/12/investigating-memory-leaks-part-1-writing-leaky-code.html

責(zé)任編輯:陳四芳 來源: 51CTO
相關(guān)推薦

2010-05-31 16:53:21

Java

2024-10-31 09:24:42

2020-06-23 09:48:09

Python開發(fā)內(nèi)存

2010-09-25 11:32:24

Java內(nèi)存泄漏

2023-06-30 23:25:46

HTTP模塊內(nèi)存

2025-01-08 08:47:44

Node.js內(nèi)存泄露定時(shí)器

2009-06-16 11:11:07

Java內(nèi)存管理Java內(nèi)存泄漏

2013-04-09 14:49:18

Linux內(nèi)存統(tǒng)計(jì)內(nèi)存泄露

2010-09-17 16:18:43

Java內(nèi)存溢出

2015-05-20 16:04:22

Chrome

2015-12-07 09:39:27

Java內(nèi)存泄露

2015-05-14 15:38:40

javajava內(nèi)存泄露

2011-11-17 13:59:41

Java內(nèi)存管理內(nèi)存泄露

2012-04-11 13:46:33

ibmdw

2017-12-11 11:00:27

內(nèi)存泄露判斷

2011-06-22 13:47:16

Java多線程

2010-09-25 11:23:15

Java內(nèi)存泄露

2011-06-22 13:57:54

Java多線程

2013-08-07 10:07:07

Handler內(nèi)存泄露

2022-10-10 11:37:14

Gomap內(nèi)存
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 一区二区三区免费 | 中文字幕欧美一区二区 | 99热这里有精品 | av在线播放网址 | 午夜影院在线免费观看视频 | 日本久久久一区二区三区 | 日本三级播放 | 日韩在线三级 | 一区欧美 | 亚洲69p | 婷婷色国产偷v国产偷v小说 | h视频在线观看免费 | 欧美性猛交一区二区三区精品 | 国产精品三级久久久久久电影 | 欧美激情在线观看一区二区三区 | 亚洲综合色视频在线观看 | 精品一级 | 国产一区二区 | 久久久久久久久99 | 精品一二三区 | 欧美一区二区在线观看 | 偷拍自拍网址 | 一级在线观看 | 久久久久国产一区二区三区 | 亚洲午夜精品一区二区三区 | 国产精品一区二区久久 | 毛片区| 曰韩一二三区 | 精品国产欧美一区二区三区成人 | 欧美久久久久久 | 久久久久久国产精品 | 色婷婷av一区二区三区软件 | 国产成人免费视频网站高清观看视频 | 亚洲一区中文字幕在线观看 | 国产一区二区电影 | 精品久久视频 | 久久午夜精品 | 成人免费福利 | 国产成人小视频 | 日本a网站 | 91在线精品视频 |