Ⅰ java是否有內存泄露和內存溢出
java中的內存溢出和內存泄漏
內存溢出:
對於整個應用程序來說,JVM內存空間,已經沒有多餘的空間分配給新的對象。所以就發生內存溢出。
內存泄露:
在應用的整個生命周期內,某個對象一直存在,且對象佔用的內存空間越來越大,最終導致JVM內存泄露,
比如:緩存的應用,如果不設置上限的話,緩存的容量可能會一直增長。
靜態集合引用,如果該集合存放了無數個對象,隨著時間的推移也有可能使容量無限制的增長,最終導致JVM內存泄露。
內存泄露,是應用程序中的某個對象長時間的存活,並且佔用空間不斷增長,最終導致內存泄露。
是對象分配後,長時間的容量增長。
內存溢出,是針對整個應用程序的所有對象的分配空間不足,會造成內存溢出。
內存泄漏
內存泄漏指由於疏忽或錯誤造成程序未能釋放已經不再使用的內存的情況。內存泄漏並非指內存在物理上的消失,而是應用程序分配某段內存後,由於設
計錯誤,失去了對該段內存的控制,因而造成了內存的浪費。內存泄漏與許多其他問題有著相似的症狀,並且通常情況下只能由那些可以獲得程序源代碼的程序員才
可以分析出來。然而,有不少人習慣於把任何不需要的內存使用的增加描述為內存泄漏,即使嚴格意義上來說這是不準確的。
一般我們常說的內存泄漏
是指堆內存的泄漏。堆內存是指程序從堆中分配的,大小任意的(內存塊的大小可以在程序運行期決定),使用完後必須顯示釋放的內存。應用程序一般使用
malloc,realloc,new等函數從堆中分配到一塊內存,使用完後,程序必須負責相應的調用free或delete釋放該內存塊,否則,這塊內
存就不能被再次使用,我們就說這塊內存泄漏了。
內存泄漏可以分為4類:
1.
常發性內存泄漏。發生內存泄漏的代碼會被多次執行到,每次被執行的時候都會導致一塊內存泄漏。
2.
偶發性內存泄漏。發生內存泄漏的代碼只有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對於特定的環境,偶發性的也許就變成了常發性的。所以測試環境和測試方法對檢測內存泄漏至關重要。
3.
一次性內存泄漏。發生內存泄漏的代碼只會被執行一次,或者由於演算法上的缺陷,導致總會有一塊僅且一塊內存發生泄漏。比如,在類的構造函數中分配內存,在析構函數中卻沒有釋放該內存,所以內存泄漏只會發生一次。
4.
隱式內存泄漏。程序在運行過程中不停的分配內存,但是直到結束的時候才釋放內存。嚴格的說這里並沒有發生內存泄漏,因為最終程序釋放了所有申請的內存。但
是對於一個伺服器程序,需要運行幾天,幾周甚至幾個月,不及時釋放內存也可能導致最終耗盡系統的所有內存。所以,我們稱這類內存泄漏為隱式內存泄漏。
簡單點:
內存泄漏就是忘記釋放使用完畢的內存,讓下次使用有一定風險。
內存溢出就是一定的內存空間不能裝下所有的需要存放的數據,造成內存數據溢出。
主要從以下幾部分來說明,關於內存和內存泄露、溢出的概念,區分內存泄露和內存溢出;內存的區域劃分,了解GC回收機制;重點關注如何去監控和發現內存問題;此外分析出問題還要如何解決內存問題。
下面就開始本篇的內容:
第一部分 概念
眾所周知,java中的內存由java虛擬機自己去管理的,他不像C++需要自己去釋放。籠統地
去講,java的內存分配分為兩個部分,一個是數據堆,一個是棧。程序在運行的時候一般分配數據堆,把局部的臨時的變數都放進去,生命周期和進程有關系。
但是如果程序員聲明了static的變數,就直接在棧中運行的,進程銷毀了,不一定會銷毀static變數。
另外為了保證java內存不會溢出,java中有垃圾回收機制。
System.gc()即垃圾收集機制是指jvm用於釋放那些不再使用的對象所佔用的內存。java語言並不要求jvm有gc,也沒有規定gc如何工作。垃圾收集的目的在於清除不再使用的對象。gc通過確定對象是否被活動對象引用來確定是否收集該對象。
而其中,內存溢出就是你要求分配的java虛擬機內存超出了系統能給你的,系統不能滿足需求,於是產生溢出。
內存泄漏是指你向系統申請分配內存進行使用(new),可是使用完了以後卻不歸還(delete),結果你申請到的那塊內存你自己也不能再訪
問,該塊已分配出來的內存也無法再使用,隨著伺服器內存的不斷消耗,而無法使用的內存越來越多,系統也不能再次將它分配給需要的程序,產生泄露。一直下
去,程序也逐漸無內存使用,就會溢出。
第二部分 原理
JAVA垃圾回收及對內存區劃分
在Java虛擬機規范中,提及了如下幾種類型的內存空間:
◇ 棧內存(Stack):每個線程私有的。
◇ 堆內存(Heap):所有線程公用的。
◇ 方法區(Method Area):有點像以前常說的「進程代碼段」,這裡面存放了每個載入類的反射信息、類函數的代碼、編譯時常量等信息。
◇ 原生方法棧(Native Method Stack):主要用於JNI中的原生代碼,平時很少涉及。
而Java的使用的是堆內存,java堆是一個運行時數據區,類的實例(對象)從中分配空間。Java虛擬機(JVM)的堆中儲存著正在運行的應用程序所建立的所有對象,「垃圾回收」也是主要是和堆內存(Heap)有關。
垃圾回收的概念就是JAVA虛擬機(JVM)回收那些不再被引用的對象內存的過程。一般我們認為正在被引用的對象狀態為「alive」,而沒有
被應用或者取不到引用屬性的對象狀態為「dead」。垃圾回收是一個釋放處於」dead」狀態的對象的內存的過程。而垃圾回收的規則和演算法被動態的作用於
應用運行當中,自動回收。
JVM的垃圾回收器採用的是一種分代(generational )回收策略,用較高的頻率對年輕的對象(young
generation)進行掃描和回收,這種叫做minor collection,而對老對象(old generation)的檢查回收頻率要低很多,稱為major
collection。這樣就不需要每次GC都將內存中所有對象都檢查一遍,這種策略有利於實時觀察和回收。
(Sun JVM 1.3
有兩種最基本的內存收集方式:一種稱為ing或scavenge,將所有仍然生存的對象搬到另外一塊內存後,整塊內存就可回收。這種方法有效率,但需要有一定的空閑內存,拷貝也有開銷。這種方法用於minor
collection。另外一種稱為mark-compact,將活著的對象標記出來,然後搬遷到一起連成大塊的內存,其他內存就可以回收了。這種方法不需要佔用額外的空間,但速度相對慢一些。這種方法用於major collection.
)
一些對象被創建出來只是擁有短暫的生命周期,比如 iterators 和本地變數。另外一些對象被創建是擁有很長的生命周期,比如持久化對象等。
垃圾回收器的分代策略是把內存區劃分為幾個代,然後為每個代分配一到多個內存區塊。當其中一個代用完了分配給他的內存後,JVM會在分配的內存區內執行一個局部的GC(也可以叫minor
collection)操作,為了回收處於「dead」狀態的對象所佔用的內存。局部GC通常要比Full GC快很多。
JVM定義了兩個代,年輕代(yong generation)(有時稱為「nursery」托兒所)和老年代(old generation)。年輕代包括
「Eden space(伊甸園)」和兩個「survivor spaces」。虛擬內存初始化的時候會把所有對象都分配到 Eden
space,並且大部分對象也會在該區域被釋放。 當進行 minor GC的時候,VM會把剩下的沒有釋放的對象從Eden space移動到其中一個survivor
spaces當中。此外,VM也會把那些長期存活在survivor spaces 里的對象移動到 老生代的「tenured」 space中。當 tenured
generation 被填滿後,就會產生Full GC,Full GC會相對比較慢因為回收的內容包括了所有的 live狀態的對象。pemanet
generation這個代包括了所有java虛擬機自身使用的相對比較穩定的數據對象,比如類和對象方法等。
關於代的劃分,可以從下圖中獲得一個概況:
第三部分 總結
內存溢出主要是由於代碼編寫時對某些方法、類應用不合理,或者沒有預估到臨時對象會佔用很大內存量,或者把過多的數據放入JVM緩存,或者性能
壓力大導致消息堆積而佔用內存,以至於在性能測試時,生成龐大數量的臨時對象,GC時沒有做出有效回收甚至根本就不能回收,造成內存空間不足,內存溢出。
如果編碼之前,對內存使用量進行預估,對放在內存中的數據進行評估,保證有用的信息盡快釋放,無用的信息能夠被GC回收,這樣在一定程度上是可以避免內存溢出問題的。