本文目錄一覽:
- 1、java是否有內存泄露和內存溢出
- 2、java什麼是內存泄漏?
- 3、怎樣解決Java中內存泄露
- 4、如何識別Java中的內存泄漏
- 5、Java內存泄露和內存泄露的區別
- 6、java在什麼情況下會出現內存泄露
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
有兩種最基本的內存收集方式:一種稱為copying或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回收,這樣在一定程度上是可以避免內存溢出問題的。
java什麼是內存泄漏?
內存泄露是指無用對象(不再使用的對象)持續佔有內存或無用對象的內存得不到及時釋放,
從而造成的內存空間的浪費
長生命周期的對象持有短生命周期對象的引用就很可能發生內存泄露,
儘管短生命周期對象已經不再需要,
但是因為長生命周期對象持有它的引用而導致不能被回收,
這就是java中內存泄露的發生場景
內存溢出,內存溢出是超出可使用的最大範圍了。比如系統給你分配1G內存,你用完了,還想用,系統不給你分,就溢出了
怎樣解決Java中內存泄露
一旦知道確實發生了內存泄漏,就需要更專業的工具來查明為什麼會發生泄漏。JVM自己是不會告訴您的。這些專業工具從JVM獲得內存系統信息的方法基本上有兩種:JVMTI和位元組碼技術(byte code instrumentation)。Java虛擬機工具介面(Java Virtual Machine Tools Interface,JVMTI)及其前身Java虛擬機監視程序介面(Java Virtual Machine Profiling Interface,JVMPI)是外部工具與JVM通信並從JVM收集信息的標準化介面。位元組碼技術是指使用探測器處理位元組碼以獲得工具所需的信息的技術。
Optimizeit是Borland公司的產品,主要用於協助對軟體系統進行代碼優化和故障診斷,其中的Optimizeit Profiler主要用於內存泄漏的分析。Profiler的堆視圖就是用來觀察系統運行使用的內存大小和各個類的實例分配的個數的。
首先,Profiler會進行趨勢分析,找出是哪個類的對象在泄漏。系統運行長時間後可以得到四個內存快照。對這四個內存快照進行綜合分析,如果每一次快照的內存使用都比上一次有增長,可以認定系統存在內存泄漏,找出在四個快照中實例個數都保持增長的類,這些類可以初步被認定為存在泄漏。通過數據收集和初步分析,可以得出初步結論:系統是否存在內存泄漏和哪些對象存在泄漏(被泄漏)。
接下來,看看有哪些其他的類與泄漏的類的對象相關聯。前面已經談到Java中的內存泄漏就是無用的對象保持,簡單地說就是因為編碼的錯誤導致了一條本來不應該存在的引用鏈的存在(從而導致了被引用的對象無法釋放),因此內存泄漏分析的任務就是找出這條多餘的引用鏈,並找到其形成的原因。查看對象分配到哪裡是很有用的。同時只知道它們如何與其他對象相關聯(即哪些對象引用了它們)是不夠的,關於它們在何處創建的信息也很有用。
最後,進一步研究單個對象,看看它們是如何互相關聯的。藉助於Profiler工具,應用程序中的代碼可以在分配時進行動態添加,以創建堆棧跟蹤。也有可以對系統中所有對象分配進行動態的堆棧跟蹤。這些堆棧跟蹤可以在工具中進行累積和分析。對每個被泄漏的實例對象,必然存在一條從某個牽引對象出發到達該對象的引用鏈。處於堆棧空間的牽引對象在被從棧中彈出後就失去其牽引的能力,變為非牽引對象。因此,在長時間的運行後,被泄露的對象基本上都是被作為類的靜態變數的牽引對象牽引。
總而言之, Java雖然有自動回收管理內存的功能,但內存泄漏也是不容忽視,它往往是破壞系統穩定性的重要因素。
如何識別Java中的內存泄漏
一般來說內存泄漏有兩種情況。一種情況,在堆中的分配的內存,在沒有將其釋放掉的時候,就將所有能訪問這塊內存的方式都刪掉(如指針重新賦值);另一種情況則是在內存對象明明已經不需要的時候,還仍然保留著這塊內存和它的訪問方式(引用)。第一種情況,在Java中已經由於垃圾回收機制的引入,得到了很好的解決。所以,Java中的內存泄漏,主要指的是第二種情況。
可能光說概念太抽象了,大家可以看一下這樣的例子:
1
Vector
v=new
Vector(10);
2
for
(int
i=1;i100;
i++){
3
Object
o=new
Object();
4
v.add(o);
5
o=null;
6
}
在這個例子中,代碼棧中存在Vector對象的引用v和Object對象的引用o。在For循環中,我們不斷的生成新的對象,然後將其添加到Vector對象中,之後將o引用置空。問題是當o引用被置空後,如果發生GC,我們創建的Object對象是否能夠被GC回收呢?答案是否定的。因為,GC在跟蹤代碼棧中的引用時,會發現v引用,而繼續往下跟蹤,就會發現v引用指向的內存空間中又存在指向Object對象的引用。也就是說儘管o引用已經被置空,但是Object對象仍然存在其他的引用,是可以被訪問到的,所以GC無法將其釋放掉。如果在此循環之後,Object對象對程序已經沒有任何作用,那麼我們就認為此Java程序發生了內存泄漏。
儘管對於C/C++中的內存泄露情況來說,Java內存泄露導致的破壞性小,除了少數情況會出現程序崩潰的情況外,大多數情況下程序仍然能正常運行。但是,在移動設備對於內存和CPU都有較嚴格的限制的情況下,Java的內存溢出會導致程序效率低下、佔用大量不需要的內存等問題。這將導致整個機器性能變差,嚴重的也會引起拋出OutOfMemoryError,導致程序崩潰。
Java內存泄露和內存泄露的區別
答:Java內存泄露與溢出的區別:
內存溢出就是你要求分配的內存超出了系統能給你的,系統不能滿足需求,於是產生溢出。ava內存泄漏就是沒有及時清理內存垃圾,導致系統無法再給你提供內存資源(內存資源耗盡)。看到上面的解釋,可能有些朋友還是不太理解吧。沒問題,看以下例子
Java內存泄露是說程序邏輯問題,造成申請的內存無法釋放.這樣的話無論多少內存,早晚都會被佔用光的.最簡單的例子就是死循環了.由於程序判斷錯誤導經常發生此事
Java內存泄漏是指在堆上分配的內存沒有被釋放,從而失去對其控制。這樣會造成程序能使用的內存越來越少,導致系統運行速度減慢,嚴重情況會使程序當掉。
關於內存溢出有點出入。比如說你申請了一個integer,但給它存了long才能存下的數,那就是內存溢出。
舉個現實中的例子:比如有一個桶,裝滿了水.你丟個蘋果進去。桶的水正常。如果你放個大石頭。水就出溢出,內存溢出也就是這個原理。
區別:內存溢出,提供的內存不夠;Java內存泄漏,無法再提供內存資源
java在什麼情況下會出現內存泄露
內存泄露就是指一個不再被程序使用的對象或變數一直被佔據在內存中。java中的內存泄露的情況:
1.長生命周期的對象持有短生命周期對象的引用就很可能發生內存泄露,例如,緩存系統,我們載入了一個對象放在緩存中(例如放在一個全局map對象中),然後一直不再使用它,這個對象一直被緩存引用,但卻不再被使用。
2.集合類,而如果集合類是全局性的變數(比如類中的靜態屬性,全局性的map等即有靜態引用或final一直指向它),那麼沒有相應的刪除機制,很可能導致集合所佔用的內存只增不減,因此提供這樣的刪除機制或者定期清除策略非常必要。
3.單例模式。不正確使用單例模式是引起內存泄露的一個常見問題,單例對象在被初始化後將在JVM的整個生命周期中存在(以靜態變數的方式),如果單例對象持有外部對象的引用,那麼這個外部對象將不能被jvm正常回收,導致內存泄露
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/300663.html