您好,登錄后才能下訂單哦!
小編給大家分享一下JVM的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
追本溯源——堆和棧
堆通常是一個可以被看做一棵樹的數(shù)組對象,棧是一種只能在一端進行插入和刪除操作的先進后出線性表,JVM的本質(zhì)是堆和棧
第一,從軟件設(shè)計的角度看,棧代表了處理邏輯,而堆代表了數(shù)據(jù)。這樣分開,使得處理邏輯更為清晰。分而治之的思想。這種隔離、模塊化的思想在軟件設(shè)計的方方面面都有體現(xiàn)。
第二,堆與棧的分離,使得堆中的內(nèi)容可以被多個棧共享(也可以理解為多個線程訪問同一個對象)。這種共享的收益是很多的。一方面這種共享提供了一種有效的數(shù)據(jù)交互方式(如:共享內(nèi)存),另一方面,堆中的共享常量和緩存可以被所有棧訪問,節(jié)省了空間。
第三,棧因為運行時的需要,比如保存系統(tǒng)運行的上下文,需要進行地址段的劃分。由于棧只能向上增長,因此就會限制住棧存儲內(nèi)容的能力。而堆不同,堆中的對象是可以根據(jù)需要動態(tài)增長的,因此棧和堆的拆分,使得動態(tài)增長成為可能,相應(yīng)棧中只需記錄堆中的一個地址即可。
第四,面向?qū)ο缶褪嵌押蜅5耐昝澜Y(jié)合。其實,面向?qū)ο蠓绞降某绦蚺c以前結(jié)構(gòu)化的程序在執(zhí)行上沒有任何區(qū)別。但是,面向?qū)ο蟮囊?,使得對待問題的思考方式發(fā)生了改變,而更接近于自然方式的思考。當(dāng)我們把對象拆開,你會發(fā)現(xiàn),對象的屬性其實就是數(shù)據(jù),存放在堆中;而對象的行為(方法),就是運行邏輯,放在棧中。我們在編寫對象的時候,其實即編寫了數(shù)據(jù)結(jié)構(gòu),也編寫的處理數(shù)據(jù)的邏輯。不得不承認,面向?qū)ο蟮脑O(shè)計,確實很美。
JVM運行機制
此圖看出jvm內(nèi)存結(jié)構(gòu)
JVM內(nèi)存結(jié)構(gòu)主要包括兩個子系統(tǒng)和兩個組件。兩個子系統(tǒng)分別是Classloader子系統(tǒng)和Executionengine(執(zhí)行引擎)子系統(tǒng);兩個組件分別是Runtimedataarea(運行時數(shù)據(jù)區(qū)域)組件和Nativeinterface(本地接口)組件。
Classloader子系統(tǒng)的作用:根據(jù)給定的全限定名類名(如java.lang.Object)來裝載class文件的內(nèi)容到Runtimedataarea中的methodarea(方法區(qū)域)。Java程序員可以extendsjava.lang.ClassLoader類來寫自己的Classloader。
Executionengine子系統(tǒng)的作用:執(zhí)行classes中的指令。任何JVMspecification實現(xiàn)(JDK)的核心都是Executionengine,不同的JDK例如Sun的JDK和IBM的JDK好壞主要就取決于他們各自實現(xiàn)的Executionengine的好壞。
Nativeinterface組件:與nativelibraries交互,是其它編程語言交互的接口。當(dāng)調(diào)用native方法的時候,就進入了一個全新的并且不再受虛擬機限制的世界,所以也很容易出現(xiàn)JVM無法控制的nativeheapOutOfMemory。
RuntimeDataArea組件:這就是我們常說的JVM的內(nèi)存了
JVM的生命周期
一、首先分析兩個概念
JVM實例和JVM執(zhí)行引擎實例
?。?)JVM實例對應(yīng)了一個獨立運行的java程序,它是進程級別。
?。?)JVM執(zhí)行引擎實例則對應(yīng)了屬于用戶運行程序的線程,它是線程級別的。
二、JVM的生命周期
(1)JVM實例的誕生:當(dāng)啟動一個Java程序時,一個JVM實例就產(chǎn)生了,任何一個擁有public static void main(String[] args)函數(shù)的class都可以作為JVM實例運行的起點。
?。?)JVM實例的運行 main()作為該程序初始線程的起點,任何其他線程均由該線程啟動。JVM內(nèi)部有兩種線程:守護線程和非守護線程,main()屬于非守護線程,守護線程通常由JVM自己使用,java程序也可以標明自己創(chuàng)建的線程是守護線程。
?。?)JVM實例的消亡:當(dāng)程序中的所有非守護線程都終止時,JVM才退出;若安全管理器允許,程序也可以使用Runtime類或者System.exit()來退出。
類的生命
一個java類的完整的生命周期會經(jīng)歷加載、連接、初始化、使用、和卸載五個階段,當(dāng)然也有在加載或者連接之后沒有被初始化就直接被使用的情況
雙親委派模型是一種組織類加載器之間關(guān)系的一種規(guī)范,他的工作原理是:如果一個類加載器收到了類加載的請求,它不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,這樣層層遞進,最終所有的加載請求都被傳到最頂層的啟動類加載器中,只有當(dāng)父類加載器無法完成這個加載請求(它的搜索范圍內(nèi)沒有找到所需的類)時,才會交給子類加載器去嘗試加載.
認識JVM內(nèi)存模型
對于JVM內(nèi)存,程序員只需要關(guān)注
對象去哪兒——堆內(nèi)存(包括新生代和老年代,新生代又分為Enden區(qū)和兩個Survivor區(qū)),堆內(nèi)存用于存放對象,合理分區(qū)主要是為了提高垃圾回收效率,新創(chuàng)建的對象會在Enden區(qū),經(jīng)歷Minor GC后會到Survivor區(qū),經(jīng)歷一般15次GC還存活的話會進入老年代。
函數(shù)如何調(diào)用——棧內(nèi)存用于運行線程,它們包含了方法里的臨時數(shù)據(jù)、堆里其它對象引用的特定數(shù)據(jù)。
類去哪兒——方法區(qū)用來存儲類型信息(運行時常量和靜態(tài)變量)和方法代碼和構(gòu)造函數(shù)代碼,通常也叫永久代,JDK8用元空間代替永久代
JVM資源一般分為兩種CPU和內(nèi)存,CPU代表著線程棧,內(nèi)存代表著堆,而往往生產(chǎn)環(huán)境JVM問題一般都是這兩種資源不足導(dǎo)致的,當(dāng)線程棧使用不當(dāng)?shù)臅r候,通常會CPU爆滿,當(dāng)堆內(nèi)存使用不當(dāng)?shù)臅r候,通常會出現(xiàn)內(nèi)存溢出。
內(nèi)存管理清潔工——垃圾回收
Java程序語言中的一個最大優(yōu)點是自動垃圾回收,Java垃圾回收會找出沒用的對象,把它從內(nèi)存中移除并釋放出內(nèi)存給以后創(chuàng)建的對象使用。下面圖片生動對比手動垃圾回收和自動垃圾回收的區(qū)別。垃圾回收重點關(guān)注碎片和性能。
初識垃圾回收算法
按回收策略
(1).引用計數(shù)算法:
給對象中添加一個引用計數(shù)器,每當(dāng)有一個地方引用它時,計數(shù)器值就加1;當(dāng)引用失效時,計數(shù)器值就減1;任何時刻計數(shù)器都為0的對象就是不再被使用的,垃圾收集器將回收該對象使用的內(nèi)存。引用計數(shù)算法實現(xiàn)簡單,效率很高,微軟的COM技術(shù)、ActionScript、Python等都使用了引用計數(shù)算法進行內(nèi)存管理,但是引用計數(shù)算法對于對象之間相互循環(huán)引用問題難以解決,因此java并沒有使用引用計數(shù)算法。
(2).根搜索算法:
通過一系列的名為“GC Root”的對象作為起點,從這些節(jié)點向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當(dāng)一個對象到GC Root沒有任何引用鏈相連時,則該對象不可達,該對象是不可使用的,垃圾收集器將回收其所占的內(nèi)存。
在Java語言里,可作為GC Roots對象的包括如下幾種:
a.System Class,像rt.jar里面的java.util.*
b.Thread,開始狀態(tài)的線程
c.虛擬機棧(棧楨中的本地變量表)中的引用的對象
d.方法區(qū)中的類靜態(tài)屬性引用的對象
e.方法區(qū)中的常量引用的對象
f.本地方法棧中JNI的引用的對象
(3).標記-清除算法:
最基礎(chǔ)的垃圾收集算法,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成之后統(tǒng)一回收掉所有被標記的對象。
標記-清除算法的缺點有兩個:首先,效率問題,標記和清除效率都不高。其次,標記清除之后會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片,空間碎片太多會導(dǎo)致當(dāng)程序需要為較大對象分配內(nèi)存時無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作。
(4).復(fù)制算法:
將可用內(nèi)存按容量分成大小相等的兩塊,每次只使用其中一塊,當(dāng)這塊內(nèi)存使用完了,就將還存活的對象復(fù)制到另一塊內(nèi)存上去,然后把使用過的內(nèi)存空間一次清理掉。這樣使得每次都是對其中一塊內(nèi)存進行回收,內(nèi)存分配時不用考慮內(nèi)存碎片等復(fù)雜情況,只需要移動堆頂指針,按順序分配內(nèi)存即可,實現(xiàn)簡單,運行高效。
復(fù)制算法的缺點顯而易見,可使用的內(nèi)存降為原來一半。
(5).標記-清除-整理算法:
標記-整理算法在標記-清除算法基礎(chǔ)上做了改進,標記階段是相同的標記出所有需要回收的對象,在標記完成之后不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,在移動過程中清理掉可回收的對象,這個過程叫做整理。
標記-整理算法相比標記-清除算法的優(yōu)點是內(nèi)存被整理以后不會產(chǎn)生大量不連續(xù)內(nèi)存碎片問題。
復(fù)制算法在對象存活率高的情況下就要執(zhí)行較多的復(fù)制操作,效率將會變低,而在對象存活率高的情況下使用標記-整理算法效率會大大提高。
按分區(qū)回收
(1).分代算法:
根據(jù)對象的存活周期的不同將內(nèi)存劃分為幾塊。一般把java堆分為新生代和老年代,這樣就可以根據(jù)各個年代的特點采用最適當(dāng)?shù)氖占惴āT谛律?,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去,只有少量存活,那就選用復(fù)制算法,只需要付出少量存活對象的復(fù)制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對他進行分配擔(dān)保,就必須使用“標記-整理”算法進行回收。
按系統(tǒng)線程
串行/并行回收方式:在GC過程中,單線程/多線程收集器采用Stop-the-World機制,做事專一吞吐量高;
并發(fā)回收:工作線程和垃圾回收線程并發(fā)執(zhí)行,一心二用,吞吐量較低,不過保證在GC的時候,其它用戶線程可以工作;
垃圾收集器
JVM常見參數(shù)
最后匯總一下JVM常見配置
類加載設(shè)置
-XX:+TraceClassLoading:類加載日志
-XX:+TraceClassUnloading:類卸載日志
堆設(shè)置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:設(shè)置年輕代大小
-XX:NewRatio=n:設(shè)置年輕代和年老代的比值。如:為3,表示年輕代與年老代比值為1:3,年輕代占整個年輕代年老代和的1/4
-XX:SurvivorRatio=n:年輕代中Eden區(qū)與兩個Survivor區(qū)的比值。注意Survivor區(qū)有兩個。如:3,表示Eden:Survivor=3:2,一個Survivor區(qū)占整個年輕代的1/5
-XX:MaxPermSize=n:設(shè)置持久代大小
收集器設(shè)置
-XX:+UseSerialGC:設(shè)置串行收集器
-XX:+UseParallelGC:設(shè)置并行收集器
-XX:+UseParalledlOldGC:設(shè)置并行年老代收集
-XX:+UseConcMarkSweepGC:設(shè)置并發(fā)收集器
垃圾回收統(tǒng)計信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器設(shè)置
-XX:ParallelGCThreads=n:設(shè)置并行收集器收集時使用的CPU數(shù)。并行收集線程數(shù)。
-XX:MaxGCPauseMillis=n:設(shè)置并行收集最大暫停時間
-XX:GCTimeRatio=n:設(shè)置垃圾回收時間占程序運行時間的百分比。公式為1/(1+n)
并發(fā)收集器設(shè)置
-XX:+CMSIncrementalMode:設(shè)置為增量模式。適用于單CPU情況
-XX:ParallelGCThreads=n:設(shè)置并發(fā)收集器年輕代收集方式為并行收集時,使用的CPU數(shù)。并行收集線程數(shù)。
以上是“JVM的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。