java的两种运行机制_Java☞JVM工作原理
參考博客:1 2 3
JVM工作原理
java虛擬機體系結構
Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫并編譯的程序可以運行在這個平臺上。這個平臺的結構如下圖所示:
Java平臺結構
在Java平臺的結構中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統和硬件無關的關鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴于平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統上實現;在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的平臺無關性。
每個JVM都有兩種機制:
①類裝載子系統:裝載具有適合名稱的類或接口
②執行引擎:負責執行包含在已裝載的類或接口中的指令
每個JVM都包含:方法區、Java堆、Java棧、本地方法棧、指令計數器及其他隱含寄存器。
image.png
Java代碼編譯和執行過程
Java代碼的編譯和執行的整個過程大概是:開發人員編寫Java代碼(.java文件),然后將之編譯成字節碼(.class文件),再然后字節碼被裝入內存,一旦字節碼進入虛擬機,它就會被解釋器解釋執行,或者是被即時代碼發生器有選擇的轉換成機器碼執行。
(1)Java代碼編譯是由Java源碼編譯器來完成,也就是Java代碼到JVM字節碼(.class文件)的過程。 流程圖如下所示:
image.png
(2)Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:
image.png
Java代碼編譯和執行的整個過程包含了以下三個重要的機制:
·Java源碼編譯機制
·類加載機制
·類執行機制
(1)Java源碼編譯機制
Java 源碼編譯由以下三個過程組成:
①分析和輸入到符號表
②注解處理
③語義分析和生成class文件
流程圖如下所示:
image.png
最后生成的class文件由以下部分組成:
①結構信息:包括class文件格式版本號及各部分的數量與大小的信息
②元數據:對應于Java源碼中聲明與常量的信息。包含類/繼承的超類/實現的接口的聲明信息、域與方法聲明信息和常量池
③方法信息:對應Java源碼中語句和表達式對應的信息。包含字節碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符號信息
(2)類加載機制
JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關系和加載順序可以由下圖來描述:
image.png
①Bootstrap ClassLoader
負責加載$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++實現,不是ClassLoader子類
②Extension ClassLoader
負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包
③App ClassLoader
負責記載classpath中指定的jar包及目錄中class
④Custom ClassLoader
屬于應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規范自行實現ClassLoader加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。
(3)類執行機制
JVM是基于堆棧的虛擬機。JVM為每個新創建的線程都分配一個堆棧.也就是說,對于一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。
JVM執行class字節碼,線程創建后,都會產生程序計數器(PC)和棧(Stack),程序計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應著每個方法的每次調用,而棧幀又是有局部變量區和操作數棧兩部分組成,局部變量區用于存放方法中的局部變量和參數,操作數棧中用于存放方法執行過程中產生的中間結果。棧的結構如下圖所示:
image.png
JVM內存各個部分
Java虛擬機在執行Java程序的過程中會把它所管理的內存劃分為若干個不同的數據區域。這些區域都有各自的用途,以及創建和銷毀的時間,有點區域隨著虛擬機進程的啟動而存在,有些區域則依賴用戶線程的啟動和結束而建立和銷毀。根據《Java虛擬機規范SE7版》的規定,Java虛擬機所管理的內存將會包括以下幾個運行時數據區域,如下如所示:
Java虛擬機運行時數據區
程序計數器
程序計數器(Program Counter Register)是一塊較小的內存空間,它可以看作是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型里(僅是概念模型, 各種虛擬機可能會通過一些高效的方式去實現),字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。
由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的,在任何一個確定 的時刻,一個處理器(對于多核處理器來說一個內核)都只會執行一條線程中的指令。因此,為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲,我們稱這類內存區域為“線程私有”的內存。
如果線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Native方法,這個計數器值則為空(Undefined)。此內存區域是唯一一個在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。
Java虛擬機棧
java虛擬機棧也是線程私有,與線程的生命周期一致,在執行每個方法都會創建一個Stack Frame,用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從開始執行到結束,對應一個Stack Frame在虛擬機值棧中從入棧和出棧的過程。如果線程請求的棧深度大于虛擬機所允許的深度,就會出現StackOverFlowException。如果允許動態擴展,在擴展的過程中,如果無法申請到足夠的內存,則會拋出OutOfMemoryException異常。
3.本地方法棧
和java虛擬機棧的作用類似,不同點在本地方法棧主要是為虛擬機使用到的Native方法提供服務,本地方法棧也會拋出StackOverFlowException和OutOfMemoryException異常。
Java堆
堆是java虛擬機中內存中最大的一塊,被所有線程共享的一塊內存區域,在虛擬機創建時創建。作用就是存放對象實例,所有的對象的實例都需要在這里分配內存。幾乎所有的對象實例和對象數組都需要在堆上分配。是java虛擬機內存回收管理的重要區域,因此也被稱為“GC”堆。如果堆中沒有內存完成實例分配,并且堆也無法擴展時,則拋出OutOfMemoryException異常。
方法區
方法區和java堆一樣,是各個線程共享的內存區域,用于存儲被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯的代碼等數據。通常被開發人員成為“永久帶”。這個區域的內存回收的目標就是針對常量池的回收和對類型的卸載,也是較為難處理的部分.
垃圾回收
- 為什么要進行垃圾回收?
隨著程序的運行,內存中存在的實例對象、變量等信息占據的內存越來越多,如果不及時進行垃圾回收,必然會帶來程序性能的下降,甚至會因為可用內存不足造成一些不必要的系統異常。
- 哪些“垃圾”需要回收?
在我們上面介紹的五大區中,有三個是不需要進行垃圾回收的:程序計數器、JVM棧、本地方法棧。因為它們的生命周期是和線程同步的,隨著線程的銷毀,它們占用的內存會自動釋放,所以只有方法區和堆需要進行GC。具體到哪些對象的話,簡單概況一句話:如果某個對象已經不存在任何引用,那么它可以被回收。通俗解釋一下就是說,如果一個對象,已經沒有什么作用了,就可以被當廢棄物被回收了。
- 什么時候回收?
根據一個經典的引用計數算法,每個對象添加一個引用計數器,每被引用一次,計數器加1,失去引用,計數器減1,當計數器在一段時間內保持為0時,該對象就認為是可以被回收得了。但是,這個算法有明顯的缺陷:當兩個對象相互引用,但是二者已經沒有作用時,按照常規,應該對其進行垃圾回收,但是其相互引用,又不符合垃圾回收的條件,因此無法完美處理這塊內存清理,因此Sun的JVM并沒有采用引用計數算法來進行垃圾回收。而是采用一個叫:根搜索算法,如下圖:
GC Root
基本思想就是:從一個叫GC Root的對象開始,向下搜索,如果一個對象不能到達GC Roots對象的時候,說明它已經不再被引用,即可被進行垃圾回收(此處 暫且這樣理解,其實事實還有一些不同,當一個對象不再被引用時,并沒有完全“死亡”,如果類重寫了finalize()方法,且沒有被系統調用過,那么系統會調用一次finalize()方法,以完成最后的工作,在這期間,如果可以將對象重新與任何一個和GC Roots有引用的對象相關聯,則該對象可以“重生”,如果不可以,那么就說明徹底可以被回收了),如上圖中的ObjD,ObjE,雖然它們兩個依然可能相互引用,但是總體來說,它們已經沒有作用了,這樣就解決了引用計數算法無法解決的問題。
- 垃圾回收采用的算法有哪些?
前面說的引用計數算法
優點:引用計數收集器可以很快的執行,交織在程序運行中。對程序需要不被長時間打斷的實時環境比較有利。
缺點:無法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數永遠不可能為0.
根搜索算法
根搜索算法是從離散數學中的圖論引入的,程序把所有的引用關系看作一張圖,從一個節點GC ROOT開始,尋找對應的引用節點,找到這個節點以后,繼續尋找這個節點的引用節點,當所有的引用節點尋找完畢之后,剩余的節點則被認為是沒有被引用到的節點,即無用的節點。
java中可作為GC Root的對象有:
1).虛擬機棧中引用的對象(本地變量表)
2).方法區中靜態屬性引用的對象
3). 方法區中常量引用的對象
4).本地方法棧中引用的對象(Native對象)
2.1. tracing算法示意圖
image.png
標記-清楚算法
標記-清除算法采用從根集合進行掃描,對存活的對象對象標記,標記完畢后,再掃描整個空間中未被標記的對象,進行回收,如上圖所示。標記-清除算法不需要進行對象的移動,并且僅對不存活的對象進行處理,在存活對象比較多的情況下極為高效,但由于標記-清除算法直接回收不存活的對象,因此會造成內存碎片。
compacting算法(或叫標記-整理算法)
標記-整理算法
標記-整理算法采用標記-清除算法一樣的方式進行對象的標記,但在清除時不同,在回收不存活的對象占用的空間后,會將所有的存活對象往左端空閑空間移動,并更新對應的指針。標記-整理算法是在標記-清除算法的基礎上,又進行了對象的移動,因此成本更高,但是卻解決了內存碎片的問題。在基于Compacting算法的收集器的實現中,一般增加句柄和句柄表。
copying算法
image.png
該算法的提出是為了克服句柄的開銷和解決堆碎片的垃圾回收。它開始時把堆分成 一個對象 面和多個空閑面, 程序從對象面為對象分配空間,當對象滿了,基于copying算法的垃圾 收集就從根集中掃描活動對象,并將每個 活動對象復制到空閑面(使得活動對象所占的內存之間沒有空閑洞),這樣空閑面變成了對象面,原來的對象面變成了空閑面,程序會在新的對象面中分配內存。一種典型的基于coping算法的垃圾回收是stop-and-copy算法,它將堆分成對象面和空閑區域面,在對象面與空閑區域面的切換過程中,程序暫停執行。
generation算法
image.png
分代的垃圾回收策略,是基于這樣一個事實:不同的對象的生命周期是不一樣的。因此,不同生命周期的對象可以采取不同的回收算法,以便提高回收效率。
年輕代(Young Generation)
1.所有新生成的對象首先都是放在年輕代的。年輕代的目標就是盡可能快速的收集掉那些生命周期短的對象。
2.新生代內存按照8:1:1的比例分為一個eden區和兩個survivor(survivor0,survivor1)區。一個Eden區,兩個 Survivor區(一般而言)。大部分對象在Eden區中生成。回收時先將eden區存活對象復制到一個survivor0區,然后清空eden區,當這個survivor0區也存放滿了時,則將eden區和survivor0區存活對象復制到另一個survivor1區,然后清空eden和這個survivor0區,此時survivor0區是空的,然后將survivor0區和survivor1區交換,即保持survivor1區為空, 如此往復。
3.當survivor1區不足以存放 eden和survivor0的存活對象時,就將存活對象直接存放到老年代。若是老年代也滿了就會觸發一次Full GC,也就是新生代、老年代都進行回收
4.新生代發生的GC也叫做Minor GC,MinorGC發生頻率比較高(不一定等Eden區滿了才觸發)
年老代(Old Generation)
1.在年輕代中經歷了N次垃圾回收后仍然存活的對象,就會被放到年老代中。因此,可以認為年老代中存放的都是一些生命周期較長的對象。
2.內存比新生代也大很多(大概比例是1:2),當老年代內存滿時觸發Major GC即Full GC,Full GC發生頻率比較低,老年代對象存活時間比較長,存活率標記高。
持久代(Permanent Generation)
用于存放靜態文件,如Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate 等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。
- 垃圾回收器有哪些?
新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge
老年代收集器使用的收集器:Serial Old、Parallel Old、CMS
image.png
Serial收集器(復制算法)
新生代單線程收集器,標記和清理都是單線程,優點是簡單高效。
Serial Old收集器(標記-整理算法)
老年代單線程收集器,Serial收集器的老年代版本。
ParNew收集器(停止-復制算法)
新生代收集器,可以認為是Serial收集器的多線程版本,在多核CPU環境下有著比Serial更好的表現。
Parallel Scavenge收集器(停止-復制算法)
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般為99%, 吞吐量= 用戶線程時間/(用戶線程時間+GC線程時間)。適合后臺應用等對交互相應要求不高的場景。
Parallel Old收集器(停止-復制算法)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量優先
CMS(Concurrent Mark Sweep)收集器(標記-清理算法)
高并發、低停頓,追求最短GC回收停頓時間,cpu占用比較高,響應時間快,停頓時間短,多核cpu 追求高響應時間的選擇
注:G1收集器
G1(Garbage-First)收集器是當今收集器技術發展的最前沿成果之一,JDK 7 Update 4后開始進入商用。在G1收集器之前的其他收集器進行收集的范圍都是整個新生代或者老年代,而G1收集器不再是這樣,使用G1收集器時,Java堆的內存布局就與其他收集器有很大差別,它將整個Java堆分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分Region的集合。G1收集器跟蹤各個Region里面的垃圾堆積的價值大小,在后臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region(這也是Garbage-First名稱的由來)。這種使用Region劃分內存空間以及有優先級的區域回收方式,保證了G1收集器在有限的時間內可以獲取盡可能高的收集效率。
總結
以上是生活随笔為你收集整理的java的两种运行机制_Java☞JVM工作原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java中static的作用详解_jav
- 下一篇: eclipse java shell 窗