JAVA虛擬機器—JVM
前言:JVM虛擬機器是java程式執行平臺,對於一個java程式是十分重要的。在執行時JVM虛擬機器的類載入器將.class檔案載入到虛擬機器中轉化為虛擬機器的可執行檔案執行,在現在的虛擬機器中提供了一個HotSpot(熱點探測)機制,頻繁讀取達到某個閾值的.class檔案,會直接被以虛擬機器可執行的檔案存放在虛擬機器中。JVM虛擬機器中有兩個可選機制版本client和server,clident機制為預設機制,如想更改JVM機制版本在jdk\jre\lib\i386\jvm.cfg檔案中更改,將server KNOWN放在client KNOWN上即可。client機制一般運行於客戶端應用,消耗較少的計算機資源;server機制一般用於服務端應用,消耗較多的計算機資源。
一:JVM的結構
1:JVM結構組成
(1)類載入子系統與方法區:類記載子系統負責從檔案系統或網路中載入class資訊,載入的類資訊放在方法區中,方法區還會存放執行時的常量池資訊,包括字串和數字常量
(2)JAVA堆:java堆在虛擬機器啟動時建立,他是java程式最主要的記憶體工作區域,幾乎所有的物件例項都存放在堆中。堆是所有執行緒共享的
(3)直接記憶體:java的NIO庫允許java程式直接使用記憶體。直接記憶體是JAVA堆外的。直接向系統申請的記憶體空間。訪問直接記憶體的速度一般會高於java堆。直接記憶體的大小不受JVM的限制,只要不高於實體記憶體就行。
(4)垃圾回收系統:垃圾回收系統是java虛擬機器的重要組成部分,垃圾回收系統可以對方法區,java堆和直接記憶體進行回收。其中,java堆是垃圾收集器的工作重點。java的垃圾回收系統不同於C++,自動在後臺工作,不需要手動控制。
(5)JAVA棧:每個java虛擬機器執行緒都有一個私有的棧,一個執行緒的java棧線上程被建立時建立,java棧中儲存著區域性變數,方法引數,同時和java方法的呼叫,返回密切相關
(6)本地方法棧:本地方法棧用於本地方法的呼叫,java虛擬機器允許java程式呼叫系統本地系統的本地方法。其他的和java棧類似。
(7)PC暫存器:每個執行緒的私有空間。在任何時刻,一個執行緒總在執行一個方法。這個正在被執行的方法稱為當前方法。如果當前方法不是本地方法,PC暫存器就會指向當前正在被執行的指令。如果當前方法是本地方法,那麼PC暫存器指向undefined
(8)執行引擎:執行引擎是JVM最核心的元件之一,它負責執行虛擬機器的位元組碼,現代虛擬機器為了提高執行效率,會使用即時編譯技術將方法編譯為機器碼後執行。
2:JAVA堆的分代
(1)為什麼要分代
堆記憶體是java虛擬機器管理記憶體中最大的一塊,也是垃圾回收系統回收最頻繁的一段。為了方便垃圾回收和記憶體管理,引入了堆的分代,堆的分代以物件的生存時間為標準,將堆分為了新生代(年輕代),老年代和永久代。這樣在一定程度上提高了垃圾回收的效率,而且儘可能減少了記憶體碎片的產生
(2)記憶體分代劃分
java虛擬機器將堆記憶體分為新生代(年輕代),老年代,永久代,永久代是HotSpot的特有概念,它採用永久代的方式實現方法區。而HotSpot也有去永久化的趨勢,jdk1.7已經將字串常量池從永久代移除。永久代主要存放常量,類資訊,靜態變數等資料,與垃圾回收關係不大。新生代和老年代是垃圾回收的主要區域,垃圾回收時以新生代和老年代之間為分界線使用不同的垃圾回收器,記憶體分代示意圖如下:
2-1:新生代(年輕代)
新生成的物件優先存放於新生代中,新生物件朝生夕死,存活率很低。常規進行一次垃圾回收,新生代會回收70%-95%的空間,回收效率很高
HotSpot將新生代劃分為三塊,一塊較大的Eden區和兩塊較小的Survivor區(from區和to區),from有時被稱為S0區,to有時被稱為S1區,預設的比例為8:1:1。劃分的目的是HotSpot以複製演算法回收新生代。新建立的物件會儲存在Eent中,大物件直接進入老年代。當Eent沒有足夠的空間進行分配時,虛擬機發起一次Minor GC,GC開始時候,to區域中是空的(每次都是),GC開始時,Eent區域中存活下來的物件會被複制到to中,from中物件的年齡值達到年齡閾值的(通常是15,物件每逃過一次GC,年齡值+1,GC分代年齡存放在物件的Header中),被複制到老年代中,沒有達到閾值的被複制到to區中,然後清空Eden區和to區,from區和to區交換身份。現在to區便又為空,為下次GC提供空間。
2-2:老年代
在新生代中經歷了多次GC後存活下來的物件會儲存到老年代。老年代中的物件的生命週期較長,存活率比較高,在老年代中進行GC的頻率相對較低,而且回收的速度也比較慢。
2-3:永久代
永久代存放類資訊,常量,靜態變數,即時編譯器編譯後的程式碼等資料沒,對這一區域而言,一般而言不進行垃圾回收
二:JVM垃圾回收演算法和收集器
1:垃圾回收常見演算法
(1):引用計數
比較古老的回收演算法,原理是此物件有一個引用,增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,回收計數為零的物件,此演算法的缺點是無法處理迴圈引用問題
(2):複製
此演算法把記憶體空間劃分為兩個相等的區域,每次只使用其中一個區域。垃圾回收時將正在使用的物件複製到另一個區域。此演算法只處理正在使用的物件,因此複製成本低,而且複製時可以進行記憶體整理,不會出現記憶體碎片。缺點是需要兩倍的記憶體空間
(3):標記-清除
此演算法執行分兩階段,第一階段從引用根節點開始標記所有被引用的物件,第二次遍歷整個堆,把未標記物件的清除,此演算法需要暫停整個應用,而且會產生記憶體碎片
(4):標記-整理
此演算法執行分兩階段,第一階段從引用根節點開始標記所有被引用的物件,第二次遍歷整個堆,把未標記物件的清除,並吧存活物件壓縮到堆的某一部分,此演算法不會產生碎片,也不會浪費空間,結合了2,3的優點。
2:JVM中垃圾收集器
(1):Scavenge GC(次收集)和Full GC(全收集)的區別
新生代 GC(Scavenge GC || Minor GC):作用於新生代的GC,執行十分頻繁,回收速度也比較快,當Eden空間不足以為物件分配空間時,會觸發Scavenge GC。一般當Eent區為物件分配空間失敗時,會觸發Scavenge GC,對Eent區進行GC,清除非存活物件,將存活物件儲存到Survivor區。然後整理Survivor兩個區。Eent區的GC非常頻繁,所以採用速度快,效率高的演算法。
老年代 GC(Full GC || Major GC):發生在老年代的GC,GC時至少伴隨一次Minor GC,Full GC比Minor GC慢十倍以上。當老年代記憶體不足,或呼叫System.gc()方法時,會觸發Full GC。
次收集:
當新生帶對堆空間緊張時會被觸發,收集間隔較短
全收集:
老年堆或持久代空間滿時會被觸發,也可以使用System.gc()方法來啟動
3:分代垃圾收集器
3-1:新生代(年輕代)收集器
(1)序列收集器(Serial)
Serial收集器是HotSpot執行在Client模式下的預設新生代收集器。使用複製演算法,他的特點是隻用以一個CPU/一條執行緒去完成工作,且在GC時必須要暫停其他所有的工作執行緒(STW,可以使用-XX:+UseSerialGC開啟)。這一點通過安全點的觸發和釋放完成
(2)並行收集器(ParNew)
ParNew是前面Serial的多執行緒版本,除使用多條執行緒進行GC外,其他的控制引數,收集演算法,物件分配規則,回收策略等和Serial完全相同(也是使用VM啟用老年代CMS收集器-XX:useConMarkSweepGC的預設新生代收集器,useConMarkSweepGC收集器也可以與Serial收集器搭配使用),在單核處理器下的效率不一定高於單執行緒的Serial,而且多執行緒切換麻煩,所以可以通過XX:ParallelGCThreads=<N>引數控制參與GC的執行緒數量。減少了安全點的時間,即減少了使用者執行緒等待的時間,從理論上垃圾收集執行緒可以與工作執行緒一同執行,但實際上並未完全實現這一點
(3)Parallel Scavenge收集器
與ParNew類似,Parallel Scavenge也是使用複製演算法,也是並行多執行緒收集器,但是與其他收集器儘可能縮短垃圾收集時間不同,Parallet Scavenge收集器更關注系統吞吐量(系統吞吐量=執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間))。停頓時間越短越適用於使用者互動的程式,良好的響應速度可以提高使用者體驗,而高吞吐量則適用於後臺運算而不需要太多互動的任務,最高效率利用CPU時間。
Parallel Scavenge提供瞭如下引數設定系統吞吐量
Parallel Scavenge收集器引數 | 描述 |
-XX:MaxGCPauseMillis | (毫秒數)收集器將盡量保證記憶體回收時間不超過設定值,但如果太小會導致GC的頻率增加 |
-XX:GCTimeRatio | (整數:0<GCTimeRatio<100)是垃圾收集時間點 |
3-2:老年代收集器
(1)Serial Old收集器
Serial Old是Serial收集器的老年代版本,同樣是單執行緒收集器,使用“標記-整理”演算法(老年代沒有Survivor區,無法使用複製演算法)。可以與任何一個新生代收集器配合使用。
(2)Parallel Old 收集器
Parallel Old是Parallel Scavenge的老年代版本,使用多執行緒和“標記-整理”演算法,吞吐量優先,主要與Parallel Scavenge配合使用。
(3)CMS收集器
CMS(Concurrent Mark Sweep)收集器是一款劃時代的收集器,一枚真正意義上的併發收集器(不用暫停使用者執行緒,使用者執行緒和GC執行緒並行,稱為併發)。雖然現在有理論上表現更好的G1收集器,但主流的網際網路企業線上選用的仍然是CMS。CMS是一種以獲取最短回收停頓時間為目標的收集器,CMS又稱為多併發低暫停的收集器,基於“標記-清除”演算法(CMS可以與Serial,ParNew新生代收集器配合使用,預設使用ParNew)大概分為以下四個步驟:1),3)需要暫停工作執行緒
1):初始標記(initlal mark)
2):併發標記(CMS Concurrent Mark:GC rootsTracing過程)
3):重新標記(CMS remark)
4):併發清除(CMS concurrent Sweep,死亡物件被就地釋放。注意:此處沒有壓縮)
3-3:分割槽收集器-G1收集器
G1收集器是面向伺服器端的收集器,主要目的是配備多核CPU的伺服器治理大記憶體,G1收集器保留老年代,新生代,永久代的概念,但是不基於分代處理,而是將堆分為多個大小相等的獨立區域。可用於整個堆的垃圾回收。
--xx:+UseG1GC 啟用G1收集器
三:JVM的調優
1:JVM自帶的監測小工具
(1)jps
用於檢視JVM中所有程序的具體狀態,包括程序的PID,程式路徑等
jps -v //輸出JVM引數
jps -q //輸出JVM中各個執行緒的PID
jps -m //輸出main方法的引數
jps -l //輸出完全的包名,主類名,PID等
(2)jstat
監測資源和效能,監測JVM記憶體中各個區域的記憶體大小和使用量
結構:jstat [option] [pid] [time>] [count]
option //可選引數
time //間隔多長時間列印一次,單位毫秒
count //列印多少次,不寫無限列印
option:
jstat -compiler [pid] //顯示JVM實時編譯的數量等資訊
jstat -class [pid] //顯示載入類的數量,和所佔用的記憶體空間
jstat -gc [pid] //顯示GC的資訊,檢視各區的分配大小和使用量
jstat -gcutil [pid] //統計GC資訊
jstar -gccapacity [pid] //JVM中三代物件的記憶體使用和佔用狀態
jstat -printcompilation [pid] //當前JVM執行的資訊
(3)jmap
可以獲得JVM的堆中的詳細資訊,從而分析堆的執行狀態
結構:
jmap [option] [pid]
引數:
jmap -heap [pid] //列印堆的資訊。GC演算法,記憶體大小和使用情況等
jmap -histo [pid] //列印堆的物件資訊,包括記憶體大小等
jmap -histo:live [pid] //會先觸發GC
jmap -finalizerinfo //列印等待回收的物件資訊
(4)jstack
列印棧的詳細資訊,從而分析棧的執行狀態
結構:jstack [option] [pid]
引數
jstack -F [pid] //當jstack -l [pid]沒有響應時強制列印棧資訊
jstack -l [pid] //長列表,列印鎖的附加資訊
jstack -h //列印幫助資訊
(5)jconsole
監控java應用程式
jconsole [pid] //本地監控
jconsole [IP:port] //遠端監控
遠端監控需要配置jmx代理資訊,如修改tomcat的bin目錄下吃的catalina.bat檔案
set JAVA_OPTS= %JAVA_OPTS% -Djava.rmi.server.hostname=HostIP
set JAVA_OPTS= %JAVA_OPTS% -Dcom.sun.management.jmxremote.port=8888
set JAVA_OPTS= %JAVA_OPTS% -Dcom.sun.management.jmxremote.ssl=false
set JAVA_OPTS= %JAVA_OPTS% -Dcom.sun.management.jmxremote.authenticate=false
(6)jvisualvm
jdk自帶的圖形話監測工具,可以使用外掛,如Visual GC(專門用於監測垃圾回收系統的外掛)
2:JVM引數介紹
引數的使用環境(多個引數用空格隔開):
(1)可以在eclipse中設定引數
run as->run configurations->選擇執行的程式->arguments->VM arguments->run
(2)命令列中設定引數
(3)基於JVM的程式自帶引數設定,如Tomcat
Tomcat預設可以使用的記憶體為128MB。
Windows下,在檔案/bin/catalina.bat中,在rem Guess CATALINA_HOME if not definedset CURRENT_DIR=%cd%後面新增JVM引數
Unix下,在檔案/bin/catalina.sh中,在檔案頭部設定JVM引數
配置大概如下:
JAVA_OPTS="-Xms50m -Xmx100m -XX:ParallelGCThreads=8"
2:一些引數
-Xms //初始堆大小,如-Xms10m
-Xmx //最大堆大小,如-Xmx100m
-XX:NewSize=n //設定新生代大小
-XX:MaxPermSize=n //設定持久代大小
-XX:NewRatio=n //年輕代和老年代的比值
-XX:SurvivorRatio=n //Eden區和Survivor區的比值
XX:+UseSerialGC //設定序列收集器
-XX:+UseParallelGC //設定並行收集器
-XX:+UseParalledlOldGC //設定並行年老代收集器
-XX:+UseConcMarkSweepGC //設定併發收集器
等等等等。。。。。
官方英文文件:http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html
非官網中文文件:http://www.jvmer.com/jvm-xx-%E5%8F%82%E6%95%B0%E4%BB%8B%E7%BB%8D/