1.引言
在Java中,它的內存管理包括兩方面:內存分配(創(chuàng)建Java對象的時候)和內存回收侥涵,這兩方面工作都是由JVM自動完成的沼撕,降低了Java程序員的學習難度,避免了像C/C++直接操作內存的危險芜飘。但是务豺,也正因為內存管理完全由JVM負責,所以也使Java很多程序員不再關心內存分配嗦明,導致很多程序低效笼沥,耗內存。因此就有了Java程序員到最后應該去了解JVM,才能寫出更高效奔浅,充分利用有限的內存的程序馆纳。
2. 正題
內存泄露導致的原因:對象已經沒有被應用程序使用,但是垃圾回收器沒辦法移除它們乘凸,因為還在被引用著厕诡。 還有一種說法是:沒有直接或間接被Gcroot集合引用 的對象都被稱作垃圾內存。其中能作為Gcroot對象的有:
(1). 虛擬機棧(棧幀中的局部變量區(qū)营勤,也叫做局部變量表)中引用的對象灵嫌。
(2). 方法區(qū)中的類靜態(tài)屬性引用的對象。
(3). 方法區(qū)中常量引用的對象葛作。
(4). 本地方法棧中JNI(Native方法)引用的對象寿羞。
2.1 算法分析-引用計數法
堆中每個對象都有一塊內存用來保存該對象被引用的次數。當任何其他變量賦值為這個對象的引用時赂蠢,計數加1 绪穆,(a=b ,則b引用的對象實例計數器+1)但當一個對象實例的某個引用超過了生命周期或者被設置為一個新值時虱岂,對象實例的引用計數器減1玖院,任何引用計數器為0 的對象實例可以當做垃圾收集。
優(yōu)點:
1)實時性 無需等到內存不夠的時候第岖,才開始回收难菌,運行時根據對象的計數器是否為0,就可以直接回收蔑滓。
2)在垃圾回收過程中郊酒,應用無需掛起。如果申請內存時键袱,內存不足燎窘,則立刻報outofmember 錯誤.
3)更新對象的計數器時,只是影響到該對象蹄咖,不會掃描全部對象
缺點:
1)每次對象被引用時褐健,都需要去更新計數器,有一點時間開銷澜汤。另外無法解決循環(huán)引用問題蚜迅。例如:
要想清除:必須先調用a.b=null;b.a=null;
2.2標記-清除(Mark-Sweep)算法
首先標記出所有等待被回收的對象。然后統(tǒng)一清除银亲。
缺點:產生的內存碎片太多慢叨,效率低下。
2.3 復制(Copying)算法
1)新生對象被分配到A塊中未使用的內存當中务蝠。當A塊的內存用完了拍谐, 把A塊的存活對象復制到B塊。
2)清理A塊所有對象。
4)新生對象被分配到B塊中未使用的內存當中轩拨。當B塊的內存用完了践瓷, 把B塊的存活對象復制到A塊。
4)清理B塊所有對象亡蓉。
循環(huán)1晕翠。
缺點:內存縮小為了原來的一半,這樣代價太高了
2.4 優(yōu)化的復制算法
1)Eden+S0可分配新生對象砍濒;
2)對Eden+S0進行垃圾收集淋肾,存活對象復制到S1。清理Eden+S0爸邢。一次新生代GC結束樊卓。
3)Eden+S1可分配新生對象;
4)對Eden+S1進行垃圾收集杠河,存活對象復制到S0碌尔。清理Eden+S1。二次新生代GC結束券敌。
循環(huán)1唾戚。
2.5 標記-整理(Mark-Compact)算法
復制算法在對象存活率較高的場景下要進行大量的復制操作,效率很低待诅。萬一對象100%存活叹坦,那么需要有額外的空間進行分配擔保。老年代都是不易被回收的對象咱士,對象存活率高立由,因此一般不能直接選用復制算法轧钓。根據老年代的特點序厉,有人提出了另外一種標記-整理算法,過程與標記-清除算法一樣毕箍,不過不是直接對可回收對象進行清理弛房,而是讓所有存活對象都向一端移動,然后直接清理掉邊界以外的內存而柑。標記-整理算法的工作過程如圖:
2.6 分代回收算法
分別是:年輕代文捶,年老代,和永久代媒咳。
年輕代:“標記-復制算法”粹排,eden:survivo:survivo=8:1:1
老年代:"標記-整理(Mark-Compact)算法",將大部分的依舊在引用的對象,整理涩澡,減少代碼碎片顽耳。
永久代:
用于存放靜態(tài)文件,如今Java類、方法等射富。持久代對垃圾回收沒有顯著影響膝迎,但是有些應用可能動態(tài)生成或
者調用一些class,例如Hibernate等胰耗,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新
增的類限次。持久代大小通過-XX:MaxPermSize=進行設置。