前言:java
咱們天天都在編寫Java代碼,編譯,執行。不少人已經知道Java源代碼文件(.java後綴)會被Java編譯器編譯爲字節碼文件(.class後綴),而後由JVM中的類加載器加載各個類的字節碼文件,加載完畢以後,交由JVM執行引擎執行。算法
那在整個程序執行過程當中,JVM中怎麼存取數據和相關信息呢?編程
事實上在JVM中是用一段空間來存儲程序執行期間須要用到的數據和相關信息,這段空間通常被稱做爲Runtime Data Area(運行時數據區),也就是咱們常說的JVM內存。數組
1、運行時數據區域包括哪些?多線程
根據《Java虛擬機規範》的規定,運行時數據區一般包括這幾個部分:程序計數器(Program Counter Register)、Java虛擬機棧(Java Vitual Machine Stack)、本地方法棧(Native Method Stack)、方法區(Method Area)、堆(Heap)。編程語言
2、各個部分存儲的信息和負責的職能函數
一、程序計數器性能
這個內存區域是Java虛擬機規範中惟一一個沒有規定任何OOM(OutOfMemoryError)狀況的區域,這是這個區域最大的特色之一,這是由於程序計數器中存儲的數據所佔空間的大小不會隨程序的執行而發生改變,所以,對於程序計數器是不會發生內存溢出現象(OutOfMemory)的。spa
這個區域主要是負責記錄正在執行的虛擬機字節碼指令地址,即當前線程執行的字節碼的行號指示器(注意:JVM不是直接執行Java代碼,而是執行.class文件,因此只要其餘編程語言能翻譯成.class文件同樣能放入JVM中執行)。JVM會給每一個線程一個獨立的程序計數器,計數器之間互不影響,且經過線程輪流切換而且分配處理器執行時間來實現JVM的多線程。不過當線程執行的是Native方法的時候這個計數器中的值爲undefined。線程
二、Java虛擬機棧
和程序計數器同樣的是Java虛擬機棧是線程私有,生命週期和線程相同。虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的時候都會建立棧幀,用來存儲局部變量表,操做數棧,動態連接,方法出口等信息,每一個方法從調用到執行完成的過程,就對應一個棧幀在虛擬機中入棧到出棧的過程,其中64位長度的long和double類型的數據會佔用2個局部變量空間,其他的數據類型只佔用1個。這裏須要理解一下的就是爲何要用棧這個結構呢,好比A方法中調用了B方法,虛擬機中是先讓A方法的棧幀進入虛擬機棧執行,當執行到調用B方法的語句就讓B棧幀進入,執行完以後B棧幀就出棧,A棧就繼續執行。這裏注意的是若是遞歸的方法遞歸的太深很容易拋出下面兩種異常,因此遞歸雖然寫起來方便,可是性能會有所降低,而且容易拋出異常。
Java虛擬機規範中,對這個區域規定了兩種異常情況
i. 線程請求棧的深度大於虛擬機所容許棧的深度,將拋出Stack Overflow Error
ii. 若是虛擬機棧能夠動態擴展且擴展時沒法申請到足夠的內存,會拋出OutOfMemoryError
三、本地方法棧
與虛擬機棧做用類似,不過是虛擬機棧爲虛擬機執行Java方法提供,而本地方法爲虛擬機使用到的Native方法服務,Native方法可能是用C++寫的。拋出的異常和虛擬機棧相同。
四、Java堆
Java堆是與前面的區域不一樣的是:這個區域是被全部線程共享的一塊內存區域,用來存放對象實例,併爲對象實例分配好內存。Java虛擬機規範中這樣描述:全部對象實例以及數組都要在堆上分配Java堆也是垃圾收集器管理的主要區域,也叫」GC堆「。因爲如今的垃圾回收算法可能是分代收集,因此Java堆裏面又可分爲:新生代和老年代。而且根據Java虛擬機規範的規定:Java堆能夠處於物理上不連續的內存空間中,只要邏輯上連續便可。有實例沒有被分配,且堆沒法再擴展的時候會拋出OutOfMemoryError異常,虛擬機調優其實也主要關注的是這個區域。
五、方法區
與Java堆同樣,線程共享,用來存儲被虛擬機加載的類信息,常量,靜態變量。這個區域Java虛擬機規範對其特別寬鬆,既能夠像Java堆那樣不須要連續內存,又能夠選擇固定大小和可擴展。還能夠選擇不實現垃圾收集,這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載。當沒法知足內存分配需求時,將拋出OutOfMemoryError異常。
目前虛擬機Hotspot已經將這部分存儲空間從使用JVM內存換成使用本地內存,即這部分再也不叫永久代,而是元空間。這個元空間其實是JVM動態規定內存大小。這個替換有什麼優點呢?由於字符串常量池是存在永久代中,很容易出現性能問題,而且類和方法信息大小難肯定,給永久代的的大小指定帶來困難,並且GC會對永久代特殊處理,這就增長了GC的複雜性。從JDK1.7開始,字符串常量池就劃分進了堆中,其餘的更可能是元空間在內存劃分的算法上更趨於合理
六、運行時常量池
是方法區的一部分。用於存放編譯期生成的各類字面量和符號引用,同時也會把翻譯出來的直接引用也存儲在運行時的常量池中,具備動態性。常量不必定只有編譯期才能產生,運行期間也能夠將新的常量放入池中。例如String的Intern()方法。一樣拋出OutOfMemoryError異常
3、直接內存
這個區域並非屬於運行時數據區域,可是這個區域也會被頻繁使用,而且拋出OOM異常。這個區域主要是因爲在JDK1.4中新加入了NIO(New Input/Output)類,引入了一種基於通道與緩衝區的I/O方式,它可使用Native函數庫直接分配堆外內存,經過一個儲存在Java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做。這樣能避免在Java堆和Native堆中來回複製數據,從而在一些場景中顯著提升性能。直接內存分配不會受到Java堆大小的限制,會受到本機總內存大小及處理器尋址空間的限制。會拋出OutOfMemoryError異常
4、總結
只有程序計數器不會報出任何相關OOM異常,而Java虛擬機棧有可能會報出OOM或Stack Overflow異常。Java虛擬機棧主要是存儲方法的一些信息,能讓方法順利的執行,而Java堆存儲的是對象的信息。虛擬機的垃圾回收算法主要在這一塊,而且日常調優的區域也是在這一塊。