1. 程式人生 > >JVM 啟動參數及原理 轉

JVM 啟動參數及原理 轉

開發階段 eve 獨立 監視 校驗 調度器 應該 最小值 noclassgc

Java虛擬機(JVM)是Java應用的運行環境,從一般意義上來講,JVM是通過規範來定義的一個虛擬的計算機,被設計用來解釋執行從Java源碼編譯而來的字節碼。更通俗地說,JVM是指對這個規範的具體實現。這種實現基於嚴格的指令集和全面的內存模型。另外,JVM也通常被形容為對軟件運行時環境的實現。通常JVM實現主要指的是HotSpot。

JVM規範保證任何的實現都能夠以同樣的方式解釋執行字節碼。其實現可以多樣化,包括進程、獨立的Java操作系統或者直接執行字節碼的處理器芯片。我們了解最多的JVM是作為軟件實現,運行在流行的操作系統平臺上(包括Windows、OS X、Linux和Solaris等)。

JVM的結構允許對一個Java應用進行更細微的控制。這些應用運行在沙箱(Sandbox)環境中。確保在沒有恰當的許可時,無法訪問到本地文件系統、處理器和網絡連接。遠程執行時,代碼還需要進行證書認證。

除了解釋執行Java字節碼,大多數的JVM實現還包含一個JIT(just-in-time 即時)編譯器,用於為常用的方法生成機器碼。機器碼使用的是CPU的本地語言,相比字節碼有著更快的運行速度。

雖然理解JVM不是開發或運行Java程序的必要條件,但是如果多了解一些JVM知識,那麽就有機會避免很多性能上的問題。理解了JVM,實際上這些問題會變得簡單明了。

體系結構

JVM規範定義了一系列子系統以及它們的外部行為。JVM主要有以下子系統:

  • Class Loader 類加載器。 用於讀入Java源代碼並將類加載到數據區。
  • Execution Engine 執行引擎。 執行來自數據區的指令。

數據區使用的是底層操作系統分配給JVM的內存。

類加載器(Class Loader)

JVM在下面幾種不同的層面使用不同的類加載器:

  • bootstrap class loader(引導類加載器):是其他類加載器的父類,它用於加載Java核心庫,並且是唯一一個用本地代碼編寫的類加載器。
  • extension class loader(擴展類加載器):是bootstrap class loader加載器的子類,用於加載擴展庫。
  • system class loader(系統類加載器):是extension class loader加載器的子類,用於加載在classpath中的應用程序的類文件。
  • user-defined class loader(用戶定義的類加載器):是系統類加載器或其他用戶定義的類加載器的子類。

當一個類加載器收到一個加載類的請求,首先它會檢查緩存,確認該類是否已經被加載,然後把請求代理給它的父類。如果父類沒能成功的加載類,那麽子類就會自己去嘗試加載該類。子類可檢查父類加載器的緩存,但父類不能看到子類所加載的類。之所類加載體系會這樣設計,是認為一個子類不應該重復加載已經被父類加載過的類。

執行引擎(Execution Engine)

執行引擎一個接一個地執行被加載到數據區的字節碼。為了保證字節碼指令對於機器來說是可讀的,執行引擎使用下面兩個方法:

  • 解釋執行:執行引擎把它遇到的每一條指令解釋為機器語言。
  • 即時編譯:如果一條指令經常被使用,執行引擎會把它編譯為本地代碼並存儲在緩存中。這樣,所有和這個方法相關的代碼都會直接執行,從而避免重復解釋。

盡管即時編譯比解釋執行要占用更多的時間,但是對於需要使用成千上萬次的方法,只需要處理一次。相比每次都解釋執行,以本地代碼的方式運行會節約很多執行時間。

JVM規範中並不規定一定要使用即時編譯。即時編譯也不是用於提高JVM性能的唯一的手段。規範僅僅規定了每條字節碼對應的本地代碼,至於執行引擎如何實現這一對應過程的,完全由JVM的具體實現來決定。

內存模型(Memory Model)

Java內存模型建立在自動內存管理的概念之上。當一個對象不再被一個應用所引用,垃圾回收器就會回收它,從而釋放相應的內存。這一點和其他很多需要自行釋放內存的語言有很大不同。

JVM從底層操作系統中分配內存,並將它們分為以下幾個區域:

  • 堆空間(Heap Space):這是共享的內存區域,用於存儲可以被垃圾回收器回收的對象。
  • 方法區(Method Area):這塊區域以前被稱作“永生代”(permanent generation),用於存儲被加載的類。這塊區域最近被JVM取消了。現在,被加載的類作為元數據加載到底層操作系統的本地內存區。
  • 本地區(Native Area):這個區域用於存儲基本類型的引用和變量。

一個有效的管理內存方法是把對空間劃分為不同代,這樣垃圾回收器就不用掃描整個堆區。大多數的對象的生命周期都很段短暫,那些生命周期較長的對象往往直到應用退出才需要被清除。

當一個Java應用創建了一個對象,這個對象是被存儲到“初生池”(eden pool)。一旦初生池存儲滿了,就會在新生代觸發一次minor gc(小範圍的垃圾回收)。首先,垃圾回收器會標記出那些“死對象”(不再被應用所引用的對象),同時延長所有保留對象的生命周期(這個生命周期長度是用數字來描述,代表了期所經歷過的垃圾回收的次數)。然後,垃圾回收器會回收這些死對象,並把剩余的活著的對象移動到“幸存池”(survivor pool),從而清空初生池。

當一個對象存活達到一定的周期後,它就會被移動到堆中的老生代:“終身代”(tenured pool)。最後,當終身代被填滿時,就會觸發一次full gc或major gc(完全的垃圾回收),以清理終身代。

(譯者註:一般我們把初生池和幸存池所在的區域合並成為新生代,把終身代所在的區域成為老生代。對應的,在新生代上產生的gc稱為minor gc,在老生代上產生的gc稱為full gc。希望這樣大家在其他地方看到對應的術語時能更好理解)

當垃圾回收(gc)執行的時候,所有應用線程都要被停止,系統產生一次暫停。minor gc非常頻繁,所以被優化的能夠快速的回收死對象,是新生代的內存的主要的回收方式。major gc運行起來就相對慢得多,因為要掃描非常多的活著的對象。垃圾回收器本身也有多種實現,有些垃圾回收器在一定情況下能更快的執行major gc。

堆的大小是動態的,只有堆需要擴張的時候才會從內存中分配。當堆被填滿時,JVM會重新給堆分配更多的內存,直到達到堆大小的上限,這種重新分配同樣會導致應用的短暫停止。

線程

JVM是運行在一個獨立的進程中的,但它可以並發執行多個線程,每個線程都運行自己的方法,這是Java必備的一個部分。以即時消息客戶端這樣一個應用為例,它至少運行兩個線程。一個線程用於等待用戶輸入,另一個檢查服務端是否有新的消息傳輸。再以服務端應用為例,有時一個請求可能要涉及多個線程並發執行,所以需要多線程來處理請求。

在JVM的進程中,所有的線程共享內存和其他可用的資源。每一個JVM進程在進入點(main方法)處都要啟動一個主線程,其他線程都從主線程啟動,成為執行過程中的一個獨立部分。線程可以再不同的處理器上並行執行,同樣也可以共享一個處理器,線程調度器負責處理多個線程共享一個處理器的情況。

很多應用(特別是服務端應用)會處理很多任務,需要並行運行。這些任務中有些是非常重要的,需要實時執行的。而另外一些是後臺任務,可以在CPU空閑時執行。任務是在不同的線程中運行的。舉例子來說,服務端可能有一些低優先級的線程,它們會根據一些數據來計算統計信息。同時也會啟動一些高優先級的進程用於處理傳入的數據,響應對這些統計信息的請求。這裏可能有很多的源數據,很多來自客戶端的數據請求,每個請求都會使服務端短暫的停止後臺計算的線程以響應這個請求。所以,你必須監控在運行的線程數目並且保證有足夠的CPU時間來執行必要的計算。

(譯者註:這一段在原文中是在性能優化的章節,譯者認為這可能是作者的不小心,似乎放在線程的章節更合適。)

性能優化

JVM的性能取決於其配置是否與應用的功能相匹配。盡管垃圾回收器和內存回收進程是自動管理內存的,但是你必須掌管它們的頻率。通常來說,你的應用可使用的內存越多,那麽這些會導致應用暫停的內存管理進程需要起作用的就越少。

如果垃圾回收發生的頻率比你想的要多很多,那麽可以在啟動JVM的時候為其配置更大的最大堆大小值。堆被填滿的時間越久,就越能降低垃圾回收發生的頻率。最大堆大小值可以在啟動JVM的時候,用-Xmx參數來設定。默認的最大堆大小是被設置為可用的操作系統內存的四分之一,或者最小1GB。

如果問題出在經常重新分配內存,那麽你可以把初始化堆大小設置為和最大堆大小一樣。這就意味著JVM永遠不需要為堆重新分配內存。但這樣做就會失去動態堆大小適配的優化,堆的大小從一開始就被固定下來。配置初始化對大小是在啟動JVM,用-Xms來設定。默認初始化堆大小會被設定為操作系統可用的物理內存的六十四分之一,或者設置一個最小值。這個值是根據不同的平臺來確定的。

如果你清楚是哪種垃圾回收(minor gc或major gc)導致了性能問題,可以在不改變整個堆大小的情況下設定新生代和老生代的大小比例。對於需要產生大量臨時對象的應用,需要增大新生代的比例(當然,後果是減小了老生代的大小)。對於長生命周期對象較多的應用,則需增大老生代的比例(自然需要減少新生代的大小)。以下幾種方法可以用來設定新生代和老生代的大小:

  • 在啟動JVM時,使用-XX:NewRatio參數來具體指定新生代和老生代的大小比例。比如,如果想讓老生代的大小是新生代的五倍,則設置參數為-XX:NewRatio=5,默認這個參數設定為2(即老生代占用堆空間的三分之二,新生代占用三分之一)。
  • 在啟動JVM時,直接使用-Xmn參數設定初始化和最大新生代大小,那麽堆中的剩余大小即是老生代的大小。
  • 在啟動JVM時,直接使用-XX:NewSize和-XX:MaxNewSize參數設定初始化和最大新生代大小,那麽堆中的剩余大小即是老生代的大小。

每一個線程都有一個棧,用於保存函數調用、返回地址等等,這些棧有著對應的內存分配。如果線程過多,就會導致OutOfMemory錯誤。即使你有足夠的空間的堆來存放對象,你的應用也可能會因為創建一個新的線程而崩潰。這種情況下,需要考慮限制線程中的棧大小的最大值。線程棧大小可以在JVM啟動的時候,通過-Xss參數來設置,默認這個值被設定為320KB至1024KB之間,這和平臺相關。

性能監控

當開發或運行一個Java應用的時候,對JVM的性能進行監控是很重要的。配置JVM不是一次配置就萬事大吉的,特別是你要應對的是Java服務器應用的情況。你必須持續的檢查堆內存和非堆內存的分配和使用情況,線程數的創建情況和內存中加載的類的數據情況等。這些都是核心參數。

使用Anturis控制臺,你可以為任何的硬件組件上運行的JVM配置監控(例如,在一臺電腦上運行的一個Tomcat網頁服務器)。

JVM監控可以使用以下衡量標準:

  • 總內存使用情況(MB):即JVM使用的總內存。如果JVM使用了所有可用內存,這項指標可以衡量底層操作系統的整體性能。
  • 堆內存使用(MB):即JVM為運行的Java應用所使用的對象分配的所有內存。不使用的對象通常會被垃圾回收器從堆中移除。所以,如果這個指數增大,表示你的應用沒有把不使用的對象移除或者你需要更好的配置垃圾回收器的參數。
  • 非堆內存的使用(MB):即為方法區和代碼緩存分配的所有內存。方法區是用於存儲被加載的類的引用,如果這些引用沒有被適當的清理,永生代池會在每次應用被重新部署的時候都會增大,導致非堆的內存泄露。這個指標也可能指示了線程創建的泄露。
  • 池內總內存(MB):即JVM所分配的所有變量內存池的內存和(即除了代碼緩存區外的所有內存和)。這個指標能夠讓你明確你的應用在JVM過載前所能使用的總內存。
  • 線程:即所有有效線程數。舉個例子,在Tomcat服務器中每個請求都是一個獨立的線程來處理,所以這個衡量指標可以表示當前有多少個請求數,是否影響到了後臺低權限的線程的運行。
  • 類:即所有被加載的類的總數。如果你的應用動態的創建很多類,這可能是服務器內存泄露的一個原因。

一、標準參數

1.-server
-client
虛擬機服務器模式/客戶機模式,使用server模式可以提高性能,啟動比client模式慢,長期運行則比client模式快。當該參數不指定時,虛擬機啟動檢測主機是否為服務器,如果是則以server模式啟動,否則以client模式啟動,J2SE5.0檢測的根據是至少2個CPU和最低2GB內存
2.-agentlib:<lib-name>=<options>
-agentpath:<lib-path>=<options>
本地類庫加載,當你的部分類包含一些本地方法時,需要自己編寫本地代碼並位於操作系統加載共享包(dll)的路徑上,如果你不喜歡將該包放在操作系統識別的加載上,則可以通過指定這個參數來加載自己的本地共享包(dll)。不同之處在於-agentlib中僅指定包名,根據操作系統的不同虛擬機在一定路徑上搜索該包,譬如對於windows平臺虛擬機在PATH路徑上搜索該包,而lib-path則是指定全路徑,例如
-agentlib:hprof 在windows平臺虛擬機會在啟動時到PATH路徑上搜索hprof.dll並加載
虛擬機在加載代理包之後有一個啟動的操作(詳細參見JDK參考),<options>指的是代理包的啟動參數
3.-classpath classpath
-c classpath
指定類路徑,系統應用類加載器(ClassLoader)會到該路徑下加載類
4.-Dproperty=value
設置系統屬性,可以通過System.getProperty(property)獲得
5.-enableassertions[:<package name>"..." | :<class name> ]
-ea[:<package name>"..." | :<class name> ]
-disableassertions[:<package name>"..." | :<class ; ]
-da[:<package name>"..." | :<class name> ]
啟用和停用斷言,默認是停用斷言。斷言指的是從JDK1.4開始在支持的關鍵字assert,assert(booleanvalue),當 booleanvalue為false時,拋出java.lang.AssertionError,必須指出的是,代碼編譯必須是1.4及其以上順從的,即編譯時使用如下參數
java -source 1.4
一般僅在開發階段啟用斷言,而在運行階段不使用
其使用包括如下幾種情況
java -ea //啟動斷言
java -ea:pkname... //在包pkname及其子包下起用斷言
java -ea:pkname.classname //對類 pkname.classname啟用斷言
停用斷言與啟用設置類似
6.-enablesystemassertions
-esa
-disablesystemassertions
-dsa
啟用和停用系統類斷言
7.-jar
運行包含在一個jar包裏的程序,一般在jar包的/META-INF/MANIFEST.MF文件中指定Main-Class值為要運行的主函數,譬如 Main-Class:ayufox.ejb3.Test
8.-javaagent:<classname>[<=options>]
加載java語言代理,該功能是JDK5新增加的,可以通過該設置在JVM運行主函數(main)之前做一些預處理工作,其中classname中必須包含有靜態方法
public static void premain(String agentArgs, Instrumentation inst) { ... }
上面的options即是傳入該函數的代理參數agentArgs,關於Instrumentation詳細參見包java.lang.instrument
9.-verbose
-verbose:class
-verbose:gc
-verbose:jni
在運行時
class:將類加載情況在控制臺中打印出來
gc:將虛擬機的垃圾回收事件信息打印
jni:放本地方法調用信息打印
-verbose與-verbose:class一樣
10.-version
-showversion
顯示版本信息,不同在於第一種顯示版本後虛擬機結束退出
11.-?
-help
顯示幫助信息並退出
12.-X
顯示非標準參數(見下面介紹)並退出
二、非標準參數(以-X開頭)
1.-Xint
所有字節碼以解析模式運行。第一代虛擬機即是以這種方式運行,由於需要Java解析器解析運行,所以效率比較低;第二代虛擬機則采用將字節碼編譯成本地代碼的方式,效率大大提高;第三代虛擬機也叫自適應(HotSpot)虛擬機,通過監測代碼的執行情況檢測出代碼被頻繁執行的部分,將其盡量優化成本地代碼方式運行,而對於普通部分,則采用解析的模式運行。
2.-Xbatch
禁止後臺編譯,一般HotSpot虛擬機在檢測到一段代碼為頻繁執行代碼需要將其編譯成本地代碼時,會啟動一個後臺線程完成這個工作,而同時采用解析的方式繼續運行字節碼。如果設置了該參數,則會停止繼續執行字節碼,先將其編譯成本地代碼,然後再繼續執行。
3.-Xdebug
-Xnoagent
-Xrun
-Xrunjdwp
啟用調試模式,見前面的《利用JPDA構建調試平臺》這篇文章,後面將在一個獨立的文章中詳細介紹
4.-Xbootclasspath:bootclasspath
-Xbootclasspath/a:path
-Xbootclasspath/p:path
設置啟動根Classpath,即使啟動類加載器將在何處加載對象,關於類啟動加載器,參見《JVM類加載器體系結構》說明,分號後面的值指定路徑,以分號隔開。其區別在於,-Xbootclasspath:bootclasspath將新的根加載路徑覆蓋默認的路徑(\jre\lib \rt.jar),-Xbootclasspath/a:path將新的根加載路徑和原有的根加載路徑相結合,-Xbootclaspath /p:path將新的根加載路徑與原有的根加載路徑相結合,加載類時優先搜索該加載路徑
5.-Xcheck:jni
對本地調用(JNI)采用更嚴格的檢測方式,在進行JNI調用之前檢測數據和傳入參數,如果碰到不合法的數據則強制結束掉虛擬機,對運行性能有損害
6.-Xfuture
對類格式(class文件格式)采用更嚴格的檢測方式,以便向後兼容,最好在開發時采用該參數
7.-Xnoclassgc
不使用垃圾回收
8.-Xloggc:file
與-verbose:gc功能一樣,不同在於-Xloggc:file將信息記錄到一個文件,而-verbose:gc將其輸出到控制臺
9.-Xincgc
-Xmsn
-Xmxn
-Xssn
跟內存分配和垃圾回收相關,-Xincgc表示采用漸進式垃圾回收,-Xmsn設置初始內存池大小,-Xmxn表示內存池允許的最大大小,-Xssn是線程棧大小,n是要設置的值,必須是1024的倍數,譬如
-Xms6291456 -Xmx83886080
-Xms6144k -Xmx81920k
-Xms6m -Xmx80m
該部分對虛擬機的性能非常重要,在後面將有獨立的篇章詳細介紹
10.-Xprof
-Xrunhprof[:help][:<suboption>=<value>,...]
在運行時剖析運行情況,並將剖析結果打印到控制臺,其中後一個可以指定特定剖析對象,譬如cpu,堆(heap)等,可以運行java -Xrunhprof:help獲得可以剖析的對象和取值
11.-Xrs
減少JVM對操作系統信號量的使用,J2SE1.3.1開始引入。
SUN 在J2SE1.3.0中增加了Java應用程序關閉時的回調鉤子(Hook),以便當JVM意外終止時用戶可以做一些資源清除工作。JVM監視控制臺事件以實現JVM意外終止時的回調。JVM明確地註冊了一個控制臺控制處理器,當JVM接收到CTRL_C_EVENT, CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, 或CTRL_SHUTDOWN事件時,該處理器介入關閉回掉鉤子(HOOK)的處理。

如果虛擬機以服務的方式運行(譬如WEB服務器)當其收到 CTRL_LOGOFF_EVENT事件,由於系統並不會因此終止JVM進程,故JVM不可以進行終止的操作,然而這與如上產生了沖突(不結束卻又調用關閉回調鉤子),為了避免這個問題,從J2SE1.3.1使用-Xrs以使JVM不再監測控制臺事件。

一、 JVM的生命周期

1. JVM實例對應了一個獨立運行的java程序它是進程級別

a) 啟動。啟動一個Java程序時,一個JVM實例就產生了,任何一個擁有public static void main(String[] args)函數的class都可以作為JVM實例運行的起點

b) 運行。main()作為該程序初始線程的起點,任何其他線程均由該線程啟動。JVM內部有兩種線程:守護線程和非守護線程,main()屬於非守護線程,守護線程通常由JVM自己使用,java程序也可以標明自己創建的線程是守護線程

c) 消亡。當程序中的所有非守護線程都終止時,JVM才退出;若安全管理器允許,程序也可以使用Runtime類或者System.exit()來退出

2. JVM執行引擎實例則對應了屬於用戶運行程序的線程它是線程級別的

二、 JVM的體系結構

1. 類裝載器(ClassLoader)(用來裝載.class文件)

2. 執行引擎(執行字節碼,或者執行本地方法)

3. 運行時數據區(方法區、堆、java棧、PC寄存器、本地方法棧)

三、 JVM類加載器

JVM整個類加載過程的步驟:

1. 裝載

裝載過程負責找到二進制字節碼並加載至JVM中,JVM通過類名、類所在的包名通過ClassLoader來完成類的加載,同樣,也采用以上三個元素來標識一個被加載了的類:類名+

包名+ClassLoader實例ID。

2. 鏈接

鏈接過程負責對二進制字節碼的格式進行校驗、初始化裝載類中的靜態變量以及解析類中調用的接口、類。

完成校驗後,JVM初始化類中的靜態變量,並將其值賦為默認值。

最後對類中的所有屬性、方法進行驗證,以確保其需要調用的屬性、方法存在,以及具備應的權限(例如public、private域權限等),會造成NoSuchMethodError、NoSuchFieldError等錯誤信息。

3. 初始化

初始化過程即為執行類中的靜態初始化代碼、構造器代碼以及靜態屬性的初始化,在四種情況下初始化過程會被觸發執行:

調用了new;

反射調用了類中的方法;

子類調用了初始化;

JVM啟動過程中指定的初始化類。

JVM類加載順序:

JVM兩種類裝載器包括:啟動類裝載器和用戶自定義類裝載器。

啟動類裝載器是JVM實現的一部分;

用戶自定義類裝載器則是Java程序的一部分,必須是ClassLoader類的子類。

JVM裝載順序:

Jvm啟動時,由Bootstrap向User-Defined方向加載類;

應用進行ClassLoader時,由User-Defined向Bootstrap方向查找並加載類;

1. Bootstrap ClassLoader

這是JVM的根ClassLoader,它是用C++實現的,JVM啟動時初始化此ClassLoader,並由此ClassLoader完成$JAVA_HOME中jre/lib/rt.jar(Sun JDK的實現)中所有class文件的加載,這個jar中包含了java規範定義的所有接口以及實現。

2. Extension ClassLoader

JVM用此classloader來加載擴展功能的一些jar包。

3. System ClassLoader

JVM用此classloader來加載啟動參數中指定的Classpath中的jar包以及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。

4. User-Defined ClassLoader

User-DefinedClassLoader是Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於加載非Classpath中的jar以及目錄。

ClassLoader抽象類的幾個關鍵方法:

(1) loadClass

此方法負責加載指定名字的類,ClassLoader的實現方法為先從已經加載的類中尋找,如沒有則繼續從parent ClassLoader中尋找,如仍然沒找到,則從System ClassLoader中尋找,最後再調用findClass方法來尋找,如要改變類的加載順序,則可覆蓋此方法

(2) findLoadedClass

此方法負責從當前ClassLoader實例對象的緩存中尋找已加載的類,調用的為native的方法。

(3) findClass

此方法直接拋出ClassNotFoundException,因此需要通過覆蓋loadClass或此方法來以自定義的方式加載相應的類。

(4) findSystemClass

此方法負責從System ClassLoader中尋找類,如未找到,則繼續從Bootstrap ClassLoader中尋找,如仍然為找到,則返回null。

(5) defineClass

此方法負責將二進制的字節碼轉換為Class對象

(6) resolveClass

此方法負責完成Class對象的鏈接,如已鏈接過,則會直接返回。

四、 JVM執行引擎

在執行方法時JVM提供了四種指令來執行:

(1)invokestatic:調用類的static方法

(2)invokevirtual:調用對象實例的方法

(3)invokeinterface:將屬性定義為接口來進行調用

(4)invokespecial:JVM對於初始化對象(Java構造器的方法為:<init>)以及調用對象實例中的私有方法時。

主要的執行技術有:

解釋,即時編譯,自適應優化、芯片級直接執行

(1)解釋屬於第一代JVM,

(2)即時編譯JIT屬於第二代JVM,

(3)自適應優化(目前Sun的HotspotJVM采用這種技術)則吸取第一代JVM和第二代

JVM的經驗,采用兩者結合的方式

開始對所有的代碼都采取解釋執行的方式,並監視代碼執行情況,然後對那些經常調用的方法啟動一個後臺線程,將其編譯為本地代碼,並進行優化。若方法不再頻繁使用,則取消編譯過的代碼,仍對其進行解釋執行。

五、 JVM運行時數據區

第一塊:PC寄存器

PC寄存器是用於存儲每個線程下一步將執行的JVM指令,如該方法為native的,則PC寄存器中不存儲任何信息。

第二塊:JVM棧

JVM棧是線程私有的,每個線程創建的同時都會創建JVM棧,JVM棧中存放的為當前線程中局部基本類型的變量(java中定義的八種基本類型:boolean、char、byte、short、int、long、float、double)、部分的返回結果以及Stack Frame,非基本類型的對象在JVM棧上僅存放一個指向堆上的地址

第三塊:堆(Heap)

它是JVM用來存儲對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配,Heap中的對象的內存需要等待GC進行回收。

(1) 堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,這也導致了new對象的開銷是比較大的

(2) Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會盡量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配

(3) TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

第四塊:方法區域(Method Area)

(1)在Sun JDK中這塊區域對應的為PermanetGeneration,又稱為持久代。

(2)方法區域存放了所加載的類的信息(名稱、修飾符等)、類中的靜態變量、類中定義為final類型的常量、類中的Field信息、類中的方法信息,當開發人員在程序中通過Class

對象中的getName、isInterface等方法來獲取信息時,這些數據都來源於方法區域,同時方法區域也是全局共享的,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出OutOfMemory的錯誤信息。

第五塊:運行時常量池(Runtime Constant Pool)

存放的為類中的固定的常量信息、方法和Field的引用信息等,其空間從方法區域中分配。

第六塊:本地方法堆棧(Native Method Stacks)

JVM采用本地方法堆棧來支持native方法的執行,此區域用於存儲每個native方法調用的狀態。

六、 JVM垃圾回收

GC的基本原理:將內存中不再被使用的對象進行回收,GC中用於回收的方法稱為收集器,由於GC需要消耗一些資源和時間,Java在對對象的生命周期特征進行分析後,按照新生代、舊生代的方式來對對象進行收集,以盡可能的縮短GC對應用造成的暫停

(1)對新生代的對象的收集稱為minor GC;

(2)對舊生代的對象的收集稱為Full GC;

(3)程序中主動調用System.gc()強制執行的GC為Full GC。

不同的對象引用類型, GC會采用不同的方法進行回收,JVM對象的引用分為了四種類型:

(1)強引用:默認情況下,對象采用的均為強引用(這個對象的實例沒有其他對象引用,GC時才會被回收)

(2)軟引用:軟引用是Java中提供的一種比較適合於緩存場景的應用(只有在內存不夠用的情況下才會被GC)

(3)弱引用:在GC時一定會被GC回收

(4)虛引用:由於虛引用只是用來得知對象是否被GC

JVM 啟動參數及原理 轉