Java虛擬機學習

Java虛擬機學習

JVM

JVM是一個虛構出來的計算機,有本身的處理器,堆棧,寄存器以及相應的指令系統等。JVM是JRE的一部分,經過在實際的計算機上仿真模擬各類計算機功能,這樣就能使Java在跨平臺上運行。html

JVM內存區域劃分

圖片描述

JVM的內部體系結構分爲三個部分,分別爲類裝載器子系統,運行時數據區和執行引擎。java

類裝載器子系統(ClassLoader)

每一個Java虛擬機都有一個類加載器,負責查找並加載程序中的類,接口,並給其肯定惟一的名字。Java虛擬機有兩種類裝載器:系統類裝載器和用戶自定義類裝載器,系統類裝載器是JVM實現的一部分,用戶自定義類裝載器是Java程序的一部分,其必須是類裝載器ClassLoader類的子類。面試

圖片描述

  • 啓動類裝載器(bootstrap calss loader): 其用來加載Java的核心庫,用原生代碼來實現的,沒有繼承java.lang.ClassLoader
  • 擴展類裝載器(extensions class loader): 其用來加載Java的擴展庫,Jav虛擬機的實現會提供一個擴展庫目錄,該裝載器就是在這個目錄下查找加載類。
  • 應用程序類裝載器(application class loader): 其根據java應用的類路徑(classpath)來加載Java應用的類。經過ClassLoader.getSystemClassLoader()獲取它。
  • 用戶自定義裝載器(user class loader): 除了系統提供的類裝載器以外,咱們還能夠經過繼承java.lang.ClassLoader類的方式來實現本身的類裝載器來知足一些特殊的需求。

類裝載器子系統涉及Java虛擬機的其它組成部分和來自java.lang庫的類。ClassLoader類定義的方法爲程序提供了訪問類裝載器機制的接口。對於每一個被裝載的類型,Java虛擬機都會給它建立一個java.lang.Class類的實例來表明該類型。和其它對象同樣,用戶自定義的類裝載器以及Class類的實例放在內存的堆區,裝載的類型信息位於方法區。算法

類裝載器子系統除了要查找定位導入二進制class文件外,還須要負責驗證被導入的類的正確性,爲類的類變量分配並初始化內存,以及解析符號引用。順序是:
裝載(查找並裝載類型的二進制數據)——>鏈接(驗證:確保被導入類型的正確性,準備:爲類變量分配內存,並將其初始化爲默認值,解析:把類型中的符號引用轉換爲直接引用)——>初始化(將類變量初始化爲正確的初始值)
+----2017-12-16-----+bootstrap

1. 裝載:裝載是指將編譯後的Java類文件(.class文件)中的二進制數據讀入內存,並將其放在運行時數據區的方法區內,而後在堆區建立一個java.lang.Class對象,用其來封裝類在方法區的數據結構.即加載後最後獲得的是Class對象,該對象是單實例的,即不管這個類建立了多少個對象,他的Class對象是惟一的.經過Class.forName(類的全路徑), 實例對象.class, 實例對象getClass() 這三種能夠加載並獲取到該類的Class對象.  
  類裝載時類中的靜態代碼會被執行,例如:Class.forName()加載JDBC驅動  
2. 鏈接:靜態變量的第一次賦值----默認值  
3. 初始化:靜態變量第二次賦值----真正的初始值 類的初始化發生在Java程序對類的首次**主動使用**中,主動使用有(建立類的實例,訪問操做類或接口的靜態變量,調用類的靜態方法,反射如:Class.forName(類全路徑),初始化此類的子類,Java虛擬機啓動時被代表爲啓動類的類:java Test),除以上外其餘對類的被動使用是不會致使類的初始化.

執行引擎:執行字節碼或者執行調用的本地方法----執行引擎的行爲由指令集定義

指令集:Java方法的字節碼流由Java虛擬機的指令序列構成。每條指令包含:一個單字節的操做碼(表示須要執行的操做),0或多個操做數(操做數向Java虛擬機提供執行操做碼的額外信息,使指令使用的值可能來自當前常量池中的項,當前幀的局部變量中的值或者當前操做數棧頂端的值)。數組

運行中的Java程序的每個線程都是一個獨立的虛擬機執行引擎的實例。從線程生命週期的開始到結束,它要麼在執行字節碼,要麼在執行本地方法。數據結構

主要的執行技術有:解釋,及時編譯,自適應優化,芯片級直接執行。自適應優化吸收解釋和及時編譯的優勢,採起兩種結合的方式。
自適應優化——開始對全部的代碼都採起解釋執行的方式並監視代碼的執行狀況,而後對那些常常調用的方法啓動一個後臺線程,將其及時編譯爲本地代碼進行調用,並進行仔細優化。當該方法再也不頻繁的被調用,則取消編譯過的代碼,將其歸爲解釋執行。app


運行時數據區:方法區,堆,Java棧,PC寄存器,本地方法棧

圖片描述

  • 方法區——線程共享

當虛擬機裝載某個類型時,它使用類裝載器定位載入相應的.class文件到虛擬機中,接着虛擬機提取其中的類型信息,並將這些信息存儲到方法區。當開發人員在程序中經過Class對象的getName(),isInterface()等方法來獲取信息時,這些數據都會來源於方法區,同時方法區也是全局共享的,在必定條件下它也會被GC掉(虛擬機容許經過用戶自定義的類裝載器來動態擴展Java程序,此時方法區也能夠被垃圾回收器收集),當方法區須要使用的內存超過最大容許時,會拋出OutOfMemory。jvm

方法區存放內容以下:函數

  • 已經被虛擬機所加載的類信息(類名稱,類類型(接口仍是類),修飾符,類的直接超類名稱),
  • 類中的靜態(類)變量,
  • 運行常量池runtime constant pool (類中定義的final類型的常量,一個有序集合,包括直接常量(string,integer,floating 常量)和對其餘類型,字段,方法的符號引用)——class文件除了有類信息外,還有一項是常量池(constant pool table),常量池用於存放編譯期生成的各類字面量和符號的引用,這部份內容將在類加載後進入方法區的運行時常量池中存儲。——運行時常量池相對於class文件常量池的一個重要特徵是具有動態性:即除了能夠存儲class文件常量池中的內容外,運行期間也可能將新的常量放入到池中,好比String類的intern()方法,,參考: 深刻理解java虛擬機(三):String.intern()-字符串常量池
  • 類中的方法信息(方法名,返回類型,參數數量和類型,修飾符),
  • 字段信息(字段名,類型,修飾符),
  • 指向ClassLoader類的引用(每一個類型被裝載時,虛擬機必須跟蹤肯定它是由系統類裝載器仍是由用戶自定義裝載器裝載的),
  • 指向Class類的引用(對於每隔一個被裝載的類型,虛擬機都爲其相應的建立了一個java.lang.Class類實例)

----------

  • 堆(heap)——線程共享

堆是JVM用來存儲對象實例以及數組值(數組在Java虛擬機中是一個真正的對象)的區域,能夠認爲Java中全部經過new建立的對象的內存都在堆中分配,堆中的對象所佔的內存是須要等待GC進行回收的(JVM沒有釋放內存的指令,須要將釋放內存的任務交給垃圾收集器處理)。堆是JVM中全部線程共享的。
  • Java棧(Java stack) ——線程私有,生命週期與線程相同

每當啓動一個線程時,Java虛擬機就會爲他分配一個Java棧。Java棧由許多棧幀組成,一個棧幀包含一個對應的Java方法調用的狀態。當線程調用一個Java方法時,虛擬機壓入一個新的棧幀到Java棧中,當該方法返回時,這個棧幀就會從Java棧中彈出。

棧幀:由局部變量區,操做數棧和幀數據區組成。當虛擬機調用一個Java方法時,它從對應類的類型信息中獲得此方法的局部變量區和操做數棧大小,並根據此來分配幀的內存,而後壓入棧中。

  • 局部變量區

    局部變量區被組織爲以字長爲單位,從0開始計數的數組。字節碼指令經過從0開始的索引使用其中的數據。類型爲int, float, reference和returnAddress的值在數組中佔據一項,而類型爲byte, short和char的值在存入數組前都被轉換爲int值,也佔據一項。但類型爲long和double的值在數組中卻佔據連續的兩項。以下圖:

    圖片描述

  • 操做數棧

    與局部變量區同樣,操做數棧被組織成一個以字長爲單位的數組,其經過標準的棧操做訪問。
  • 幀數據區

    Java棧幀須要幀數據區來支持常量池的解析——(每當虛擬機要執行一個須要操做常量池數據的指令時,就會經過幀數據區中指向常量池的指針來訪問常量池),正常方法返回——(幀數據區還要幫助虛擬機處理Java方法的正常結束或異常停止。若是經過return正常結束,虛擬機必須恢復發起調用的方法的棧幀,包括設置程序計數器指向發起調用方法的下一個指令;若是方法有返回值,虛擬機須要將它壓入到發起調用的方法的操做數棧)以及異常派發機制——(爲了處理Java方法執行期間的異常退出狀況,幀數據區還保存一個對此方法異常表的引用)。
  • PC寄存器(程序計數器 program counter)——線程私有,生命週期與線程相同

每個線程都有它本身的PC寄存器,也是在該線程啓動時建立的。PC寄存器的內容老是指向下一條即將被執行的指令的地址,這裏的地址能夠是一個本地地址,也能夠是在方法區中相對於該方法的起始指令的偏移量。

若是線程執行Java方法,則PC寄存器保存的是下一條執行指令的地址。若線程執行的是本地方法,那麼此時PC寄存器的值是"undefined"。

  • 本地方法棧(native method stack)——線程私有,生命週期與線程相同

當線程調用Java方法時,虛擬機會建立一個新的棧幀並將其壓入到對應線程的Java棧。當線程調用的是本地方法時,虛擬機會保持Java棧不變,再也不向Java棧中壓入新的棧幀,虛擬機只是簡單的動態鏈接並調用指定的本地方法。

依賴於本地方法的實現,如某個JVM實現的本地方法接口使用C鏈接模型,則本地方法棧就是C棧,能夠說某線程在調用本地方法時,就進入了一個不受JVM限制的領域,也就是JVM能夠利用本地方法來動態擴展自己。



JVM垃圾回收(Generational Collecting)

GC經過肯定對象是否被活動對象引用來肯定是否收集回收該對象。

觸發GC的條件

  • Java內存不足時,GC被調用。當應用程序在運行時在運行過程當中建立新的對象,若此時內存空間不足,就會強制調用GC線程。若GC一次扔不能知足內存分配,會再次調用GC,若仍沒法知足要求,則會報錯"out of memory",Java應用中止。
  • GC在優先級最低的線程中運行,通常在應用程序空閒即沒有應用線程在運行的時候被調用。

兩個重要的方法

  • System.gc()

    使用System.gc()直接請求Java的垃圾回收。

  • finalize()

    在jvm垃圾回收以前調用的方法。之全部要使用finalize(),是存在着垃圾回收器不能處理的狀況:1) 在本地方法native method調用中,可能因爲在分配內存的時候可能採用了相似C語言的作法,而非Java的new作法,好比本地方法調用了C++的malloc()來分配內存而沒有調用free()來釋放掉內存,這時候就可能形成內存泄露。這時就能夠在finalize()方法中用本地方法調用free()來釋放掉這些特殊的內存空間。 2)又或者是打開了文件資源,這些資源不屬於垃圾回收器能回收的範圍,則須要在finalize()中調用對應的本地方法來回收文件資源。

減小GC開銷的措施

  • 不要顯式調用System.gc()。此函數建議JVM進行主GC,雖然只是建議而非必定,但不少狀況下它會觸發主GC,從而增長主GC的頻率,也即增長了間歇性停頓的次數。大大的影響系統性能。
  • 減小對臨時對象的使用。臨時對象在方法結束後會成爲垃圾,很快建立很快結束,增長了GC開銷。
  • 對象不用時最好置爲NULL。NULL對象通常都會做爲垃圾處理,把不用的對象置爲NULL有利於GC斷定垃圾效率。
  • 能用基本類型int long,就不要new Integet,new Long對象。基本類型佔用內存資源相應較小。
  • 少用靜態對象變量。靜態對象變量屬於全局變量,不會被GC回收,他們會一直佔用內存空間。
  • 字符串修改用StringBuffer,StringBuilder,不用String。
  • 避免大量集中new新對象。

對象在jvm堆區的狀態

  • 可觸及狀態:程序中還有變量引用,那麼此對象爲可觸及狀態。
  • 可復活狀態:當程序中已經沒有變量引用這個對象,那麼此對象由可觸及狀態轉爲可復活狀態。CG線程將在必定的時間準備調用此對象的finalize方法(finalize方法繼承或重寫子Object),finalize方法內的代碼有可能將對象轉爲可觸及狀態,不然對象轉化爲不可觸及狀態。
  • 不可觸及狀態:只有當對象處於不可觸及狀態時,GC線程才能回收此對象的內存。

經常使用垃圾收集器

  • 標記-清除收集器 mark-sweep
  • 複製收集器 copying
  • 標記-壓縮收集器 mark-compact
  • 分代收集器 generational

垃圾收集算法

tracing算法

基於tracing算法的垃圾回收也稱爲標記和清除(mark-sweep)垃圾收集器。
標記-清除算法分爲兩個階段:標記階段和清除階段。標記階段的任務是標記出全部的須要被回收的對象,清除階段就是回收被標記的對象所佔用的內存空間。
此算法缺陷就是很容易產生內存碎片,在產生大量的內存碎片後就可能沒法對新的大對象所須要的內存空間進行分配。

copying算法

爲了解決標記-清除算法的缺陷,coping算法是將內存按內存容量劃分爲大小相等的兩塊,每次對新對象的內存分配只使用其中的一塊。當這一塊內存用完的時候,就將在這塊內存上還存活下來的對象複製到另以空閒塊內存上面,而後把那塊已經使用的內存空間所有清理。
此算法雖然不會產生內存碎片,可是每次只能使用一半的內存空間,下降了內存實際使用率。並且當存活的對象還不少的時候,須要將它們所有複製到另外一塊內存上,這也使效率下降。

compating算法

爲了解決compying算法的缺陷而充分的利用內存空間,提出了mark-compact 算法,即標記-壓縮。該算法的標記階段和mark-sweep同樣將全部須要被回收的對象進行標記。可是標記完成後,它不是直接清理可回收對象,而是將存活的對象都向一端移動,而後清理掉存活對象邊界之外的內存空間。這樣即不會產生內碎片,也充分利用了內存空間。

generation算法

分代收集算法是目前大部分jvm的垃圾回收器所採用的算法。它的核心思想是根據對象存活的生命週期來劃分不一樣的區域,對每一個區域進行不用的垃圾回收策略。通常狀況將堆區分爲老年代(tenured generation)和新生代(young generation) ,老年代的特色是每次垃圾回收時只會有少許的對象須要被回收,而新生代的特色是每次垃圾回收時都會有大量的對象須要被回收掉。

目前大部分垃圾收集器對於新生代都採起Copying算法,由於新生代中每次垃圾回收都要回收大部分對象,也就是說須要複製的操做次數較少,可是實際中並非按照1:1的比例來劃分新生代的空間的,通常來講是將新生代劃分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的對象複製到另外一塊Survivor空間中,而後清理掉Eden和剛纔使用過的Survivor空間。

而因爲老年代的特色是每次回收都只回收少許對象,通常使用的是Mark-Compact算法。

  • 新生代:新建立的對象都存放在這裏。由於大多數對象很快變得不可達,因此大多數對象在年輕代中建立,而後消失。當對象從這塊內存區域消失時,咱們說發生了一次「minor GC」。
  • 老年代:沒有變得不可達,存活下來的年輕代對象被複制到這裏。這塊內存區域通常大於年輕代。由於它更大的規模,GC發生的次數比在年輕代的少。對象從老年代消失時,咱們說 "major GC"("full GC")。
  • 永久代(permanent generation)也稱爲「方法區(method area)」,他存儲class對象和字符串常量。因此這塊內存區域絕對不是永久的存放從老年代存活下來的對象的。在這塊內存中有可能發生垃圾回收。發生在這裏垃圾回收也被稱爲major GC。


參考來自
深刻理解Java虛擬機體系結構
什麼是JVM?
面試準備之JVM的組成、垃圾回收機制
深刻理解Java虛擬機
深刻理解JVM--JVM垃圾回收機制



分享一波阿里雲代金券快速上雲

from usthe.com

相關文章
相關標籤/搜索