java與C++之間又一堵由內存動態分配和垃圾收集技術所圍成的“高牆”,牆外面的人想進去,牆裏面的人想出來。
--------<<深入理解java虛擬機>>
C/C++ 中主要由用戶程序代碼來回收分配的內存,不存在無用的對象的篩選過程,效率上比垃圾回收機制更勝一籌。
java語言的一些性能上的劣勢都是爲了換取開發效率。
回收靠誰來做? ----- > GC
什麼是GC?
Garbage Collection 垃圾收集(顧名思義,回收垃圾,不需要的對象)
GC要完成的事情:
1,哪些內存需要回收?
2,什麼時候回收?
3,如何回收?
GC關注的區域:
既然GC關注堆,我們就要先了解堆
新生代的比例 8:1:1
無用的對象回收,有用的對象存活,那麼如何判斷對象是否存活?
如上圖:
GC ROOTS 到object1 ,object2,object3,object4有通路,即對象可達,則對象存活
GC ROOTS 到object5 ,object6,object7沒有通路,即對象不可達,則判定爲可回收的對象
垃圾收集算法
1,標記-清除算法
出現的最早
什麼叫標記,怎麼標記 ? ----- > 通過GC ROOTS的可達性分析對需要回收的對象做標記
什麼叫清除? ----- > 統一清除(回收)所有被標記的對象
不足之處
效率問題 :兩次掃描耗時嚴重,標記和清除兩個過程本身的效率也都不高
空間問題:標記清除後會產生大量不連續的內存碎片
2,複製算法
爲了解決效率問題,複製算法橫空出世
實現簡單 運行高效 內存空間利用率低
3,標記-整理算法
分代收集算法
新生代:複製算法
老年代:標記-清除算法 標記-整理算法
把堆分爲新生代和老年代 ,這樣就可以根據各個年代的特點採用最適當的收集算法。
設計時,對象儘量不要進入老年代 ,而是使我們的對象儘量在新生代回收掉(最理想的狀態下,所有的對象都不要進入老年代)
新生代 minor GC
老年代 major GC 比新生代慢十倍
垃圾收集算法和垃圾收集器有什麼關係?
垃圾收集算法是理論,垃圾收集器是垃圾收集算法的實現。
垃圾收集器
Serial
ParNew
Serial Old
Parallel Old
CMS
G1
垃圾收集器之間的連線表示搭配使用
目前流行的是ParNew 和 CMS的搭配使用
內存分配與回收的策略
GC是回收對象,那對象的創建和內存的分配是怎樣一個過程?
虛擬機爲新生對象分配內存,對象所需內存的大小在類加載完成後便可以完全確定
分配內存時有兩種情況:
1)java堆中的內存是規整的
什麼叫規整?-----> 所有用過的內存放在一邊,空閒的內存放在另一邊,中間放着指針作爲分界點的指示器
上述分配方法稱 “指針碰撞”
指針可以看作是一個內存空間地址的偏移量
2)java堆中的內存不是規整的
已使用的內存和空閒的內存相互交錯,必須維護一個列表,記錄哪些內存塊可以用
在分配時從列表中找到一塊足夠大的空間劃分給對象,並更新列表上的記錄。
上述分配方式稱“空閒鏈表”(Free List)
指針碰撞 多線程併發競爭同一塊內存空間問題
如果兩個線程在同一塊空間申請空間 (併發情況並不是線程安全的)
如何解決上述問題?
法一:
jvm採用CAS 保證更新操作的原子性 實現同步
法二:
TLAB Thread Local Allocation Buffer 本地線程分配緩衝
棧上分配
避免同步 線程分配的空間不宜過大
每個線程都有自己的一小塊內存,哪個線程要分配內存,直接在自己的TLAB上分配
對象優先在Eden區分配
Eden區空間不夠,觸發Minor GC(針對新生代的GC)
大對象直接進入老年代
-XX:PretenureSizeThreshold=3145728 3M
上述jvm參數 表示有超過3MB的對象會直接在老年代分配
長期存活的對象直接進入老年代
對象如果從Eden區經過一次Minor GC到Survivor區,age=1
在Survivor區沒熬過一次Minor GC,age++
設置晉升老年代年齡閾值
-XX:MaxTenuringThreshold=15
age=15時,進入老年代
動態對象的年齡判定
相同年齡所有對象的大小總和 > Survivor空間的一半
年齡大於或等於該年齡的對象就可以直接進入老年代,無需考慮MaxTenuringThreshold設置的閾值
空間分配擔保
Minor GC 之前檢查 老年代最大可用連續空間是否>新生代所有對象總空間