欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java 分析并解決內存泄漏的實例

 更新時間:2020年08月28日 08:44:14   作者:technology  
這篇文章主要介紹了Java 分析并解決內存泄漏的實例,幫助大家更好的理解和學習Java,感興趣的朋友可以了解下

這幾天,一直在為Java的“內存泄露”問題糾結。Java應用程序占用的內存在不斷的、有規(guī)律的上漲,最終超過了監(jiān)控閾值。福爾摩 斯不得不出手了!

分析內存泄露的一般步驟

如果發(fā)現Java應用程序占用的內存出現了泄露的跡象,那么我們一般采用下面的步驟分析:

  1. 把Java應用程序使用的heap dump下來
  2. 使用Java heap分析工具,找出內存占用超出預期(一般是因為數量太多)的嫌疑對象
  3. 必要時,需要分析嫌疑對象和其他對象的引用關系。
  4. 查看程序的源代碼,找出嫌疑對象數量過多的原因。

dump heap

如果Java應用程序出現了內存泄露,千萬別著急著把應用殺掉,而是要保存現場。如果是互聯(lián)網應用,可以把流量切到其他服務器。保存現場的目的就是為了把 運行中JVM的heap dump下來。

JDK自帶的jmap工具,可以做這件事情。它的執(zhí)行方法是:

jmap -dump:format=b,file=heap.bin <pid> 

format=b的含義是,dump出來的文件時二進制格式。
file-heap.bin的含義是,dump出來的文件名是heap.bin。
<pid>就是JVM的進程號。
(在linux下)先執(zhí)行ps aux | grep java,找到JVM的pid;然后再執(zhí)行jmap -dump:format=b,file=heap.bin <pid>,得到heap dump文件。

analyze heap

將二進制的heap dump文件解析成human-readable的信息,自然是需要專業(yè)工具的幫助,這里推薦Memory Analyzer 。

Memory Analyzer,簡稱MAT,是Eclipse基金會的開源項目,由SAP和IBM捐助。巨頭公司出品的軟件還是很中用的,MAT可以分析包含數億級對 象的heap、快速計算每個對象占用的內存大小、對象之間的引用關系、自動檢測內存泄露的嫌疑對象,功能強大,而且界面友好易用。

MAT的界面基于Eclipse開發(fā),以兩種形式發(fā)布:Eclipse插件和Eclipe RCP。MAT的分析結果以圖片和報表的形式提供,一目了然??傊畟€人還是非常喜歡這個工具的。下面先貼兩張官方的screenshots:

言歸正傳,我用MAT打開了heap.bin,很容易看出,char[]的數量出其意料的多,占用90%以上的內存 。一般來說,char[]在JVM確實會占用很多內存,數量也非常多,因為String對象以char[]作為內部存儲。但是這次的char[]太貪婪 了,仔細一觀察,發(fā)現有數萬計的char[],每個都占用數百K的內存 。這個現象說明,Java程序保存了數以萬計的大String對象 。結合程序的邏輯,這個是不應該的,肯定在某個地方出了問題。

順藤摸瓜

在可疑的char[]中,任意挑了一個,使用Path To GC Root功能,找到該char[]的引用路徑,發(fā)現String對象是被一個HashMap中引用的 。這個也是意料中的事情,Java的內存泄露多半是因為對象被遺留在全局的HashMap中得不到釋放。不過,該HashMap被用作一個緩存,設置了緩 存條目的閾值,導達到閾值后會自動淘汰。從這個邏輯分析,應該不會出現內存泄露的。雖然緩存中的String對象已經達到數萬計,但仍然沒有達到預先設置 的閾值(閾值設置地比較大,因為當時預估String對象都比較?。?。

但是,另一個問題引起了我的注意:為什么緩存的String對象如此巨大?內部char[]的長度達數百K。雖然緩存中的 String對象數量還沒有達到閾值,但是String對象大小遠遠超出了我們的預期,最終導致內存被大量消耗,形成內存泄露的跡象(準確說應該是內存消 耗過多) 。

就這個問題進一步順藤摸瓜,看看String大對象是如何被放到HashMap中的。通過查看程序的源代碼,我發(fā)現,確實有String大對象,不 過并沒有把String大對象放到HashMap中,而是把String大對象進行split(調用String.split方法),然后將split出 來的String小對象放到HashMap中 了。

這就奇怪了,放到HashMap中明明是split之后的String小對象,怎么會占用那么大空間呢?難道是String類的split方法有問題?

查看代碼

帶著上述疑問,我查閱了Sun JDK6中String類的代碼,主要是是split方法的實現:

public  
String[] split(String regex, int limit) { 
  return Pattern.compile(regex).split(this, limit); 
} 

可以看出,Stirng.split方法調用了Pattern.split方法。繼續(xù)看Pattern.split方法的代碼:

public  
String[] split(CharSequence input, int limit) { 
    int index = 0; 
    boolean matchLimited = limit > 0; 
    ArrayList<String> matchList = new  
ArrayList<String>(); 
    Matcher m = matcher(input); 
    // Add segments before each match found 
    while(m.find()) { 
      if (!matchLimited || matchList.size() < limit - 1) { 
        String match = input.subSequence(index,  
m.start()).toString(); 
        matchList.add(match); 
        index = m.end(); 
      } else if (matchList.size() == limit - 1) { // last one 
        String match = input.subSequence(index, 
                          
input.length()).toString(); 
        matchList.add(match); 
        index = m.end(); 
      } 
    } 
    // If no match was found, return this 
    if (index == 0) 
      return new String[] {input.toString()}; 
    // Add remaining segment 
    if (!matchLimited || matchList.size() < limit) 
      matchList.add(input.subSequence(index,  
input.length()).toString()); 
    // Construct result 
    int resultSize = matchList.size(); 
    if (limit == 0) 
      while (resultSize > 0 &&  
matchList.get(resultSize-1).equals("")) 
        resultSize--; 
    String[] result = new String[resultSize]; 
    return matchList.subList(0, resultSize).toArray(result); 
  } 
  注意看第9行:Stirng match = input.subSequence(intdex, m.start()).toString();

這里的match就是split出來的String小對象,它其實是String大對象subSequence的結果。繼續(xù)看 String.subSequence的代碼:

public  
CharSequence subSequence(int beginIndex, int endIndex) { 
    return this.substring(beginIndex, endIndex); 
} 
  String.subSequence有調用了String.subString,繼續(xù)看:
public String  
substring(int beginIndex, int endIndex) { 
  if (beginIndex < 0) { 
    throw new StringIndexOutOfBoundsException(beginIndex); 
  } 
  if (endIndex > count) { 
    throw new StringIndexOutOfBoundsException(endIndex); 
  } 
  if (beginIndex > endIndex) { 
    throw new StringIndexOutOfBoundsException(endIndex - beginIndex); 
  } 
  return ((beginIndex == 0) && (endIndex == count)) ? this : 
    new String(offset + beginIndex, endIndex - beginIndex, value); 
  } 

看第11、12行,我們終于看出眉目,如果subString的內容就是完整的原字符串,那么返回原String對象;否則,就會創(chuàng)建一個新的 String對象,但是這個String對象貌似使用了原String對象的char[]。我們通過String的構造函數確認這一點:

// Package  
private constructor which shares value array for speed. 
  String(int offset, int count, char value[]) { 
  this.value = value; 
  this.offset = offset; 
  this.count = count; 
  } 

為了避免內存拷貝、加快速度,Sun JDK直接復用了原String對象的char[],偏移量和長度來標識不同的字符串內容。也就是說,subString出的來String小對象 仍然會指向原String大對象的char[],split也是同樣的情況 。這就解釋了,為什么HashMap中String對象的char[]都那么大。

原因解釋

其實上一節(jié)已經分析出了原因,這一節(jié)再整理一下:

程序從每個請求中得到一個String大對象,該對象內部char[]的長度達數百K。
程序對String大對象做split,將split得到的String小對象放到HashMap中,用作緩存。
Sun JDK6對String.split方法做了優(yōu)化,split出來的Stirng對象直接使用原String對象的char[]
HashMap中的每個String對象其實都指向了一個巨大的char[]
HashMap的上限是萬級的,因此被緩存的Sting對象的總大小=萬*百K=G級。
G級的內存被緩存占用了,大量的內存被浪費,造成內存泄露的跡象。

解決方案

原因找到了,解決方案也就有了。split是要用的,但是我們不要把split出來的String對象直接放到HashMap中,而是調用一下 String的拷貝構造函數String(String original),這個構造函數是安全的,具體可以看代碼:

  /** 
   * Initializes a newly created {@code String} object so that it 
represents 
   * the same sequence of characters as the argument; in other words, 
the 
   * newly created string is a copy of the argument string. Unless an 
   * explicit copy of {@code original} is needed, use of this 
constructor is 
   * unnecessary since Strings are immutable. 
   * 
   * @param original 
   *     A {@code String} 
   */ 
  public String(String original) { 
  int size = original.count; 
  char[] originalValue = original.value; 
  char[] v; 
  if (originalValue.length > size) { 
    // The array representing the String is bigger than the new 
    // String itself. Perhaps this constructor is being called 
    // in order to trim the baggage, so make a copy of the array. 
      int off = original.offset; 
      v = Arrays.copyOfRange(originalValue, off, off+size); 
  } else { 
    // The array representing the String is the same 
    // size as the String, so no point in making a copy. 
    v = originalValue; 
  } 
  this.offset = 0; 
  this.count = size; 
  this.value = v; 
  } 

只是,new String(string)的代碼很怪異,囧?;蛟S,subString和split應該提供一個選項,讓程序員控制是否復用String對象的 char[]。

是否Bug

雖然,subString和split的實現造成了現在的問題,但是這能否算String類的bug呢?個人覺得不好說。因為這樣的優(yōu)化是比較合理 的,subString和spit的結果肯定是原字符串的連續(xù)子序列。只能說,String不僅僅是一個核心類,它對于JVM來說是與原始類型同等重要的 類型。

JDK實現對String做各種可能的優(yōu)化都是可以理解的。但是優(yōu)化帶來了憂患,我們程序員足夠了解他們,才能用好他們。

一些補充

有個地方我沒有說清楚。

我的程序是一個Web程序,每次接受請求,就會創(chuàng)建一個大的String對象,然后對該String對象進行split,最后split之后的String對象放到全局緩存中。如果接收了5W個請求,那么就會有5W個大String對象。這5W個大String對象都被存儲在全局緩存中,因此會造成內存泄漏。我原以為緩存的是5W個小String,結果都是大String。

有同學后續(xù)建議用"java.io.StreamTokenizer"來解決本文的問題。確實是終極解決方案,比我上面提到的“new String()”,要好很多很多。

以上就是Java 分析并解決內存泄漏的實例的詳細內容,更多關于Java 內存泄漏的資料請關注腳本之家其它相關文章!

相關文章

  • SpringMVC如何配置JSP視圖解析器

    SpringMVC如何配置JSP視圖解析器

    這篇文章主要介紹了SpringMVC如何配置JSP視圖解析器問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • SpringMVC自定義類型轉換器實現解析

    SpringMVC自定義類型轉換器實現解析

    這篇文章主要介紹了SpringMVC自定義類型轉換器實現解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-12-12
  • springboot版本升級以及解決springsecurity漏洞的問題

    springboot版本升級以及解決springsecurity漏洞的問題

    這篇文章主要介紹了springboot版本升級以及解決springsecurity漏洞的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • java調用process線程阻塞問題的解決

    java調用process線程阻塞問題的解決

    這篇文章主要介紹了java調用process線程阻塞問題的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Java?CAS機制詳解

    Java?CAS機制詳解

    這篇文章主要介紹了Java?CAS機制,CAS機制是一種數據更新的方式。在具體講什么是CAS機制之前,我們先來聊下在多線程環(huán)境下,對共享變量進行數據更新的兩種模式:悲觀鎖模式和樂觀鎖模式
    2023-01-01
  • Java前端開發(fā)框架實現的流程和代碼示例

    Java前端開發(fā)框架實現的流程和代碼示例

    我們可以實現一個Java前端開發(fā)框架,這個框架包含了初始化、組件渲染、組件更新、事件監(jiān)聽和事件觸發(fā)等功能,希望這個指南能夠對剛入行的小白有所幫助
    2023-10-10
  • java實現解析Cron時間表達式為中文描述

    java實現解析Cron時間表達式為中文描述

    這篇文章主要為大家詳細介紹了java如何實現解析Cron時間表達式為中文描述,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的小伙伴可以了解下
    2023-11-11
  • EL調用Java方法_動力節(jié)點Java學院整理

    EL調用Java方法_動力節(jié)點Java學院整理

    簡單來說,我們在一個類中的某個方法,可以使用EL進行調用,這個能被EL表達式調用的方法稱之為EL函數,但是這種方式必須滿足兩點要求,具體哪兩點,大家可以參考下本文
    2017-07-07
  • MyBatis關于二級緩存問題

    MyBatis關于二級緩存問題

    本篇文章主要介紹了MyBatis關于二級緩存問題,二級緩存是Mapper級別的緩存,多個sqlSession操作同一個Mapper,其二級緩存是可以共享的。
    2017-03-03
  • Java日常練習題,每天進步一點點(47)

    Java日常練習題,每天進步一點點(47)

    下面小編就為大家?guī)硪黄狫ava基礎的幾道練習題(分享)。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你
    2021-08-08

最新評論