日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

虛擬機運行機制

JVM運行在操作系統(tǒng)上,不與硬件設(shè)備直接交互。

JAVA程序執(zhí)行流程:Java源代碼文件( Hello·java)被編譯器編譯成字節(jié)碼文件( Hello·class),字節(jié)碼文件被JVM中的解釋器編譯成機器碼在不同操作系統(tǒng)上運行。

Java程序具體運行過程:

 

  1. Java源文件被編譯成字節(jié)碼文件
  2. JVM將字節(jié)碼文件通過JVM內(nèi)部解釋器編譯成與操作系統(tǒng)對應(yīng)的機器碼
  3. 機器碼調(diào)用相應(yīng)操作系統(tǒng)的Native Method庫執(zhí)行相應(yīng)方法

 

Java跨平臺的原因:每種操作系統(tǒng)的解釋器都是不同的,但基于解釋器實現(xiàn)的虛擬機是相同的。

虛擬機實例生命周期描述:在一個Java進程開始運行后,虛擬機就開始實例化,有多個進程啟動就會實例化多個虛擬機實例。進程退出或關(guān)閉,則虛擬機實例銷毀,在多個虛擬機實例之間不能共享數(shù)據(jù)。

虛擬機內(nèi)部結(jié)構(gòu)

JVM包括一個類加載器子系統(tǒng)(Class Loader Subsystem)、運行時數(shù)據(jù)區(qū)(Runtime Data Area)、執(zhí)行引擎(Engine)和本地接口庫(Nateive Interface Library)。

本地接口庫調(diào)用本地方法庫(Native Method Library)與OS交互。

JVM內(nèi)部結(jié)構(gòu)圖如下:

其中:

 

  • 類加載器子系統(tǒng)用于將編譯好的字節(jié)碼文件( ·class)加載到JVM。
  • 運行時數(shù)據(jù)區(qū)用于存儲在JVM運行過程中產(chǎn)生的數(shù)據(jù),包括程序計數(shù)器(PC)、方法區(qū)(Method Area)、本地方法區(qū)(Native Method Area)、虛擬機棧(JVM Stack)、虛擬機堆(JVM Heap)。
  • 執(zhí)行引擎包括即時編譯器(JIT Compiler)和垃圾回收器(Garbage Collection)。
  • 即時編譯器用于將字節(jié)碼編譯成具體的機器碼。
  • 垃圾回收器用于管理內(nèi)存,回收在運行過程中不再使用的對象。
  • 本地接口庫用于調(diào)用操作系統(tǒng)的本地方法庫完成具體的指令操作。

 

虛擬機與多線程

多核操作系統(tǒng)上,JVM允許在一個進程內(nèi)同時并發(fā)執(zhí)行多個線程。

JVM中的線程與操作系統(tǒng)中的線程是相互對應(yīng)的。

JVM線程的調(diào)度是交給操作系統(tǒng)負責(zé)的。

 

有些程序語言有協(xié)程的概念,如Golang的并發(fā),協(xié)程可以粗略地看成是輕量級的線程,一個協(xié)程并非對應(yīng)一個操作系統(tǒng)線程,而是多個協(xié)程對應(yīng)一個操作系統(tǒng)線程,協(xié)程之間通過調(diào)度器協(xié)調(diào)。
這種設(shè)計有以下的好處:
輕量級,資源占用較少。
調(diào)度是基于語言層面,減少操作系統(tǒng)線程調(diào)度的開銷。

 

從JVM角度看,Java線程的執(zhí)行流程:

 

  1. 準備:完成JVM線程的本地存儲、緩沖區(qū)分配、同步對象、棧、程序計數(shù)器等初始化工作。
  2. 創(chuàng)建:調(diào)用操作系統(tǒng)接口創(chuàng)建一個與之對應(yīng)的原生線程。
  3. 調(diào)度:操作系統(tǒng)負責(zé)調(diào)度所有線程,并為其分配CPU時間片。
  4. 執(zhí)行:在原生線程初始化完畢時,調(diào)用Java線程的 run()方法執(zhí)行線程邏輯。
  5. 結(jié)束:在 run()方法邏輯執(zhí)行完畢后,釋放原生線程和Java線程所對應(yīng)的資源。
  6. 回收:在Java線程運行結(jié)束時,原生線程隨之被回收。

 

在JVM后臺運行的線程主要有:

 

  • 虛擬機線程(JVM Thread):虛擬機線程在JVM到達安全點(Safe Point)時出現(xiàn)。
  • 周期性任務(wù)線程:通過定時器調(diào)度線程來實現(xiàn)周期性操作的執(zhí)行。
  • GC線程:GC線程支持JVM中不同的垃圾回收活動。
  • 編譯器線程:編譯器線程在運行時將字節(jié)碼動態(tài)編譯成本地平臺機器碼,是JVM跨平臺的具體實現(xiàn)。
  • 信號分發(fā)線程:接收發(fā)送到JVM的信號并調(diào)用JVM方法。

 

虛擬機內(nèi)存區(qū)域

JVM的內(nèi)存區(qū)域分為:

 

  • 線程私有區(qū)域:線程私有區(qū)域包括的組件有程序計數(shù)器、虛擬機棧、本地方法區(qū)。
  • 線程共享區(qū)域:虛擬機堆、方法區(qū)。
  • 直接內(nèi)存

 

線程私有區(qū)域的生命周期與線程相同,隨線程的啟動而創(chuàng)建,隨線程的結(jié)束而銷毀。

在JVM中,每個Java線程與操作系統(tǒng)本地線程直接映射,因此這部分內(nèi)存區(qū)域的存在與否和本地線程的啟動和銷毀對應(yīng)。

線程共享區(qū)域隨虛擬機的啟動而創(chuàng)建,隨虛擬機的關(guān)閉而銷毀。

直接內(nèi)存又稱為對外內(nèi)存,直接內(nèi)存不是JVM運行時數(shù)據(jù)區(qū)的一部分,但在并發(fā)編程中被頻繁調(diào)用。

 

JDK的NIO模塊提供的基于 Channel與 Buffer的I/O操作就是基于堆外內(nèi)存實現(xiàn)的,NIO模塊通過調(diào)用Native Method Library直接在操作系統(tǒng)上分配堆外內(nèi)存,然后使用 DirectByteBuffer對象作為這塊內(nèi)存的引用對內(nèi)存進行操作,Java進程可以通過堆外內(nèi)存技術(shù)避免在Java堆和Native堆中來回復(fù)制數(shù)據(jù)帶來的資源占用和性能消耗,因此堆外內(nèi)存在高并發(fā)應(yīng)用場景下被廣泛使用?.NETty、Flink、HBase、Hadoop都有用到堆外存內(nèi)存)。

 

程序計數(shù)器

程序計數(shù)器(PC)屬于線程私有區(qū)域,程序計數(shù)器是唯一無內(nèi)存溢出(Out of Memory)問題的區(qū)域。

程序計數(shù)器是一塊很小的內(nèi)存空間,用于存儲當(dāng)前運行的線程所執(zhí)行的字節(jié)碼的行號指示器。

每個運行中的線程都有一個獨立的程序計數(shù)器,在方法正在執(zhí)行時,該方法的程序計數(shù)器記錄的是實時虛擬機字節(jié)碼指令的地址。

注:如果該方法執(zhí)行的是 native方法,則程序計數(shù)器的值為空(Undefined)。

虛擬機棧

虛擬機棧(JVM Stack)屬于線程私有區(qū)域,描述Java方法的執(zhí)行過程。

虛擬機棧是描述Java方法的執(zhí)行過程的內(nèi)存模型,它在當(dāng)前棧幀(Stack Frame)中主要存儲了以下信息:

 

  • 局部變量表
  • 操作數(shù)棧
  • 動態(tài)鏈接
  • 方法出口

 

同時,棧幀用來存儲部分運行時數(shù)據(jù)及其數(shù)據(jù)結(jié)構(gòu),處理動態(tài)鏈接(Dynamic Linking)方法的返回值和異常分派(Dispatch Exception)。

棧幀:棧幀用來記錄方法的執(zhí)行過程。

 

  • 方法被執(zhí)行時,虛擬機會為其創(chuàng)建一個與之對應(yīng)的棧幀。
  • 虛擬機棧中的入棧操作:方法的執(zhí)行。
  • 虛擬機棧中的出棧操作:方法的返回。
  • 無論方法是正常運行完成,還是異常(拋出了在方法內(nèi)未被捕獲的異常)退出,都認為方法運行結(jié)束。

 

線程運行及棧幀的變化過程如下:

線程1在CPU1上運行,線程2在CPU2上運行,在CPU資源不夠時其他線程將處于等待狀態(tài)(圖中的線程N),等待獲取CPU時間片。而在線程內(nèi)部,每個方法的執(zhí)行和返回都對應(yīng)一個棧幀的入棧和出棧,每個運行中的線程當(dāng)前只有一個棧幀處于活動狀態(tài)。

本地方法區(qū)

本地方法區(qū)(Native Method Area)和虛擬機棧的作用類似,區(qū)別是虛擬機棧是為執(zhí)行Java方法服務(wù)的,本地方法區(qū)是為Native方法服務(wù)的。

虛擬機堆

虛擬機堆(JVM Heap),也稱為運行時數(shù)據(jù)區(qū),虛擬機堆是線程共享的。

在JVM運行過程中創(chuàng)建的對象和產(chǎn)生的數(shù)據(jù)都被存儲在堆中,堆是被線程共享的內(nèi)存區(qū)域,也是垃圾回收器進行垃圾回收的最主要的內(nèi)存區(qū)域。

由于現(xiàn)代JVM采用分代收集算法,因此Java堆從GC(Garbage Collection,垃圾回收)的角度還可以細分為:新生代、老年代和永久代。

方法區(qū)

方法區(qū)(Method Area),也被稱為永久代,用于存儲常量、靜態(tài)變量、類信息、JIT編譯后的機器碼、運行時常量池等數(shù)據(jù)。

JVM把GC分代收集擴展至方法區(qū),即使用Java堆的永久分代來實現(xiàn)方法區(qū),這樣JVM的垃圾收集器就可以像管理Java堆一樣管理這部分內(nèi)存。永久帶的內(nèi)存回收主要針對常量池的回收和類的卸載,因此可回收的對象很少。

常量被存儲在運行時常量池(Runtime Constant Pool)中,是方法區(qū)的一部分。靜態(tài)變量也屬于方法區(qū)的一部分。在類信息(Class文件)中不但保存了類的版本、字段、方法、接口等描述信息,還保存了常量信息。

在即時編譯后,代碼的內(nèi)容將在執(zhí)行階段(類加載完成后)被保存在方法區(qū)的運行時常量池中。Java虛擬機對Class文件每一部分的格式都有明確的規(guī)定,只有符合JVM規(guī)范的Class文件才能通過虛擬機的檢查,然后被裝載、執(zhí)行。

虛擬機運行時內(nèi)存

JVM的運行時內(nèi)存也叫做JVM堆,從GC的角度可以將JVM堆分為新生代、老年代和永久代。

其中,新生代默認占1/3堆空間,老年代默認占2/3堆空間。

新生代又分為Eden區(qū)、ServivorFrom和ServivorTo區(qū)。

 

  • Eden區(qū)默認占8/10新生代空間。
  • ServivorForm區(qū)和ServivorTo區(qū)默認分別占1/10新生代空間。

 

JVM堆分代分區(qū)的結(jié)構(gòu)如下:

JVM新創(chuàng)建的對象(除了大對象)都會被存放在新生代,默認占1/3堆內(nèi)存空間。

由于JVM會頻繁創(chuàng)建對象,所以新生代會頻繁出發(fā)MinorGC進行垃圾回收。

新生代

新生代分為Eden區(qū)(8/10新生代空間)、ServivorFrom區(qū)(1/10新生代空間)、ServivorTo區(qū)(1/10新生代空間)。

Eden區(qū):Java新創(chuàng)建的對象首先會被存放在Eden區(qū),如果新創(chuàng)建的對象屬于大對象,則直接將其分配到老年代。

 

  • 大對象的定義和具體的JVM版本、堆大小和垃圾回收策略有關(guān),一般為 2KB~128KB,可通過 XX:PretenureSizeThreshold設(shè)置其大小。
  • 在Eden區(qū)的內(nèi)存空間不足時會觸發(fā)MinorGC,對新生代進行一次垃圾回收。

 

ServivorTo區(qū):保留上一次MinorGC時的幸存者。

ServivorFrom區(qū):將上一次MinorGC時的幸存者作為這一次MinorGC的被掃描者。

新生代的GC過程叫做MinorGC,采用復(fù)制算法實現(xiàn),具體過程如下:

 

  1. 將Eden區(qū)和ServivorFrom區(qū)中存活的對象復(fù)制到ServivorTo區(qū),如果某對象的年齡達到老年代的標準,則將其復(fù)制到老年代,同時將這些對象年齡加1。
  • 對象晉升老年代的標準由 XX:MaxTenuringThreshold設(shè)置,默認為 15。
  • 如果ServivorTo區(qū)的內(nèi)存空間不夠,則也直接將其復(fù)制到老年代。
  • 如果對象屬于大對象,也可直接將其復(fù)制到老年代。
  1. 清空Eden區(qū)和ServivorFrom區(qū)對象。
  2. 將ServivorTo區(qū)和ServivorFrom區(qū)互換,原來的ServivorTo區(qū)成為下一次GC時的ServivorFrom區(qū)。

 

老年代

老年代主要存放長生命周期對象和大對象。

老年代的GC過程叫做MajorGC,在老年代,對象比較穩(wěn)定,MajorGC不會被頻繁觸發(fā)。

在進行MajorGC之前,JVM會進行一次MinorGC,在MinorGC過后仍然出現(xiàn)老年代空間不足或無法找到足夠大的連續(xù)內(nèi)存空間分配給新創(chuàng)建的大對象時,會觸發(fā)MajorGC進行垃圾回收活動,釋放JVM的內(nèi)存空間。

MajorGC采用標記清除算法,該算法首先會掃描所以對象并標記存活的對象,然后回收未被標記的對象,并釋放內(nèi)存空間。

因為要先掃描老年代的所有對象再回收,所以MajorGC的耗時比較長,MajorGC的標記清除算法容易產(chǎn)生內(nèi)存碎片。

在老年代沒有內(nèi)存空間可分配時,會拋出OOM異常。

永久代

永久代指內(nèi)存的永久保存區(qū)域,主要存放 Class和 Meta(元數(shù)據(jù))的信息。

Class在類加載時被放入永久代碼。

永久代和老年代、新生代不同,GC不會在程序運行期間對永久代的內(nèi)存進行清理,這也導(dǎo)致了永久代的內(nèi)存會隨著加載的Class文件的增加而增加,在加載的Class文件過多時會拋出Out OfMemory異常,比如Tomcat引用Jar文件過多導(dǎo)致JVM內(nèi)存不足而無法啟動。

需要注意的是,在Java 8中永久代已經(jīng)被元數(shù)據(jù)區(qū)(也叫作元空間)取代。

元數(shù)據(jù)區(qū)的作用和永久代類似,二者最大的區(qū)別在于:元數(shù)據(jù)區(qū)并沒有使用虛擬機的內(nèi)存,而是直接使用操作系統(tǒng)的本地內(nèi)存。

因此,元空間的大小不受JVM內(nèi)存的限制,只和操作系統(tǒng)的內(nèi)存有關(guān)。

在Java 8中,JVM將類的元數(shù)據(jù)放入本地內(nèi)存(Native Memory)中,將常量池和類的靜態(tài)變量放入Java堆中,這樣JVM能夠加載多少元數(shù)據(jù)信息就不再由JVM的最大可用內(nèi)存(MaxPermSize)空間決定,而由操作系統(tǒng)的實際可用內(nèi)存空間決定。

垃圾回收與算法

確定垃圾

Java采用引用計數(shù)法和可達性分析來確定對象是否需要被回收。

 

  • 引用計數(shù)法容易產(chǎn)生循環(huán)引用問題。
  • 可達性分析通過根搜索算法(GC Roots Tracing)來實現(xiàn)。
根搜索算法以一系列GC Roots的點作為起點向下搜索,在一個對象到任何 GC Roots都沒有引用鏈相連時,說明其已死亡。
  • 根搜索算法主要針對棧中的引用、方法區(qū)中的靜態(tài)引用和JNI中的引用展開分析。

 

引用計數(shù)法

在Java中如果要操作對象,就必須先獲取該對象的引用,因此可以通過引用計數(shù)法來判斷一個對象是否可以被回收。

在為對象添加一個引用時,引用計數(shù)加1;在為對象刪除一個引用時,引用計數(shù)減1;如果一個對象的引用計數(shù)為0,則表示此刻該對象沒有被引用,可以被回收。

引用計數(shù)法容易產(chǎn)生循環(huán)引用問題。

循環(huán)引用指兩個對象相互引用,導(dǎo)致它們的引用一直存在,而不能被回收。

可達性分析

為了解決引用計數(shù)法的循環(huán)引用問題,Java還采用了可達性分析來判斷對象是否可以被回收。

可達性分析的過程:

 

  • 首先定義一些GC Roots對象,然后以這些GC Roots對象作為起點向下搜索,如果在GC roots和一個對象之間沒有可達路徑,則稱該對象是不可達的。
  • 不可達對象要經(jīng)過至少兩次標記才能判定其是否可以被回收,如果在兩次標記后該對象仍然是不可達的,則將被垃圾收集器回收。

 

常用垃圾回收算法

Java中常用的垃圾回收算法有:

 

  • 標記清除(Mark-Sweep)算法
  • 復(fù)制(Copying)算法
  • 標記整理(Mark Compact)算法
  • 分代收集(Generational Collecting)算法

 

標記清除算法

標記清除算法是基礎(chǔ)的垃圾回收算法,其過程分為標記和清除階段。

在標記階段標記所以需要回收的對象,在清除階段清除可回收的對象并釋放其所占用的內(nèi)存空間。

由于標記清除算法在清理對象所占用的內(nèi)存空間后并沒有重新整理可用的內(nèi)存空間,因此如果內(nèi)存中可被回收的小對象居多,則會引起內(nèi)存碎片化的問題,繼而引起大對象無法獲得連續(xù)可用空間的問題。

復(fù)制算法

復(fù)制算法是為了解決標記清除算法內(nèi)存碎片化的問題而設(shè)計的。

復(fù)制算法的基本原理:

 

  • 首先將內(nèi)存劃分為兩塊大小相等的內(nèi)存區(qū)域,即區(qū)域1和區(qū)域2,新生成的對象都被存放在區(qū)域1中。
  • 在區(qū)域1內(nèi)的對象存儲滿后會對區(qū)域1進行一次標記,并將標記后仍然存活的對象全部復(fù)制到區(qū)域2中,這時區(qū)域1將不存在任何存活的對象,直接清理整個區(qū)域1的內(nèi)存即可。

 

復(fù)制算法的內(nèi)存清理效率高且易于實現(xiàn),但由于同一時刻只有一個內(nèi)存區(qū)域可用,即可用的內(nèi)存空間被壓縮到原來的一半,因此存在大量的內(nèi)存浪費。

同時,在系統(tǒng)中有大量長時間存活的對象時,這些對象將在內(nèi)存區(qū)域1和內(nèi)存區(qū)域2之間來回復(fù)制而影響系統(tǒng)的運行效率。

因此,該算法只在對象為“朝生夕死”狀態(tài)時運行效率較高。

標記整理算法

標記整理算法結(jié)合了標記清除算法和復(fù)制算法的優(yōu)點,其標記階段和標記清除算法的標記階段相同,在標記完成后將存活的對象移到內(nèi)存的另一端,然后清除該端的對象并釋放內(nèi)存。

分代收集算法

無論是標記清除算法、復(fù)制算法還是標記整理算法,都無法對所有類型(長生命周期、短生命周期、大對象、小對象)的對象都進行垃圾回收。

因此,針對不同的對象類型,JVM采用了不同的垃圾回收算法,該算法被稱為分代收集算法。

分代收集算法根據(jù)對象的不同類型將內(nèi)存劃分為不同的區(qū)域,JVM將堆劃分為新生代和老年代。

新生代主要存放新生成的對象,其特點是對象數(shù)量多但是生命周期短,在每次進行垃圾回收時都有大量的對象被回收。

老年袋主要存放大對象和生命周期長的對象,因此可回收的對象相對較少。

因此,JVM根據(jù)不同的區(qū)域?qū)ο蟮奶攸c選擇了不同的算法。

目前,大部分JVM在新生代都采用了復(fù)制算法,因為在新生代中每次進行垃圾回收時都有大量的對象被回收,需要復(fù)制的對象(存活的對象)較少,不存在大量的對象在內(nèi)存中被來回復(fù)制的問題,因此采用復(fù)制算法能安全、高效地回收新生代大量的短生命周期的對象并釋放內(nèi)存。

JVM將新生代進一步劃分為一塊較大的Eden區(qū)和兩塊較小的Servivor區(qū),Servivor區(qū)又分為ServivorFrom區(qū)和ServivorTo區(qū)。

JVM在運行過程中主要使用Eden區(qū)和ServivorFrom區(qū),進行垃圾回收時會將在Eden區(qū)和ServivorFrom區(qū)中存活的對象復(fù)制到ServivorTo區(qū),然后清理Eden區(qū)和ServivorFrom區(qū)的內(nèi)存空間。

老年代主要存放生命周期較長的對象和大對象,因而每次只有少量非存活的對象被回收,因而在老年代采用標記清除算法。

在JVM中還有一個區(qū)域,即方法區(qū)的永久代,永久代用來存儲Class類、常量、方法描述等。

在永久代主要回收廢棄的常量和無用的類。

JVM內(nèi)存中的對象主要被分配到新生代的Eden區(qū)和ServivorFrom區(qū),在少數(shù)情況下會被直接分配到老年代。

在新生代的Eden區(qū)和ServivorFrom區(qū)的內(nèi)存空間不足時會觸發(fā)一次GC,該過程被稱為MinorGC。

在MinorGC后,在Eden區(qū)和ServivorFrom區(qū)中存活的對象會被復(fù)制到ServivorTo區(qū),然后Eden區(qū)和ServivorFrom區(qū)被清理。

如果此時在ServivorTo區(qū)無法找到連續(xù)的內(nèi)存空間存儲某個對象,則將這個對象直接存儲到老年代。

若Servivor區(qū)的對象經(jīng)過一次GC后仍然存活,則其年齡加1。

在默認情況下,對象在年齡達到15歲時,將被移到老年代。

引用類型

在Java中,一切皆對象,對象的操作是通過該對象的引用(Reference)實現(xiàn)的。

在Java中,引用類型有4種:

 

  • 強引用
  • 軟引用
  • 弱引用
  • 虛引用

 

強引用

在Java中最常見的就是強引用。

強引用:在把一個對象賦給一個引用變量時,這個引用變量就是一個強引用。

有強引用的對象一定為可達性狀態(tài),所以不會被垃圾回收機制回收。

因此,強引用是造成Java內(nèi)存泄漏(Memory Link)的主要原因。

軟引用

軟引用:軟引用通過 SoftReference類實現(xiàn)。

如果一個對象只有軟引用,則在系統(tǒng)內(nèi)存空間不足時該對象將被回收。

弱引用

弱引用:弱引用通過 WeakReference類實現(xiàn)。

如果一個對象只有弱引用,則在垃圾回收過程中一定會被回收。

虛引用

虛引用:虛引用通過 PhantomReference類實現(xiàn)。

虛引用和引用隊列聯(lián)合使用,主要用于跟蹤對象的垃圾回收狀態(tài)。

分代收集算法

JVM根據(jù)對象存活周期的不同將內(nèi)存劃分為新生代、老年代和永久代,并根據(jù)各年代的特點分別采用不同的GC算法。

新生代與復(fù)制算法

新生代主要存儲短生命周期的對象,因此在垃圾回收的標記階段會標記大量已死亡的對象及少量存活的對象,因此只需要選用復(fù)制算法將少量存活的對象復(fù)制到內(nèi)存的另一端并清理原區(qū)域的內(nèi)存即可。

老年代與標記整理算法

老年代主要存放長生命周期的對象和大對象,可回收的對象一般較少,因此JVM采用標記整理算法進行垃圾回收,直接釋放死亡狀態(tài)的對象所占用的內(nèi)存空間即可。

分區(qū)收集算法

分區(qū)算法將整個堆空間劃分為連續(xù)的大小不同的小區(qū)域,對每個小區(qū)都單獨進行內(nèi)存使用和垃圾回收,這樣做的好處是可以根據(jù)每個小區(qū)域內(nèi)存的大小靈活使用和釋放內(nèi)存。

分區(qū)收集算法可以根據(jù)系統(tǒng)可接受的停頓時間,每個都快速回收若干個小區(qū)域的內(nèi)存,以縮短垃圾回收系統(tǒng)停頓的時間,最后以多次并行累加的方式逐步完成整個內(nèi)存區(qū)域的垃圾回收。

如果垃圾回收機制一次回收整個堆內(nèi)存,則需要更長的系統(tǒng)停頓時間,長時間的系統(tǒng)停頓將影響系統(tǒng)運行的穩(wěn)定性。

垃圾收集器

Java堆內(nèi)存分為新生代和老年代。

新生代主要存儲短生命周期的對象,適合使用復(fù)制算法進行垃圾回收。

老年袋主要存儲長生命周期對象和大對象,適合使用標記整理算法進行垃圾回收。

JVM針對新生代和老年代分別提供了多種不同的垃圾收集器,針對新生代提供的垃圾收集器有 SerialOld、 ParallelOld、 CMS,還有針對不同區(qū)域的 G1分區(qū)收集算法。

Serial

Serial:單線程、復(fù)制算法。

Serial垃圾收集器基于復(fù)制算法實現(xiàn),它是一個單線程收集器,在它正在進行垃圾收集時,必須暫停其他工作線程,直到垃圾收集結(jié)束。

Serial垃圾收集器采用了復(fù)制算法,簡單、高效,對于單CPU運行環(huán)境來說,沒有線程交互開銷,可以獲得最高的單線程垃圾收集效率,因此Serial垃圾收集器是JVM運行在Client模式下的新生代的默認垃圾收集器。

ParNew

ParNew:多線程、復(fù)制算法。

ParNew垃圾收集器是Serial垃圾收集器的多線程實現(xiàn),同樣采用了復(fù)制算法,它采用多線程模式工作,除此之外和Serial收集器幾乎一樣。

ParNew垃圾收集器在垃圾收集過程中會暫停所有其他工作線程,是Java虛擬機運行在Server模式下的新生代的默認垃圾收集器。

ParNew垃圾收集器默認開啟與CPU同等數(shù)量的線程進行垃圾回收,在Java應(yīng)用啟動時可通過 -XX:ParallelGCThreads參數(shù)調(diào)節(jié)ParNew垃圾收集器的工作線程數(shù)。

Parallel Scavenge

Parallel Scavenge:多線程、復(fù)制算法。

Parallel Scavenge收集器是為提高新生代垃圾收集效率而設(shè)計的垃圾收集器,基于多線程復(fù)制算法實現(xiàn),在系統(tǒng)吞吐量上有很大的優(yōu)化,可以更高效地利用CPU盡快完成垃圾回收任務(wù)。

Parallel Scavenge通過自適應(yīng)調(diào)節(jié)策略提高系統(tǒng)吞吐量,提供了三個參數(shù)用于調(diào)節(jié)、控制垃圾回收的停頓時間及吞吐量,分別是控制最大垃圾收集停頓時間的 -XX:MaxGCPauseMillis參數(shù),控制吞吐量大小的 -XX:GCTimeRatio參數(shù)和控制自適應(yīng)調(diào)節(jié)策略開啟與否的 UseAdaptiveSizePolicy參數(shù)。

Serial Old

Serial Old:單線程、標記整理算法。

Serial Old垃圾收集器是Serial垃圾收集器的老年代實現(xiàn),同Serial一樣采用單線程執(zhí)行,不同的是,Serial Old針對老年代長生命周期的特點基于標記整理算法實現(xiàn)。

Serial Old垃圾收集器是JVM運行在Client模式下的老年代的默認垃圾收集器。

新生代的Serial垃圾收集器和老年代的Serial Old垃圾收集器可搭配使用,分別針對JVM的新生代和老年代進行垃圾回收,其垃圾收集過程如圖所示。

在新生代采用Serial垃圾收集器基于復(fù)制算法進行垃圾回收,未被其回收的對象在老年代被Serial Old垃圾收集器基于標記整理算法進行垃圾回收。

Parallel Old

Parallel Old:多線程、標記整理算法。

Parallel Old垃圾收集器采用多線程并發(fā)進行垃圾回收,它根據(jù)老年代長生命周期的特點,基于多線程的標記整理算法實現(xiàn)。

Parallel Old垃圾收集器在設(shè)計上優(yōu)先考慮系統(tǒng)吞吐量,其次考慮停頓時間等因素,如果系統(tǒng)對吞吐量的要求較高,則可以優(yōu)先考慮新生代的Parallel Scavenge垃圾收集器和老年代的Parallel Old垃圾收集器的配合使用。

新生代的Parallel Scavenge垃圾收集器和老年代的Parallel Old垃圾收集器的搭配運行過程如圖。

新生代基于Parallel Scavenge垃圾收集器的復(fù)制算法進行垃圾回收,老年代基于Parallel Old垃圾收集器的標記整理算法進行垃圾回收。

CMS

CMS(Concurrent Mark Sweep)垃圾收集器是為老年代設(shè)計的垃圾收集器,其主要目的是達到最短的垃圾回收停頓時間,基于線程的標記清除算法實現(xiàn),以便在多線程并發(fā)環(huán)境下以最短的垃圾收集停頓時間提高系統(tǒng)的穩(wěn)定性。

CMS的工作機制相對復(fù)雜,垃圾回收過程包含如下4個步驟。

 

  1. 初始標記:只標記和GC Roots直接關(guān)聯(lián)的對象,速度很快,需要暫停所有工作線程。
  2. 并發(fā)標記:和用戶線程一起工作,執(zhí)行GC Roots跟蹤標記過程,不需要暫停工作線程。
  3. 重新標記:在并發(fā)標記過程中用戶線程繼續(xù)運行,導(dǎo)致在垃圾回收過程中部分對象的狀態(tài)發(fā)生變化,為了確保這部分對象的狀態(tài)正確性,需要對其重新標記并暫停工作線程。
  4. 并發(fā)清除:和用戶線程一起工作,執(zhí)行清除GC Roots不可達對象的任務(wù),不需要暫停工作線程。

 

CMS垃圾收集器在和用戶線程一起工作時(并發(fā)標記和并發(fā)清除)不需要暫停用戶線程,有效縮短了垃圾回收時系統(tǒng)的停頓時間,同時由于CMS垃圾收集器和用戶線程一起工作,因此其并行度和效率也有很大提升。

G1

G1(Garbage First)垃圾收集器為了避免全區(qū)域垃圾收集引起的系統(tǒng)停頓,將堆內(nèi)存劃分為大小固定的幾個獨立區(qū)域,獨立使用這些區(qū)域的內(nèi)存資源并且跟蹤這些區(qū)域的垃圾收集進度,同時在后臺維護一個優(yōu)先級列表,在垃圾回收過程中根據(jù)系統(tǒng)允許的最長垃圾收集時間,優(yōu)先回收垃圾最多的區(qū)域。

G1垃圾收集器通過內(nèi)存區(qū)域獨立劃分使用和根據(jù)不同優(yōu)先級回收各區(qū)域垃圾的機制,確保了G1垃圾收集器在有限時間內(nèi)獲得最高的垃圾收集效率。

相對于CMS收集器,G1垃圾收集器有兩個突出的改進。

 

  • 基于標記整理算法,不產(chǎn)生內(nèi)存碎片。
  • 可以精確地控制停頓時間,在不犧牲吞吐量的前提下實現(xiàn)短停頓垃圾回收。

 

類加載機制

JVM的類加載階段

JVM的類加載分為5個階段:加載、驗證、準備、解析、初始化。

在類初始化完成后就可以使用該類的信息,在一個類不再被需要時可以從JVM中卸載。

加載

加載:加載是指JVM讀取 Class文件,并且根據(jù) Class文件描述創(chuàng)建 java.lang.Class對象的過程。

類加載過程主要包含將 Class文件讀取到運行時區(qū)域的方法區(qū)內(nèi),在堆中創(chuàng)建 java.lang.Class對象,并封裝類在方法區(qū)的數(shù)據(jù)結(jié)構(gòu)的過程,在讀取 Class文件時既可以通過文件的形式讀取,也可以通過 JAR包、 WAR包讀取,還可以通過代理自動生成 Class或其他方式讀取。

驗證

驗證:驗證主要用于確保 Class文件符合當(dāng)前虛擬機的要求,保障虛擬機自身的安全,只有通過驗證的 Class文件才能被JVM加載。

準備

準備:準備主要工作是在方法區(qū)中為類變量分配內(nèi)存空間并設(shè)置類中變量的初始值。

初始值指不同數(shù)據(jù)類型的默認值,這里需要注意 final類型的變量和非 final類型的變量在準備階段的數(shù)據(jù)初始化過程不同。

栗如,一個成員變量的定義如下:

public static long value = 1000;

在以上代碼中,靜態(tài)變量value在準備階段的初始值是0,將value設(shè)置為1000的動作是在對象初始化時完成的,因為JVM在編譯階段會將靜態(tài)變量的初始化操作定義在構(gòu)造器中。但是,如果將變量value聲明為final類型:

public static final int value = 1000;

則JVM在編譯階段后會為final類型的變量value生成其對應(yīng)的ConstantValue屬性,虛擬機在準備階段會根據(jù)ConstantValue屬性將value賦值為1000。

解析

解析:解析是指JVM會將常量池中的符號引用替換為直接引用。

初始化

初始化:初始化主要通過執(zhí)行類構(gòu)造器的

方法為類進行初始化。

方法是在編譯階段由編譯器自動收集類中靜態(tài)語句塊和變量的賦值操作組成的。

JVM規(guī)定,只有在父類的

方法都執(zhí)行成功后,子類中的

方法才可以被執(zhí)行。

在一個類中既沒有靜態(tài)變量賦值操作也沒有靜態(tài)語句塊時,編譯器不會為該類生成

方法。

在發(fā)生以下幾種情況時,JVM不會執(zhí)行類的初始化流程。

 

  • 常量在編譯時會將其常量值存入使用該常量的類的常量池中,該過程不需要調(diào)用常量所在的類,因此不會觸發(fā)該常量類的初始化。
  • 在子類引用父類的靜態(tài)字段時,不會觸發(fā)子類的初始化,只會觸發(fā)父類的初始化。
  • 定義對象數(shù)組,不會觸發(fā)該類的初始化。
  • 在使用類名獲取 Class對象時不會觸發(fā)類的初始化。
  • 在使用 Class.forName加載指定的類時,可以通過 initialize參數(shù)設(shè)置是否需要對類進行初始化。
  • 在使用 ClassLoader默認的 loadClass方法加載類時不會觸發(fā)該類的初始化。

 

類加載器

JVM提供了3種類加載器,分別是啟動類加載器、擴展類加載器和應(yīng)用程序類加載器。

類加載器分為:啟動類加載器、擴展類加載器、應(yīng)用程序類加載器、自定義加載器。

 

  • 啟動類加載器:負責(zé)加載 Java_HOME/lib目錄中的類庫,或通過 -Xbootclasspath參數(shù)指定路徑中被虛擬機認可的類庫。
  • 擴展類加載器:負責(zé)加載 Java_HOME/lib/ext目錄中的類庫,或通過 java.ext.dirs系統(tǒng)變量加載指定路徑中的類庫。
  • 應(yīng)用程序類庫加載器:負責(zé)加載用戶路徑( classpath)上的類庫。
  • 除了上述3種類加載器,我們也可以通過繼承 java.lang.ClassLoader實現(xiàn)自定義的類加載器。

 

雙親委派機制

JVM通過雙親委派機制對類進行加載。

雙親委派機制指一個類在收到類加載請求后不會嘗試自己加載這個類,而是把該類加載請求向上委派給其父類去完成,其父類在接收到該類加載請求后又會將其委派給自己的父類,以此類推,這樣所有的類加載請求都被向上委派到啟動類加載器中。

若父類加載器在接收到類加載請求后發(fā)現(xiàn)自己也無法加載該類(通常原因是該類的 Class文件在父類的類加載路徑中不存在),則父類會將該信息反饋給子類并向下委派子類加載器加載該類,直到該類被成功加載,若找不到該類,則JVM會拋出 ClassNotFoud異常。

雙親委派類加載機制的類加載流程如下:

 

  1. 將自定義加載器掛載到應(yīng)用程序類加載器。
  2. 應(yīng)用程序類加載器將類加載請求委托給擴展類加載器。
  3. 擴展類加載器將類加載請求委托給啟動類加載器。
  4. 啟動類加載器在加載路徑下查找并加載 Class文件,如果未找到目標 Class文件,則交由擴展類加載器加載。
  5. 擴展類加載器在加載路徑下查找并加載 Class文件,如果未找到目標 Class文件,則交由應(yīng)用程序類加載器加載。
  6. 應(yīng)用程序類加載器在加載路徑下查找并加載 Class文件,如果未找到目標 Class文件,則交由自定義加載器加載。
  7. 在自定義加載器下查找并加載用戶指定目錄下的 Class文件,如果在自定義加載路徑下未找到目標 Class文件,則拋出 ClassNotFoud異常。

 

雙親委派機制的核心是保障類的唯一性和安全性。

例如在加載 rt.jar包中的 java.lang.Object類時,無論是哪個類加載器加載這個類,最終都將類加載請求委托給啟動類加載器加載,這樣就保證了類加載的唯一性。

如果在JVM中存在包名和類名相同的兩個類,則該類將無法被加載,JVM也無法完成類加載流程。

OSGI

OSGI(Open Service Gateway Initiative)是Java動態(tài)化模塊化系統(tǒng)的一系列規(guī)范,旨在為實現(xiàn)Java程序的模塊化編程提供基礎(chǔ)條件。

基于OSGI的程序可以實現(xiàn)模塊級的熱插拔功能,在程序升級更新時,可以只針對需要更新的程序進行停用和重新安裝,極大提高了系統(tǒng)升級的安全性和便捷性。

OSGI提供了一種面向服務(wù)的架構(gòu),該架構(gòu)為組件提供了動態(tài)發(fā)現(xiàn)其他組件的功能,這樣無論是加入組件還是卸載組件,都能被系統(tǒng)的其他組件感知,以便各個組件之間能更好地協(xié)調(diào)工作。

OSGI不但定義了模塊化開發(fā)的規(guī)范,還定義了實現(xiàn)這些規(guī)范所依賴的服務(wù)與架構(gòu),市場上也有成熟的框架對其進行實現(xiàn)和應(yīng)用,但只有部分應(yīng)用適合采用OSGI方式,因為它為了實現(xiàn)動態(tài)模塊,不再遵循JVM類加載雙親委派機制和其他JVM規(guī)范,在安全性上有所犧牲。

分享到:
標簽:JVM
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定