Java虛擬機(JVM)是Java應用的運行環境,從通常意義上來說,JVM是經過規範來定義的一個虛擬的計算機,被設計用來解釋執行從Java源碼編譯而來的字節碼。html
JVM主要有子系統和內存區:bootstrap
Class Loader
類加載器。 用於讀入Java源代碼並將類加載到數據區。緩存
Execution Engine
執行引擎。 執行來自數據區的指令。性能優化
運行時數據區使用的是底層操做系統分配給JVM的內存。(JVM的內存模型)服務器
JVM在下面幾種不一樣的層面使用不一樣的類加載器:多線程
bootstrap class loader(引導類加載器):是其餘類加載器的父類,它用於加載Java核心庫,而且是惟一一個用本地代碼編寫的類加載器。併發
extension class loader(擴展類加載器):是bootstrap class loader加載器的子類,用於加載擴展庫。jvm
system class loader(系統類加載器):是extension class loader加載器的子類,用於加載在classpath中的應用程序的類文件。函數
user-defined class loader(用戶定義的類加載器):是系統類加載器或其餘用戶定義的類加載器的子類。性能
當一個類加載器收到一個加載類的請求,首先它會檢查緩存,確認該類是否已經被加載,而後把請求代理給它的父類。若是父類沒能成功的加載類,那麼子類就會本身去嘗試加載該類。子類可檢查父類加載器的緩存,但父類不能看到子類所加載的類。之所類加載體系會這樣設計,是認爲一個子類不該該重複加載已經被父類加載過的類。
執行引擎一個接一個地執行被加載到數據區的字節碼。爲了保證字節碼指令對於機器來講是可讀的,執行引擎使用下面兩個方法:
解釋執行:執行引擎把它遇到的每一條指令解釋爲機器語言。
即時編譯:若是一條指令常常被使用,執行引擎會把它編譯爲本地代碼並存儲在緩存中。這樣,全部和這個方法相關的代碼都會直接執行,從而避免重複解釋。
儘管即時編譯比解釋執行要佔用更多的時間,可是對於須要使用成千上萬次的方法,只須要處理一次。相比每次都解釋執行,以本地代碼的方式運行會節約不少執行時間。
JVM規範中並不規定必定要使用即時編譯。即時編譯也不是用於提升JVM性能的惟一的手段。規範僅僅規定了每條字節碼對應的本地代碼,至於執行引擎如何實現這一對應過程的,徹底由JVM的具體實現來決定。
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的性能取決於其配置是否與應用的功能相匹配。儘管垃圾回收器和內存回收進程是自動管理內存的,可是你必須掌管它們的頻率。一般來講,你的應用可以使用的內存越多,那麼這些會致使應用暫停的內存管理進程須要起做用的就越少。
(1)若是垃圾回收發生的頻率比你想的要多不少,那麼能夠在啓動JVM的時候爲其配置更大的最大堆大小值。堆被填滿的時間越久,就越能下降垃圾回收發生的頻率。最大堆大小值能夠在啓動JVM的時候,用-Xmx
參數來設定。默認的最大堆大小是被設置爲可用的操做系統內存的四分之一,或者最小1GB。
(2)若是問題出在常常從新分配內存,那麼你能夠把初始化堆大小設置爲和最大堆大小同樣。這就意味着JVM永遠不須要爲堆從新分配內存。但這樣作就會失去動態堆大小適配的優化,堆的大小從一開始就被固定下來。配置初始化對大小是在啓動JVM,用-Xms
來設定。默認初始化堆大小會被設定爲操做系統可用的物理內存的六十四分之一,或者設置一個最小值。這個值是根據不一樣的平臺來肯定的。
(3)若是你清楚是哪一種垃圾回收(minor gc或major gc)致使了性能問題,能夠在不改變整個堆大小的狀況下設定新生代和老生代的大小比例。對於須要產生大量臨時對象的應用,須要增大新生代的比例(固然,後果是減少了老生代的大小)。對於長生命週期對象較多的應用,則需增大老生代的比例(天然須要減小新生代的大小)。如下幾種方法能夠用來設定新生代和老生代的大小:
在啓動JVM時,使用-XX:NewRatio
參數來具體指定新生代和老生代的大小比例。好比,若是想讓老生代的大小是新生代的五倍,則設置參數爲-XX:NewRatio=5,默認這個參數設定爲2(即老生代佔用堆空間的三分之二,新生代佔用三分之一)。
在啓動JVM時,直接使用-Xmn
參數設定初始化和最大新生代大小,那麼堆中的剩餘大小便是老生代的大小。
在啓動JVM時,直接使用-XX:NewSize
和-XX:MaxNewSize
參數設定初始化和最大新生代大小,那麼堆中的剩餘大小便是老生代的大小。
(4)每個線程都有一個棧,用於保存函數調用、返回地址等等,這些棧有着對應的內存分配。若是線程過多,就會致使OutOfMemoryError。即便你有足夠的空間的堆來存放對象,你的應用也可能會由於建立一個新的線程而崩潰。這種狀況下,須要考慮限制線程中的棧大小的最大值。線程棧大小能夠在JVM啓動的時候,經過-Xss
參數來設置,默認這個值被設定爲320KB至1024KB之間,這和平臺相關。
當開發或運行一個Java應用的時候,對JVM的性能進行監控是很重要的。配置JVM不是一次配置就萬事大吉的,特別是你要應對的是Java服務器應用的狀況。你必須持續的檢查堆內存和非堆內存的分配和使用狀況,線程數的建立狀況和內存中加載的類的數據狀況等。這些都是核心參數。
使用Anturis控制檯,你能夠爲任何的硬件組件上運行的JVM配置監控(例如,在一臺電腦上運行的一個Tomcat網頁服務器)。
JVM監控可使用如下衡量標準:
總內存使用狀況(MB):即JVM使用的總內存。若是JVM使用了全部可用內存,這項指標能夠衡量底層操做系統的總體性能。
堆內存使用(MB):即JVM爲運行的Java應用所使用的對象分配的全部內存。不使用的對象一般會被垃圾回收器從堆中移除。因此,若是這個指數增大,表示你的應用沒有把不使用的對象移除或者你須要更好的配置垃圾回收器的參數。
非堆內存的使用(MB):即爲方法區和代碼緩存分配的全部內存。方法區是用於存儲被加載的類的引用,若是這些引用沒有被適當的清理,永生代池會在每次應用被從新部署的時候都會增大,致使非堆的內存泄露。這個指標也可能指示了線程建立的泄露。
池內總內存(MB):即JVM所分配的全部變量內存池的內存和(即除了代碼緩存區外的全部內存和)。這個指標可以讓你明確你的應用在JVM過載前所能使用的總內存。
線程:即全部有效線程數。舉個例子,在Tomcat服務器中每一個請求都是一個獨立的線程來處理,因此這個衡量指標能夠表示當前有多少個請求數,是否影響到了後臺低權限的線程的運行。
類:即全部被加載的類的總數。若是你的應用動態的建立不少類,這多是服務器內存泄露的一個緣由。