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