1. 程式人生 > >轉一篇 有關JAVA的記憶體洩露的文章

轉一篇 有關JAVA的記憶體洩露的文章

1 引言
     Java的一個重要優點就是通過垃圾收集器GC (Garbage Collection)自動管理記憶體的回收,程式設計師不需要通過呼叫函式來釋放記憶體。因此,很多程式設計師認為Java 不存在記憶體洩漏問題,或者認為即使有記憶體洩漏也不是程式的責任,而是GC 或JVM的問題。其實,這種想法是不正確的,因為Java 也存在記憶體洩漏,但它的表現與C++不同。如果正在開發的Java 程式碼要全天24 小時在伺服器上執行,則記憶體漏洞在此處的影響就比在配置實用程式中的影響要大得多,即使最小的漏洞也會導致JVM耗盡全部可用記憶體。另外,在很多嵌入式系統中,記憶體的總量非常有限。在相反的情況下,即便程式的生存期較短,如果存在分配大量臨時物件(或者若干吞噬大量記憶體的物件)的任何Java 程式碼,而且當不再需要這些物件時也沒有取消對它們的引用,則仍然可能達到記憶體極限。


2 Java 記憶體回收機制
     Java 的記憶體管理就是物件的分配和釋放問題。分配記憶體的方式多種多樣,取決於該種語言的語法結構。但不論是哪一種語言的記憶體分配方式,最後都要返回所分配的記憶體塊的起始地址,即返回一個指標到記憶體塊的首地址。在Java 中所有物件都是在堆(Heap)中分配的,物件的建立通常都是採用new或者是反射的方式,但物件釋放卻有直接的手段,所以物件的回收都是由Java虛擬機器通過垃圾收集器去完成的。這種收支兩條線的方法確實簡化了程式設計師的工作,但同時也加重了JVM的工作,這也是Java 程式執行速度較慢的原因之一。因為,GC 為了能夠正確釋放物件,GC 必須監控每一個物件的執行狀態,包括物件的申請、引用、被引用、賦值等,GC 都需要進行監控。監視物件狀態是為了更加準確地、及時地釋放物件,而釋放物件的根本原則就是該物件不再
被引用。Java 使用有向圖的方式進行記憶體管理,可以消除引用迴圈的問題,例如有三個物件,相互引用,只要它們和根程序不可達,那麼GC 也是可以回收它們的。在Java 語言中,判斷一塊記憶體空間是否符合垃圾收集器收集標準的標準只有兩個:一個是給物件賦予了空值null,以下再沒有呼叫過,另一個是給物件賦予了新值,即重新分配了記憶體空間。

3 Java 中的記憶體洩漏

3.1 Java 中記憶體洩漏與C++的區別
    在Java 中,記憶體洩漏就是存在一些被分配的物件,這些物件有下面兩個特點,首先,這些物件是可達的,即在有向圖中,存在通路可以與其相連;其次,這些物件是無用的,即程式以後不會再使用這些物件。如果物件滿足這兩個條件,這些物件就可以判定為Java 中的記憶體洩漏,這些物件不會被GC 所回收,然而它卻佔用記憶體。在C++中,記憶體洩漏的範圍更大一些。有些物件被分配了記憶體空間,然後卻不可達,由於C++中沒有GC,這些記憶體將永遠收
不回來。在Java 中,這些不可達的物件都由GC 負責回收,因此程式設計師不需要考慮這部分的記憶體洩漏。通過分析,可以得知,對於C++,程式設計師需要自己管理邊和頂點,而對於Java 程式設計師只需要管理邊就可以了(不需要管理頂點
的釋放)。通過這種方式,Java 提高了程式設計的效率。

3.2 記憶體洩漏示例
3.2.1 示例1
   在這個例子中,迴圈申請Object 物件,並將所申請的物件放入一個Vector 中,如果僅僅釋放引用本身,那麼Vector 仍然引用該物件,所以這個物件對GC 來說是不可回收的。因此,如果物件加入到Vector 後,還必須從Vector 中刪除,最簡單的方法就是將Vector物件設定為null。
Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{Object o = new Object();
v.add(o);
o = null;
}//

此時,所有的Object 物件都沒有被釋放,因為變數v 引用這些物件。實際上無用,而還被引用的物件,GC 就無能為力了(事實上GC 認為它還有用),這一點是導致記憶體洩漏最重要的原因。

(1)如果要釋放物件,就必須使其的引用記數為0,只有那些不再被引用的物件才能被釋放,這個原理很簡單,但是很重要,是導致記憶體洩漏的基本原因,也是解決記憶體洩漏方法的宗旨;
(2)程式設計師無須管理物件空間具體的分配和釋放過程,但必須要關注被釋放物件的引用記數是否為0;
(3)一個物件可能被其他物件引用的過程的幾種:
a.直接賦值,如上例中的A.a = E;
b.通過引數傳遞,例如public void addObject(Object E);
c.其它一些情況如系統呼叫等。


3.3 容易引起記憶體洩漏的幾大原因
3.3.1 靜態集合類
      像HashMap、Vector 等靜態集合類的使用最容易引起記憶體洩漏,因為這些靜態變數的生命週期與應用程式一致,如示例1,如果該Vector 是靜態的,那麼它將一直存在,而其中所有的Object物件也不能被釋放,因為它們也將一直被該Vector 引用著。
3.3.2 監聽器
     在java 程式設計中,我們都需要和監聽器打交道,通常一個應用當中會用到很多監聽器,我們會呼叫一個控制元件的諸如addXXXListener()等方法來增加監聽器,但往往在釋放物件的時候卻沒有記住去刪除這些監聽器,從而增加了記憶體洩漏的機會。
3.3.3 物理連線
         一些物理連線,比如資料庫連線和網路連線,除非其顯式的關閉了連線,否則是不會自動被GC 回收的。Java 資料庫連線一般用DataSource.getConnection()來建立,當不再使用時必須用Close()方法來釋放,因為這些連線是獨立於JVM的。對於Resultset 和Statement 物件可以不進行顯式回收,但Connection 一定要顯式回收,因為Connection 在任何時候都無法自動回收,而Connection一旦回收,Resultset 和Statement 物件就會立即為NULL。但是如果使用連線池,情況就不一樣了,除了要顯式地關閉連線,還必須顯式地關閉Resultset Statement 物件(關閉其中一個,另外一個也會關閉),否則就會造成大量的Statement 物件無法釋放,從而引起記憶體洩漏。


3.3.4 內部類和外部模組等的引用
        內部類的引用是比較容易遺忘的一種,而且一旦沒釋放可能導致一系列的後繼類物件沒有釋放。對於程式設計師而言,自己的程式很清楚,如果發現記憶體洩漏,自己對這些物件的引用可以很快定位並解決,但是現在的應用軟體
並非一個人實現,模組化的思想在現代軟體中非常明顯,所以程式設計師要小心外部模組不經意的引用,例如程式設計師A 負責A 模組,呼叫了B 模組的一個方法如:
public void registerMsg(Object b);
這種呼叫就要非常小心了,傳入了一個物件,很可能模組B就保持了對該物件的引用,這時候就需要注意模組B 是否提供相應的操作去除引用。


4 預防和檢測記憶體漏洞
    在瞭解了引起記憶體洩漏的一些原因後,應該儘可能地避免和發現記憶體洩漏。
(1)好的編碼習慣。最基本的建議就是儘早釋放無用物件的引用,大多數程式設計師在使用臨時變數的時候,都是讓引用變數在退出活動域後,自動設定為null。在使用這種方式時候,必須特別注意一些複雜的物件圖,例如陣列、列、樹、圖等,這些物件之間有相互引用關係較為複雜。對於這類物件,GC 回收它們一般效率較低。如果程式允許,儘早將不用的引用物件賦為null。另外建議幾點:
在確認一個物件無用後,將其所有引用顯式的置為null;
當類從Jpanel 或Jdialog 或其它容器類繼承的時候,刪除該物件之前不妨呼叫它的removeall()方法;在設一個引用變數為null 值之前,應注意該引用變數指向的物件是否被監聽,若有,要首先除去監聽器,然後才可以賦空值;當物件是一個Thread 的時候,刪除該物件之前不妨呼叫它的interrupt()方法;記憶體檢測過程中不僅要關注自己編寫的類物件,同時也要關注一些基本型別的物件,例如:int[]、String、char[]等等;如果有資料庫連線,使用try...finally 結構,在finally 中關閉Statement 物件和連線。
(2)好的測試工具。在開發中不能完全避免記憶體洩漏,關鍵要在發現有記憶體洩漏的時候能用好的測試工具迅速定位問題的所在。市場上已有幾種專業檢查Java 記憶體洩漏的工具,它們的基本工作原理大同小異,都是通過監測Java 程式執行時,所有物件的申請、釋放等動作,將記憶體管理的所有資訊進行統計、分析、視覺化。開發人員將根據這些資訊判斷程式是否有記憶體洩漏問題。這些工具包括Optimizeit Profiler、JProbe Profiler、JinSight、Rational 公司的Purify 等。 

記:
    映像(Reflector)是一個程式分析自己的能力。java.lang.reflect包提供了獲取關於欄位、建構函式、方法和類的修改器的資訊的能力。利用這些資訊可以建立和Java Beans元件打交道的工具。可以動態建立元件的特徵。
    堆(heap) :棧(stack)與堆(heap)都是Java用來在Ram中存放資料的地方。與C++不同,Java自動管理棧和堆,程式設計師不能直接地設定棧或堆。棧的優勢是,存取速度比堆要快,僅次於直接位於CPU中的暫存器。但缺點是,存在棧中的資料大小與生存期必須是確定的,缺乏靈活性。另外,棧資料可以共享,堆的優勢是可以動態地分配記憶體大小,生存期也不必事先告訴編譯器,Java的垃圾收集器會自動收走這些不再使用的資料。但缺點是,由於要在執行時動態分配記憶體,存取速度較慢。
    連線池:在實際應用開發中,特別是在WEB應用系統中,如果JSP、Servlet或EJB使用JDBC直接訪問資料庫中的資料,每一次資料訪問請求都必須經歷建立資料庫連線、開啟資料庫、存取資料和關閉資料庫連線等步驟,而連線並開啟資料庫是一件既消耗資源又費時的工作,如果頻繁發生這種資料庫操作,系統的效能必然會急劇下降,甚至會導致系統崩潰。資料庫連線池技術是解決這個問題最常用的方法,在許多應用程式伺服器(例如:Weblogic,WebSphere,JBoss)中,基本都提供了這項技術,無需自己程式設計,但是,深入瞭解這項技術是非常必要的。
  資料庫連線池技術的思想非常簡單,將資料庫連線作為物件儲存在一個Vector物件中,一旦資料庫連線建立後,不同的資料庫訪問請求就可以共享這些連線,這樣,通過複用這些已經建立的資料庫連線,可以克服上述缺點,極大地節省系統資源和時間。
  資料庫連線池的主要操作如下:
  (1)建立資料庫連線池物件(伺服器啟動)。
  (2)按照事先指定的引數建立初始數量的資料庫連線(即:空閒連線數)。
  (3)對於一個數據庫訪問請求,直接從連線池中得到一個連線。如果資料庫連線池物件中沒有空閒的連線,且連線數沒有達到最大(即:最大活躍連線數),建立一個新的資料庫連線。
  (4)存取資料庫。
  (5)關閉資料庫,釋放所有資料庫連線(此時的關閉資料庫連線,並非真正關閉,而是將其放入空閒佇列中。如實際空閒連線數大於初始空閒連線數則釋放連線)。
  (6)釋放資料庫連線池物件(伺服器停止、維護期間,釋放資料庫連線池物件,並釋放所有連線)。

相關推薦

有關Java記憶體洩露文章(受益哦)

引言      Java的一個重要優點就是通過垃圾收集器GC (Garbage Collection)自動管理記憶體的回收,程式設計師不需要通過呼叫函式來釋放記憶體。因此,很多程式設計師認為Java 不存在記憶體洩漏問題,或者認為即使有記憶體洩漏也不是程式的責任,而是GC 或JVM的問題。其實,這種想法是

有關JAVA記憶體洩露文章

引言      Java的一個重要優點就是通過垃圾收集器GC (Garbage Collection)自動管理記憶體的回收,程式設計師不需要通過呼叫函式來釋放記憶體。因此,很多程式設計師認為Java 不存在記憶體洩漏問題,或者認為即使有記憶體洩漏也不是程式的責任,而是GC 或JVM的問題。其實,這種想法是不正

有關JAVA記憶體洩露文章

1 引言     Java的一個重要優點就是通過垃圾收集器GC (Garbage Collection)自動管理記憶體的回收,程式設計師不需要通過呼叫函式來釋放記憶體。因此,很多程式設計師認為Java 不存在記憶體洩漏問題,或者認為即使有記憶體洩漏也不是程式的責任,而是GC 或JVM的問題。其實,這種想法是不

MYSQL文章《數據庫表設計,沒有最好只有最適合》

其他 eqv 新的 fmt 記錄 ces 末尾 base64 過程 http://mp.weixin.qq.com/s/a8klpzM5iam0_JYSw7-U4g 我們在設計數據庫的時候,是否會突破常規,找到最適合自己需求的設計方案,下面來舉個例子: 常用的鄰接表設計

ElasticSearch原始碼解析():介紹中文分詞的文章

轉自:http://www.cnblogs.com/flish/archive/2011/08/08/2131031.html  基於CRF(Conditional Random Field)分詞演算法 論文連結:http://nlp.stanford.edu/pubs/

關於加密密碼安全性的文章

引子 最近有個虛擬練習專案,涉及到系統安全保障的設計,於是對安全保障這塊做了一些更深入的瞭解。發現了很多有趣的東西,開闊了眼界。中間查了一些資料,於是我打算重新整理,用更加循序漸進,大家都能懂的方式,說一說如何設計一個安全的系統。 著名的安全事件 首先來看看最近幾年

比較詳細介紹FatFs檔案系統移植的文章 FatFs檔案系統的移植

  因為需要,又不想自己寫,所以就移植了一個檔案系統。     說下我的硬體和開發工具:接成 TRUE IDE 模式下的CF卡(也就是相當於一塊硬碟了),三星S3C2440的ARM9,開發工具是很老很老的D版的ADS1.2。    

比較詳細介紹FatFs檔案系統移植的文章

摘自:http://blog.163.com/[email protected]/blog/static/3278568820090710053782/ 補充一點,FatFs的作者寫了兩個,一個是正宗的FatFs,比較適合大的RAM的裝置,另一個是FatFs/

Java併發(四):volatile的實現原理 Java併發():Java記憶體模型乾貨總結

synchronized是一個重量級的鎖,volatile通常被比喻成輕量級的synchronized volatile是一個變數修飾符,只能用來修飾變數。 volatile寫:當寫一個volatile變數時,JMM會把該執行緒對應的本地記憶體中的共享變數重新整理到主記憶體。 volatile讀:當讀一

Java記憶體洩露與定位

1、為什麼會發生記憶體洩漏 Java如何檢測內在洩漏呢?我們需要一些工具進行檢測,並發現記憶體洩漏問題,不然很容易發生down機問題。 編寫java程式最為方便的地方就是我們不需要管理記憶體的分配和釋放,一切由jvm來進行處理,當java物件不再被應用時,等到堆記憶體不夠用時,jvm會進行垃

排查Java 記憶體洩露-藉助排查工具

轉自:https://juejin.im/entry/57fb07255bbb50005b2f20ac java記憶體洩露典型特徵 現象一: 堆/Perm 區不斷增長, 沒有下降趨勢(回收速度趕不上增長速度), 最後不斷觸發FullGC, 甚至crash(如下**兩張圖是同一

JAVA 記憶體洩露詳解-值得收藏的好文

非常好的文章, 轉載自:http://blog.csdn.net/anxpp/article/details/51325838     Java的一個重要特性就是通過垃圾收集器(GC)自動管理記憶體的回收,而不需要程式設計師自己來釋放記憶體。理論上Java

關於如何在Unity裡使用Protobuf

https://www.cnblogs.com/designyourdream/p/4268092.html 原帖地址: http://purdyjotut.blogspot.com/2013/10/using-protobuf-in-unity3d.html 先轉過來,等時間合適了,再

讀書筆記 ---- 《深入理解Java虛擬機器》---- 第11Java記憶體模型與執行緒

上一篇:晚期(執行期)優化:https://blog.csdn.net/pcwl1206/article/details/84642835 目  錄: 1  概述 2  Java記憶體模型 2.1  主記憶體與工作記憶體 2.2 

【搞定Java併發程式設計】第7Java記憶體模型詳解

上一篇:ThreadLocal詳解:https://blog.csdn.net/pcwl1206/article/details/84859661 其實在Java虛擬機器的學習中,我們或多或少都已經接觸過了有關Java記憶體模型的相關概念(點選檢視),只不過在Java虛擬機器中講的不夠詳細,因此

【華為】弟弟發過來關於華為的文章

題目《華為離職員工:都說主管有份短名單 就像閻王的生死簿》 這篇文章講的是一個在華為工作了12年、年紀在38歲左右的人(2005年碩士畢業 26歲),在離職後回顧自己的經歷時的反思。 38歲啊!這麼快! 我認為,演說者表達了兩方面意思: (1)他後悔在工作期間只挑好走

整理了 linux 環境配置的文章

安裝之前先檢查一下系統有沒有自帶open-jdk 命令: rpm -qa |grep java rpm -qa |grep jdk rpm -qa |grep gcj 如果沒有輸入資訊表示沒有安裝。 如果安裝可以使用rpm -qa | grep java | x

JVM基礎系列教程|第二Java記憶體模型

推薦視訊連結 所有的Java開發人員可能會遇到這樣的困惑?我該為堆記憶體設定多大空間呢?OutOfMemoryError的異常到底涉及到執行時資料的哪塊區域?該怎麼解決呢?其實如果你經常解決伺服器效能問題,那麼這些問題就會變的非常常見,瞭解JVM記憶體也是為了

java 記憶體洩露

一、java相對於C++來說很難記憶體洩露,因為有自己的垃圾回收機制。如果想知道java出現記憶體洩露,最好先了解java是如何管理記憶體的。Java的記憶體管理就是物件的分配和釋放問題。在Java中,程式設計師需要通過關鍵字new為每個物件申請記憶體空間 (基本型別除外),

關於java爬蟲實現的技術分享

最近由於工作的需要,獨自開始研究爬蟲爬取網際網路資料;經過兩週左右的探究,踩過許多坑,也學習到了許多以往不知道的知識。一直都在做伸手黨,很是慚愧_(:_」∠)_感覺都要臉紅了☺,在這裡總結一下經驗,順便分享給大家,希望可以幫助到有需要的朋友。爬蟲技術不是很成熟,如果能有大佬能