中華人民共和國城鄉(xiāng)與建設(shè)部網(wǎng)站長沙seo優(yōu)化推薦
JVM原理
(1)jvm是java的核心和基礎(chǔ),在java編譯器和os平臺之間的虛擬處理器,可在上面執(zhí)行字節(jié)碼程序。
(2)java編譯器只要面向jvm,生成jvm能理解的字節(jié)碼文件。java源文件經(jīng)編譯成字節(jié)碼程序,通過jvm將每條指令翻譯成不同的機器碼,通過特定平臺運行。
JVM執(zhí)行程序的過程
- 加載.class文件
- 管理并分配內(nèi)存
- 執(zhí)行垃圾收集
JRE(java運行時環(huán)境)由JVM構(gòu)造的java程序的運行環(huán),也是Java程序運行的環(huán)境,但是他同時一個操作系統(tǒng)的一個應(yīng)用程序一個進程,因此他也有他自己的運行的生命周期,也有自己的代碼和數(shù)據(jù)空間。
JVM在整個jdk中處于最底層,負責(zé)于操作系統(tǒng)的交互,用來屏蔽操作系統(tǒng)環(huán)境,提供一個完整的Java運行環(huán)境,因此也就虛擬計算機。
JVM的生命周期
JVM實例對應(yīng)了一個獨立運行的java程序它是進程級別
1) 啟動。啟動一個Java程序時,一個JVM實例就產(chǎn)生了,任何一個擁有public static void
main(String[] args)函數(shù)的class都可以作為JVM實例運行的起點
2) 運行。main()作為該程序初始線程的起點,任何其他線程均由該線程啟動。JVM內(nèi)部有兩種線程:守護線程和非守護線程,main()屬于非守護線程,守護線程通常由JVM自己使用,java程序也可以表明自己創(chuàng)建的線程是守護線程
3) 消亡。當(dāng)程序中的所有非守護線程都終止時,JVM才退出;若安全管理器允許,程序也可以使用Runtime類或者System.exit()來退出
JVM執(zhí)行引擎實例則對應(yīng)了屬于用戶運行程序的線程它是線程級別的
JVM運行時數(shù)據(jù)區(qū)
方法區(qū)(Method Area)
方法區(qū)是所有線程共享的內(nèi)存區(qū)域,它用于存儲已被Java虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。
它有個別命叫Non-Heap(非堆)。當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,拋出OutOfMemoryError異常。
Java堆(Java Heap)
java堆是java虛擬機所管理的內(nèi)存中最大的一塊,是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實例。
在Java虛擬機規(guī)范中的描述是:所有的對象實例以及數(shù)組都要在堆上分配。
java堆是垃圾收集器管理的主要區(qū)域,因此也被成為“GC堆”。
從內(nèi)存回收角度來看java堆可分為:新生代和老生代。
從內(nèi)存分配的角度看,線程共享的Java堆中可能劃分出多個線程私有的分配緩沖區(qū)。
無論怎么劃分,都與存放內(nèi)容無關(guān),無論哪個區(qū)域,存儲的都是對象實例,進一步的劃分都是為了更好的回收內(nèi)存,或者更快的分配內(nèi)存。
根據(jù)Java虛擬機規(guī)范的規(guī)定,java堆可以處于物理上不連續(xù)的內(nèi)存空間中。當(dāng)前主流的虛擬機都是可擴展的(通過 -Xmx 和 -Xms 控制)。如果堆中沒有內(nèi)存可以完成實例分配,并且堆也無法再擴展時,將會拋出OutOfMemoryError異常。
程序計數(shù)器(Program Counter Register)
程序計數(shù)器是一塊較小的內(nèi)存空間,它可以看作是:保存當(dāng)前線程所正在執(zhí)行的字節(jié)碼指令的地址(行號),也可以把它叫做線程計數(shù)器。
由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的,一個處理器都只會執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都有一個獨立的程序計數(shù)器,各個線程之間計數(shù)器互不影響,獨立存儲。稱之為“線程私有”的內(nèi)存。程序計數(shù)器內(nèi)存區(qū)域是虛擬機中唯一沒有規(guī)定OutOfMemoryError情況的區(qū)域。
Java虛擬機棧(Java Virtual Machine Stacks)
java虛擬機是線程私有的,它的生命周期和線程相同。
虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。
虛擬機棧中是有單位的,單位就是棧幀,一個方法一個棧幀。一個棧幀中他又要存儲,局部變量,操作數(shù)棧,動態(tài)鏈接,出口等。
本地方法棧(Native Method Stack)
本地方法棧很好理解,他很棧很像,只不過方法上帶了 native 關(guān)鍵字的棧字
它是虛擬機棧為虛擬機執(zhí)行Java方法(也就是字節(jié)碼)的服務(wù)
native關(guān)鍵字的方法是看不到的,必須要去oracle官網(wǎng)去下載才可以看的到,而且native關(guān)鍵字修飾的大部分源碼都是C和C++的代碼。
同理可得,本地方法棧中就是C和C++的代碼
Java內(nèi)存結(jié)構(gòu)
直接內(nèi)存(Direct Memory)
直接內(nèi)存不是虛擬機運行時數(shù)據(jù)區(qū)的一部分,也不是java虛擬機規(guī)范中定義的內(nèi)存區(qū)域。但是既然是內(nèi)存,肯定還是受本機總內(nèi)存(包括RAM以及SWAP區(qū)或者分頁文件)大小以及處理器尋址空間的限制。
在JDK1.4 中新加入了NIO(New Input/Output)類,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O 方式,它可以使用native 函數(shù)庫直接分配堆外內(nèi)存,然后通脫一個存儲在Java堆中的DirectByteBuffer 對象作為這塊內(nèi)存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在Java堆和Native(本地)堆中來回復(fù)制數(shù)據(jù)。
直接內(nèi)存與堆內(nèi)存的區(qū)別:
直接內(nèi)存申請空間耗費很高的性能,堆內(nèi)存申請空間耗費比較低
直接內(nèi)存的IO讀寫的性能要優(yōu)于堆內(nèi)存,在多次讀寫操作的情況相差非常明顯
JVM字節(jié)碼執(zhí)行引擎
虛擬機核心的組件就是執(zhí)行引擎,它負責(zé)執(zhí)行虛擬機的字節(jié)碼,一般戶先進行編譯成機器碼后執(zhí)行。
“虛擬機”是一個相對于“物理機”的概念,虛擬機的字節(jié)碼是不能直接在物理機上運行的,需要JVM字節(jié)碼執(zhí)行引擎編譯成機器碼后才可在物理機上執(zhí)行。
垃圾收集系統(tǒng)
程序在運行過程中,會產(chǎn)生大量的內(nèi)存垃圾(一些沒有引用指向的內(nèi)存對象都屬于內(nèi)存垃圾,因為這些對象已經(jīng)無法訪問,程序用不了它們了,對程序而言它們已經(jīng)死亡),為了確保程序運行時的性能,java虛擬機在程序運行的過程中不斷地進行自動的垃圾回收(GC)。
垃圾收集系統(tǒng)是Java的核心,也是不可少的,Java有一套自己進行垃圾清理的機制,開發(fā)人員無需手工清理。
JVM的垃圾回收機制
垃圾回收機制簡稱GC
GC主要用于Java堆的管理。Java 中的堆是 JVM 所管理的最大的一塊內(nèi)存空間,主要用于存放各種類的實例對象。
什么是垃圾回收機制
程序在運行過程中,會產(chǎn)生大量的內(nèi)存垃圾(一些沒有引用指向的內(nèi)存對象都屬于內(nèi)存垃圾,因為這些對象已經(jīng)無法訪問,程序用不了它們了,對程序而言它們已經(jīng)死亡),為了確保程序運行時的性能,java虛擬機在程序運行的過程中不斷地進行自動的垃圾回收(GC)。
GC是不定時去堆內(nèi)存中清理不可達對象。不可達的對象并不會馬上就會直接回收, 垃圾收集器在一個Java程序中的執(zhí)行是自動的,不能強制執(zhí)行清楚那個對象,即使程序員能明確地判斷出有一塊內(nèi)存已經(jīng)無用了,是應(yīng)該回收的,程序員也不能強制垃圾收集器回收該內(nèi)存塊。程序員唯一能做的就是通過調(diào)用System.gc 方法來"建議"執(zhí)行垃圾收集器,但是他是否執(zhí)行,什么時候執(zhí)行卻都是不可知的。這也是垃圾收集器的最主要的缺點。當(dāng)然相對于它給程序員帶來的巨大方便性而言,這個缺點是瑕不掩瑜的。
手動執(zhí)行GC:
System.gc(); // 手動回收垃圾
finalize方法作用
finalize()方法是在每次執(zhí)行GC操作之前時會調(diào)用的方法,可以用它做必要的清理工作。
它是在Object類中定義的,因此所有的類都繼承了它。子類覆蓋finalize()方法以整理系統(tǒng)資源或者執(zhí)行其他清理工作。finalize()方法是在垃圾收集器刪除對象之前對這個對象調(diào)用的。
public class Test {public static void main(String[] args) {Test test = new Test();test = null;System.gc(); // 手動回收垃圾}@Overrideprotected void finalize() throws Throwable {// gc回收垃圾之前調(diào)用System.out.println("gc回收垃圾之前調(diào)用的方法");}
}
新生代、老年代、永久代(方法區(qū))的區(qū)別
Java 中的堆是 JVM 所管理的最大的一塊內(nèi)存空間,主要用于存放各種類的實例對象。
在 Java 中,堆被劃分成兩個不同的區(qū)域:新生代 ( Young )、老年代 ( Old )。
老年代就一個區(qū)域。新生代 ( Young ) 又被劃分為三個區(qū)域:Eden、From Survivor、To Survivor。
這樣劃分的目的是為了使 JVM 能夠更好的管理堆內(nèi)存中的對象,包括內(nèi)存的分配以及回收。
默認的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數(shù) –XX:NewRatio 來指定 ),即:新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小。
其中,新生代 ( Young ) 被細分為 Eden 和 兩個 Survivor 區(qū)域,這兩個 Survivor 區(qū)域分別被命名為 From Survivor 和 ToSurvivor ,以示區(qū)分。
默認的,Edem : From Survivor : To Survivor = 8 : 1 : 1 ( 可以通過參數(shù) –XX:SurvivorRatio 來設(shè)定 ),即: Eden = 8/10 的新生代空間大小,From Survivor = To Survivor = 1/10 的新生代空間大小。
JVM 每次只會使用 Eden 和其中的一塊 Survivor 區(qū)域來為對象服務(wù),所以無論什么時候,總是有一塊 Survivor 區(qū)域是空閑著的。
因此,新生代實際可用的內(nèi)存空間為 9/10 ( 即90% )的新生代空間。
永久代就是JVM的方法區(qū)。在這里都是放著一些被虛擬機加載的類信息,靜態(tài)變量,常量等數(shù)據(jù)。這個區(qū)中的東西比老年代和新生代更不容易回收。
Minor GC、Major GC、Full GC區(qū)別及觸發(fā)條件
Minor GC是新生代GC,指的是發(fā)生在新生代的垃圾收集動作。由于java對象大都是朝生夕死的,所以Minor GC非常頻繁,一般回收速度也比較快。
Major GC是老年代GC,指的是發(fā)生在老年代的GC,通常執(zhí)行Major GC會連著Minor GC一起執(zhí)行。Major GC的速度要比Minor GC慢的多。
Full GC是清理整個堆空間,包括年輕代和老年代
Minor GC 觸發(fā)條件一般為:
- eden區(qū)滿時,觸發(fā)MinorGC。即申請一個對象時,發(fā)現(xiàn)eden區(qū)不夠用,則觸發(fā)一次MinorGC。
- 新創(chuàng)建的對象大小 > Eden所??臻g
Major GC和Full GC 觸發(fā)條件一般為:
Major GC通常是跟full GC是等價的
- 每次晉升到老年代的對象平均大小>老年代剩余空間
- MinorGC后存活的對象超過了老年代剩余空間
- 永久代空間不足
- 執(zhí)行System.gc()
- CMS GC異常
- 堆內(nèi)存分配很大的對象
垃圾收集器
垃圾收集器是垃圾回收算法(引用計數(shù)法、標(biāo)記清楚法、標(biāo)記整理法、復(fù)制算法)的具體實現(xiàn),不同垃圾收集器、不同版本的JVM所提供的垃圾收集器可能會有很在差別。
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:CMS、Serial Old、Parallel Old
- 整堆收集器:G1
垃圾回收器詳解
垃圾回收器 | 工作區(qū)域 | 回收算法 | 工作線程 | 用戶線程并行 | 描述 |
---|---|---|---|---|---|
Serial | 新生帶 | 復(fù)制算法 | 單線程 | 否 | Client模式下默認新生代收集器。簡單高效 |
ParNew | 新生帶 | 復(fù)制算法 | 多線程 | 否 | Serial的多線程版本,Server模式下首選, 可搭配CMS的新生代收集器 |
Parallel Scavenge | 新生帶 | 復(fù)制算法 | 多線程 | 否 | 目標(biāo)是達到可控制的吞吐量 |
Serial Old | 老年帶 | 標(biāo)記-整理 | 單線程 | 否 | Serial老年代版本,給Client模式下的虛擬機使用 |
Parallel Old | 老年帶 | 標(biāo)記-整理 | 多線程 | 否 | Parallel Scavenge老年代版本,吞吐量優(yōu)先 |
CMS | 老年帶 | 標(biāo)記-清楚 | 多線程 | 是 | 追求最短回收停頓時間 |
G1 | 新生帶 + 老年帶 | 標(biāo)記-整理 + 復(fù)制算法 | 多線程 | 是 | JDK1.9默認垃圾收集器 |
JVM參數(shù)配置
-Xms:初始堆大小,JVM 啟動的時候,給定堆空間大小。 -Xmx:最大堆大小,JVM 運行過程中,如果初始堆空間不足的時候,最大可以擴展到多少。 -Xmn:設(shè)置堆中年輕代大小。整個堆大小=年輕代大小+年老代大小+持久代大小。 -XX:NewSize=n 設(shè)置年輕代初始化大小大小 -XX:MaxNewSize=n 設(shè)置年輕代最大值-XX:NewRatio=n 設(shè)置年輕代和年老代的比值。如: -XX:NewRatio=3,表示年輕代與年老代比值為 1:3,年輕代占整個年輕代+年老代和的 1/4 -XX:SurvivorRatio=n 年輕代中 Eden 區(qū)與兩個 Survivor 區(qū)的比值。注意 Survivor 區(qū)有兩個。8表示兩個Survivor :eden=2:8 ,即一個Survivor占年輕代的1/10,默認就為8-Xss:設(shè)置每個線程的堆棧大小。JDK5后每個線程 Java 棧大小為 1M,以前每個線程堆棧大小為 256K。-XX:ThreadStackSize=n 線程堆棧大小-XX:PermSize=n 設(shè)置持久代初始值 -XX:MaxPermSize=n 設(shè)置持久代大小-XX:MaxTenuringThreshold=n 設(shè)置年輕帶垃圾對象最大年齡。如果設(shè)置為 0 的話,則年輕代對象不經(jīng)過 Survivor 區(qū),直接進入年老代。-XX:LargePageSizeInBytes=n 設(shè)置堆內(nèi)存的內(nèi)存頁大小-XX:+UseFastAccessorMethods 優(yōu)化原始類型的getter方法性能-XX:+DisableExplicitGC 禁止在運行期顯式地調(diào)用System.gc(),默認啟用 -XX:+AggressiveOpts 是否啟用JVM開發(fā)團隊最新的調(diào)優(yōu)成果。例如編譯優(yōu)化,偏向鎖,并行年老代收集等,jdk6紙之后默認啟動-XX:+UseBiasedLocking 是否啟用偏向鎖,JDK6默認啟用 -Xnoclassgc 是否禁用垃圾回收-XX:+UseThreadPriorities 使用本地線程的優(yōu)先級,默認啟用
JVM的GC垃圾收集器設(shè)置
-XX:+UseSerialGC:設(shè)置串行收集器,年輕帶收集器 -XX:+UseParNewGC:設(shè)置年輕代為并行收集??膳c CMS 收集同時使用。JDK5.0 以上,JVM 會根據(jù)系統(tǒng)配置自行設(shè)置,所以無需再設(shè)置此值。-XX:+UseParallelGC:設(shè)置并行收集器,目標(biāo)是目標(biāo)是達到可控制的吞吐量-XX:+UseParallelOldGC:設(shè)置并行年老代收集器,JDK6.0 支持對年老代并行收集。 -XX:+UseConcMarkSweepGC:設(shè)置年老代并發(fā)收集器-XX:+UseG1GC:設(shè)置 G1 收集器,JDK1.9默認垃圾收集器
示例:
假設(shè)服務(wù)器內(nèi)存8G,運行8個微服務(wù),給每個微服務(wù)分配800M內(nèi)存
設(shè)置JVM:
java -Xmx800m -Xms800m -Xmn300m -Xss1024k
-Xmx800m 設(shè)置JVM最大內(nèi)存為800M
-Xms800m 設(shè)置JVM最小內(nèi)存為800M(與最大值保持一致可以防止每次垃圾回收(GC)后JVM對內(nèi)存進行再分配)
-Xmn300m 設(shè)置新生代大小,新生代大小會影響到老年代的大小,Sun推薦設(shè)置為總內(nèi)存的3/8
-Xss1024k 每個線程堆棧大小,值越小能生成的線程數(shù)越多,但是一般不超過5000個,在遞歸場景下值太小可能會造成堆棧溢出