一、什麼是垃圾
垃圾就是無用的東西。那怎麼區分一個東西有用無用呢?
1.垃圾的判定
1.1 引用計數法
原理:
最初人們的想法是這樣:一個東西,我拿來用一次就記作+1,用好了放回去就記作-1,如果一個東西最後在盤算的時候,發現它的得分是0,那麼就可以認爲它目前沒有被誰拿去用,那我現在把它丟掉不會影響到任何人現在正在做的事情。想法是不是很好?
問題:
可是,如果有一天,我們有一個螺絲釘,一個螺絲帽。我們取出螺絲釘和螺絲帽,並把螺絲帽和螺絲釘擰在一起,擰成一個螺組。雖然這個螺組我們沒有在用了,可是由於螺絲釘在和螺絲帽配對,螺絲帽也在忙着和螺絲釘配對,因此他倆都不能被放回去。使得這兩個工具無意義的佔用着空間,因爲他們的組合體現在實際上並沒有什麼用。
使用場景:
據說Lisp,Python,Ruby等的垃圾收集算法採用的就是引用計數算法。
1.2 可達性分析算法
原理:找不在葡萄串上的葡萄,只需要捏着一串葡萄的根部提起來,落在盤子裏的葡萄就是不在葡萄串上的葡萄了。這就是可達性分析算法的原理。即:先找到所謂的根節點,從跟出發,所有用到的都不能回收,因爲都有用,其他的用不着的都可以回收。
問題:
暫未明確
使用場景:
JVM中使用的通常都是可達性分析算法來判斷哪些是垃圾對象。
細說:
jvm掃描堆中的對象,看是否能夠沿着GCRoot對象爲起點的引用鏈找到該對象,找不到,表示可以回收,反之則保留。在實際的操作中通常是
可以通過jmap 工具抓取程序當今的堆內存使用快照
jmap -dump:format=b,live,file=1.bin pid
表示通過jmap 抓取堆內存快照,格式是2進制格式(format=b);
live:表示只抓取存活的對象而忽略掉已經被回收的對象。
file=1.bin :表示抓取的文件會存放在1.bin這個文件中。
通過Eclipse Memory Analyzer工具打開抓取的文件,在java basics->GC Roots中查看可以作爲root的 對象。
1.System Class 系統核心類
2.Native Stack 原生類
3.Thread 線程相關:活動線程。線程的每次方法調用產生的棧幀都可以作爲方法對象。
4.Busy Monitor 正在加鎖的對象
“可達性分析算法”就是看堆中的每個對象是不是在根節點的引用鏈上。如果在根節點上,就不能被回收,否則可以。
“引用鏈”即通過引用關係產生的鏈狀結構。
關於“引用”:
引用可不單是 A a = new A();
這個叫做“強引用”。其他引用還有軟引用、弱引用、虛引用、終接器引用。
【後面我單獨寫一篇來講引用。】
這裏只做簡單的說明。
- 被GC Roots對象強引用的對象不會被回收;
- 被GC Roots對象軟引用的對象在發生了GC和GC後內存仍然不足時它會被垃圾回收;
- 被GC Roots對象弱引用的對象在發生了GC時它會被垃圾回收;
應用:當內存緊張時,可以使用軟鏈接。
二、垃圾回收發生在哪裏
開門見山,這裏講jvm內存結構
堆內存中,分爲新生代,老年代和StringTable(串表區)。其中新生代分爲伊甸園區、倖存區From、倖存區To。
通常情況下,對象在伊甸園誕生,隨着一輪小規模垃圾回收,一些有用的對象就會被複制到“倖存區To”,這些被複制過來的對象的年齡會加一,而伊甸園區則會整個被回收,當這個過程結束之後,倖存區To就會與倖存區From交換名字,即:此時倖存區To變爲了空的,對象都到了倖存區From。等到下次小規模回收的時候,倖存區From和伊甸園裏的有用對象年齡會再統一加一,年齡達到老年標準的對象可以直接進入老年代,沒有達到的會被放入倖存區To,然後倖存區To和倖存區From再交換名稱。
意外情況:
天神下凡:當新生區連續內存空間不足以存放一個大對象而老年代存得下時,對象會在老年代被創建。
補充說明:
StringTable:串池,當創建一個字符串變量時,會先從串池中搜索是否存在該字符串,若有,則直接引用,若沒有,則創建該字符串並將其添加到串池,並引用。它的本質是一個HashTable,大小是固定的不可擴容。
三、垃圾回收的種類
上面提到的“小規模垃圾回收”實際上叫做“minor GC”。
1.Minor GC
- 負責從新生代進行垃圾回收。
- 新對象總是在伊甸園產生的(當新生代連續空間不足時,大對象可以在老年代被創建)
- 當新生代內存不足時,觸發minorGC,將伊甸園中的有用對象複製到倖存區To年齡+1,並將倖存區To與倖存區From互換名稱,接着將整個伊甸園清理掉。
- 當第二次minorGC時,會將伊甸園和倖存區From中的有用元素都放入倖存區To,對象年齡+1,然後清空伊甸園和倖存區From,再將To和From互換名稱。
- 隨着一次次的minorGC,當對象的年齡到15歲時(最大),會被移動到老年代中。對象的壽命最大爲15,因爲對象頭用來存放年齡的位置爲4bit。當新生代空間不足時會將一些對象放入老年代。
- minor GC發生時,會引發stop the world,暫停用戶線程,等GC結束之後用戶線程纔會恢復運行。
- 當老年代空間不足,會先嚐試觸發minorGC,如果之後空間仍然不足,會觸發一次full GC。它同樣會引起一次 stop the world,而且stw時間更長。
2.Full GC
- 在老年代空間不足時負責從老年代進行垃圾回收。
3.小結
- 新生代內存不足發生的垃圾收集都是 minor GC
- Serial GC和Parallel GC在老年代內存不足時發生的垃圾收集都是 Full GC
- 在CMS和G1中,當老年代內存不足時,當老年代內存佔堆內存45%,進入併發標記階段,進而進入混合收集階段。在這兩個階段中,若回收速度大於垃圾產生速度,則不會產生Full GC;若回收速度小於垃圾產生速度,即垃圾收集不過來,則會退化爲並行收集,進而發生Full GC。
三、如何清理
1.標記清除算法
清理垃圾的方式有很多種,比如自己準備一些便利貼,哪些東西沒用了你就給它貼一張寫着
“垃圾”的便利貼,等到週末有空了,把所有貼了“垃圾”便利貼的東西都給扔到垃圾箱去就可以了(記得自己在扔的時候分個類啊!不然可能會罰款)這就是標記清除算法。
2.標記整理算法
當然你也可以在標記之後,把這些垃圾儘可能的放在一塊,這樣的好處就是你的空間會集中一些,不至於這裏一點空,那裏一點空的。這就是標記整理算法。
3.複製算法
當你家足夠大,或者是你的垃圾足夠多的,錢也足夠多,那你就不用像上面那麼做了,可以直接把有用的東西再買一份放到另一間空屋子就可以了。這就是複製算法。
四、有哪些垃圾回收器
按原理分
1.串行 垃圾回收器
特點:
- 單線程
- 適用於堆內存較小,適合個人電腦
Serial:新生代的垃圾回收器,使用“複製”算法回收垃圾。
SerialOld:老年代的垃圾回收器,使用“標記整理”算法回收垃圾。
2.吞吐量優先 垃圾回收器
特點:
- 多線程
- 堆內存較大,多核cpu
- 讓單位時間內,STW的時間最短 如:0.2+0.2=0.4(STW:stop the world,即當執行到垃圾回收的某個階段時,會將所有用戶線程暫停下來,這段暫停時間被稱爲SWT)
開啓開關:-XX:+UseParallelGC~-XX:+UseParallelOldGC~(在jdk1.8開始,此開關默認開啓。即默認使用吞吐量優先垃圾回收器)
工作模式:-XX:+UseAdaptiveSizePolicy:自適應調整新生代的大小
GC時間佔比:-XX:GCTimeRatio = ratio:用來設定GC時間與程序運行時間的比值。1/(1+ratio);默認是99,通常需要配置成19.
最大暫停毫秒數:-XX:MaxGCPauseMillis=ms:默認值是200;
3.響應時間優先 垃圾回收器
並行標記清除 垃圾回收器
-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~SerialOld
-XX:+UseConcMarkSweepGC :用來進行老年代垃圾回收;當其併發失敗,切換至SerialOld進行垃圾回收
-XX:+UseParNewGC:工作在新生代的垃圾回收器
並行度與併發數:
-XX:ParallelGCThreads=n~-XX:ConcGCThreads=threads
並行的垃圾回收線程數:通常等於cpu核數。
併發的垃圾回收線程數:通常設置爲並行數的1/4。即:留着剩餘3/4的線程數用來執行用戶邏輯。
並行數是垃圾回收線程能使用的最多線程數,併發數是可以同時運行的垃圾回收器線程數。
執行cms內存回收時的內存佔比:-XX:CMSInitiatingOccupancyFration=percent
重新標記前對新生代進行垃圾回收:-XX:+CMSScavengeBeforeRemark
cms存在由碎片過多導致的同步失敗,會使垃圾收集時間劇增。這是其一大問題。
按實現分
CMS垃圾回收器
CMS(Concurrent Mark Sweep
),是一款併發的、使用標記-清除算法的垃圾回收器。
具體說明可以看這篇https://www.jianshu.com/p/2a1b2f17d3e4
G1垃圾回收器
- 同時注重吞吐量和低延遲,默認的暫停目標是200ms
- 支持超大堆空間,會將堆劃分爲多個大小相等的Region
- 整體上是標記+整理算法,兩個區域之間是複製算法
- 這是一種在jdk9中默認使用的垃圾回收器。下面有一篇詳細說明。
https://www.jianshu.com/p/aef0f4765098