JVM學習

參考這個博客:
https://mp.weixin.qq.com/s/4c9K5eYMFGVV2WyKaYXVBA

參考書籍:深入理解Java虛擬機

JVM就是java虛擬機的意思,java之所以可以跨平臺,就是因爲其語言運行在虛擬機上,從而達到跨平臺的效果
目前常用的虛擬機有兩個,一個是sun公司的hotspot,另一個是 JRockit

java的內存管理,其內存分爲三大部分,棧,堆,方法區(又叫塊,永久代Permanent Generation)
上面只是一個籠統的劃分,細分如下:
1.方法區(Method Area)
2.堆(heap)
3.棧(stack)
3.1虛擬機棧,就是常說的對象變量存放的地方
3.2本地方法棧,爲JVM中native方法服務
3.3程序計數器,就是程序運行時,運行到哪一步的一個記錄器。多線程中程序切換,就是由各自線程的程序計算器來記錄當前線程運行的狀況,以便切換後能繼續執行下去。

如下圖:
這裏寫圖片描述

棧:
棧是線程獨有的,堆和塊是線程共享的,所以棧的生命週期與線程相關,一般不用回收,只有堆和區需要

棧是存放對象的引用的,比如,Object j=new Object(); 那麼這個j就是存放在棧中,還有局部變量等。
一般如果出現棧溢出(StackOverFlow),那麼都是方法死循環造成的。

堆:
堆是存放對象的空間,上面這個j對應的內存空間就是在堆中,這部分纔是我們可以控制的,也是gc(垃圾回收)需要注意的數組也是在堆上分配

堆如果細分,有新生代(年輕代),老年代(年老代),再細分,年輕代內存又被分成三部分,Eden空間(伊甸園)、From Survivor空間(S0)、To Survivor空間(S1),默認情況下年輕代按照8:1:1的比例來分配

方法區
方法區,是存放類信息,類的常量,靜態變量,接口,方法等。
也可以稱爲永久代(Permant Gernation),一般不輕易回收,但也會,不是真的永久。。。
這裏寫圖片描述

當對象沒有被引用時,即j=null時,而且沒有其它對象引用j,纔會回收堆上的空間

JVM設置的值
-Xmx ——最大堆大小
-Xms ——初始堆大小
一般Xmx與Xms設置大小相同,不然當內存達到Xms時,未到Xmx,需要再分配內存時,會觸發fullGc。而如果一開始就分了Xmx內存大小,則沒有這個問題。但是會在內存不足時,直接報錯(當機),沒有再分配的機會。

-XX:PermSize ——方法區最小值
-XX:MaxPermSize——方法區最大值
-Xss——棧的大小, 每個線程可使用的內存大小
-Xmn——新生代大小,老生代=Xmx-Xmn,Xmn不能等於Xmx,否則會報錯
-XX:NewSize——新生代初始大小
-XX:MaxNewSize——新生代最大值
一般用-Xmn代替上面兩個設置。
-XX:NewRatio=m 老年代與 新生代比例 如果是5,則是5:1的意思

-XX:SurvivorRatio=m Eden和Survivor的比例
SurvivorRatio爲新生代空間中的Eden區和救助空間Survivor區的大小比值,默認是32,也就是說Eden區是 Survivor區的32倍大小,要注意Survivo是有兩個區的,因此Surivivor其實佔整個young genertation的1/34

在設置內存時,可以用m,g等,但是不能m和g混着用,否則可能報錯

32位系統,一般一個進程(java服務)分配的內存大小事有限的,比如linux給這個進程分配了2g內存,那麼最大堆內存+最大方法區內存+棧內存不能大於這個2g,故一般設置時,Xmx,MaxPermSize最好不要大於服務器內存,否則會有可能服務無法啓動
計數器很小一般忽略不計,棧內存一般默認1m,你可以調整。因爲棧跟線程相關,故棧內存越小,能創建的線程就越多,那麼線程數也不是無限的,操作系統一般有規定不能大於多少。

對象分配規則

對象被創建時,內存的分配首先發生在年輕代的Eden區(大對象可以直接在年老代),大部分對象在創建後很快就不再使用,很快就被年輕代的GC回收。
新生代分爲伊甸園區(Eden),存活區1(S0),存活區2(S1),當伊甸園區滿了之後,會執行一次垃圾回收(Minor GC),把剩餘的放到兩個存活區中。每經過1次Minor GC,對象的年齡計數器+1,到達閥值後,進入老年代。

對象如果在新生代存貨了下來(幾次GC後沒被回收掉),則會被複制到老年代。年老代的空間比年輕代大,存放更多對象,發生GC的次數也少,當年老代內存不足,將執行full gc(整個內存區域垃圾回收)

垃圾回收

minorGc——一般在新生代,時間較短
MajorGC——一般在老年代,也可能因爲minorGc後空間不足觸發
fullGc——整個堆回收,時間較長,會導致整個應用停頓。比如你Xms=100m Xmx=1024,那麼再內存不足時,由於沒到最大值,會自動再給他分配內存,此時就會觸發fullGc。或者你對象很大,老年代放不下,也會觸發fullGc。如果到達1024,還要分配內存,就會出發內存錯誤。

對象存活判斷:

1.引用計數
原理:對象有一個引用,則+1,刪除一個引用,則-1,只收集計數爲0的對象
缺點:無法處理循環引用的問題,如對象A和對象B互相引用,A.b=B,B.a=A,除此之外,沒有其他對象引用A,B,那麼無法回收他們
JVM並未使用此算法

2.可達性分析
GC Roots開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的,不可達對象。

回收算法
複製(Copy)
原理:把內存空間劃分爲兩個相等的區域,每次只使用一個區域。垃圾回收時,遍歷當前區域,把存活的對象複製到另外一個區域, 再把已使用過的內存空間一次清理掉。
優點:不會出現碎片問題,高效,遍歷一次即可。
缺點:需要兩倍大的內存
應用場景:堆的新生代回收中使用。經過一次垃圾回收後的對象複製到Survior中
如圖所示,可以看到,每次內存都只用了一半,很浪費。
這裏寫圖片描述

標記 -清除算法(Mark-Sweep)
首先標記出所有需要回收的對象,在標記完成後統一回收掉所有被標記的對象
缺點:會導致內存零碎,那麼如果有大對象,將會出現沒有連續的內存去存放這個大對象。需要兩次遍歷,第一標記,第二次清除。
如圖所示,可看到回收後碎片較多。
這裏寫圖片描述

標記—壓縮
從GC roots開始遍歷所有引用,對有活的對象進行標記。讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存
優點:避免複製算法的空間問題,避免標記-清除的碎片問題
堆的老生代回收中使用該算法,因爲要遍歷,故比複製算法要慢
如圖所示:

這裏寫圖片描述

分代收集,Java堆分爲新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用複製算法,只需要付出少量存活對象的複製成本就可以完成收集。而老年代中因爲對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或“標記-整理”算法來進行回收。

垃圾回收器
即使用回收算法的各種回收器,過於複雜,不討論。有以下幾種

Serial收集器,串行收集器是最古老,最穩定以及效率高的收集器,可能會產生較長的停頓,只使用一個線程去回收。
ParNew收集器,ParNew收集器其實就是Serial收集器的多線程版本。
Parallel收集器,Parallel Scavenge收集器類似ParNew收集器,Parallel收集器更關注系統的吞吐量。
Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法
CMS收集器,CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。
G1收集器,G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量性能特徵

一般老年代比新生代大,這樣可以在新生代觸發minorgc,時間較短(影響較小),老年代空間大,就不用老是gc了。
如果gc時間較短,幾百毫秒,基本不需要怎麼調整,如果1秒以上,才需要關注。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章