目錄
1、什麼是垃圾
內存分配與回收方式:
-
C語言:malloc、free
-
C++:new、delete
-
Java:new 自動回收內存
自動回收內存系統不容易出錯,手動回收內存,容易出現以下的錯誤:
-
忘記回收
-
多次回收
垃圾的定義:沒有任何引用指向的一個對象或者多個對象(循環引用)。
當把成員變量設置爲空(null)之後,不再指向任何引用對象,那麼該對象就被稱作垃圾:
還有一種情況,多個對象之間互相引用,但是沒有其他的引用指向這個循環的對象。
2、如何定位垃圾
-
reference count
當引用計數變爲0的時候,這個對象就成爲垃圾了。但是引用計數不能解決對象循環引用
如下:
每個引用計數都是1,但是它們全部是垃圾,所以用引用計數的方式的話,這些垃圾就找不到了,會發生內存泄漏。
-
Root Searching
根可達或者根搜索算法。
通過程序找到一些根對象,通過根對象找到它所連接的那些對象不是垃圾,其他的都是垃圾。
Roots:線程棧變量、靜態變量、常量池、JNI指針
3、常見的垃圾回收算法
-
Mark-Sweep(標記清除)
將可回收的對象標記爲非垃圾。
缺點:位置不連續,產生內存碎片。
-
Copying(拷貝算法)
內存一分爲二,將存活對象複製到未使用的內存中,原內存全部標記爲可使用;新分配內存時先分配存活對象所在的那段內存,垃圾回收時,重複上述操作。
特點:沒有碎片,但是內浪費空間。最大的問題:內存浪費。
-
Mark-Compact(標記壓縮)
將存活對象依次複製到垃圾對象和未使用的區域中,結合了標記清除和拷貝的做法,但是效率比copy略低。
三種方法找垃圾的效率是一致的,區別在於找到垃圾後對其進行整理的方式。拷貝算法是內存拷貝,是線性地址的拷貝,速度很快的,效率很高。但是壓縮算法卻不這麼簡單,因爲任意一個內存進行移動時,如果是多線程, 都要進行線程同步;如果是單線程,那單線程的效率本來就低。 所以任何一塊內存挪動都要進行線程同步,所以效率肯定是很低的。
4、JVM內存分代模型(用於分代垃圾回收算法)
目前,生產環境中普遍使用的是JDK1.7或JDK1.8,根據JDK版本不同,分代也不同。
JVM中分代:新生代+老年代+永久代(JDK1.7)/元數據區(JDK1.8)Metaspace。
- 永久代和元數據區是裝載Class的,將硬盤上的Class對象load到內存的時候,裝載了永久代或者元數據區域,具體放在哪裏區別於使用的JDK版本
- 永久代必須指定大小限制,而元數據可以設置,也可不設置,無上限(受限於物理內存)
- 字符串常量在JDK1.7中,是放在永久代區域;而JDK1.8中,是放在堆裏
- MethodArea是一個邏輯概念,並不是指的一個區域,在JDK1.7中對應的就是永久代,JDK1.8中對應的是元數據
堆內存邏輯分區
- 新生代中分了兩類區域,eden和survivor,而survivor有兩塊。默認的比例,新生代:老年代=1:3,新生代中eden: survivor:survivor = 8:1:1。
之所以新生代中按照這個比例分配,是因爲eden區在GC的時候,90%的對象都會被回收,剩下的存活對象在survivor區是可以放下的。
當創建一個對象時,默認會去找eden區, 如果對象特別大,eden區裝不下則直接進入老年代。
新生代 = Eden + 2個survivor區(survivor0、survivor1):
- YGC(Young GC)回收後,大多數的對象會被回收,活着的對象進入survivor0
- 再次YGC,活着的對象eden+s0拷貝到s1,將eden和s0清空
- 再次YGC,活着的對象eden+s1拷貝到s0,將eden和s1清空
- 年齡足夠->老年代(年齡足夠:15,CMS 6)
- survivor區裝不下的時候,裝不下的部分直接進入老年代
老年代:
- 頑固份子
- 老年代區域滿了,就進行Full GC(簡稱FGC, FGC包括新生代和老年代同時GC)
GC Tuning:儘量減少FGC。
5、垃圾回收器
- Serial、ParNew、Parallel Scavenge是用於回收Young Generation
- CMS、Serial Old、Parallel Old是用於回收Old Generation
- G1、ZGC、Shenandoah不區分老年代和新生代。
- Epsilon是一個空的GC,僅僅用於調試JDK。
- 圖中的紅色虛線表示可以配合使用。
Serial
垃圾回收的時候,程序是無法執行的。stop-the-world(STW)是停止程序運行,回收線程開始運行,回收結束後程序再接着運行。
Parallel Scavenge
並行回收,多個線程同時進行垃圾回收。
ParNew
配合CMS的年輕代並行回收。
SerialOld
單線程回收算法用於old區域
Parallel Old
多線程回收算法用於old區域
CMS
ConcurrentMarkSweep,用於回收老年代,在垃圾回收的同時程序也能運行。(黃色的表示垃圾回收線程,藍色表示程序執行線程)
調優針對的是Serial、Parallel Scavenge和Serial Old、Parallel New,因爲JDK1.8默認的垃圾回收:Parallel Scavenge + Parallel Old。
6、JVM參數
JVM的命令行參數參考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
JVM參數分類:
- 標準:-開頭,所有的HotSpot都支持。 如:java -version
- 非標準:-X開頭,特定版本HotSpot支持特定命令
- 不穩定:-XX開頭,下個版本可能取消
- -XX: +PrintFlagsFinal --- 設置值(最終生效值)
- -XX:+PrintFlagsInitial --- 默認值
- -XX:+PrintCommandLineFlags ---命令行參數
7、思維導圖
參考文檔:
https://blogs.oracle.com/jonthecollector/our-collectors
https://blogs.oracle.com/jonthecollector/why-not-a-grand-unified-garbage-collector