JVM的知識-看這一篇就夠了

什麼是JVM?

我們先看下完整的單詞。JVM(Java Virtual Machine,Java虛擬機)

首先什麼是虛擬機

虛擬機是一種抽象化的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。

虛擬機有哪幾種類型

大體上虛擬機可以分爲系統虛擬機和程序虛擬機。

visual Box、VMare就屬於系統虛擬機。他們完全是對物理計算機的仿真,提供一個可運行完整操作系統的軟件平臺。

java虛擬機就是典型程序虛擬機,它專門爲執行單個計算機程序而設計。
在java虛擬機中執行的指令我們稱之爲java字節碼指令。

java虛擬機有哪幾種

最初sun使用的叫Classic的java虛擬機,
到現在使用最廣泛的是HotSpot虛擬機,除了sun以外還有BEA的JRockit虛擬機。


java虛擬機的基本結構圖解

圖片來自網絡,我們先大概看下基本結構,接下來再詳細講一下。
在這裏插入圖片描述

JVM內存區域劃分

根據上圖我們可以粗略分來,JVM的內部體系結構分爲三部分,
類裝載(ClassLoader)子系統,
運行時數據區,
執行引擎。

1)類裝載子系統

每一個Java虛擬機都有一個類加載器子系統(class loader subsystem),
負責加載程序中的類型(類和接口),並賦予唯一的名字。

JVM的兩種類裝載器包括:啓動類裝載器和用戶自定義類裝載器,啓動類裝載器是JVM實現的一部分,用戶自定義類裝載器則是Java程序的一部分,必須是ClassLoader類的子類。

2)執行引擎

每一個Java虛擬機都有一個執行引擎(execution engine)負責執行被加載類中包含的指令。

它或者在執行字節碼,或者執行本地方法
主要的執行技術有:解釋,即時編譯,自適應優化、芯片級直接執行其中解釋屬於第一代JVM,即時編譯JIT屬於第二代JVM,自適應優化(目前Sun的HotspotJVM採用這種技術)則吸取第一代JVM和第二代JVM的經驗,採用兩者結合的方式 。

自適應優化:開始對所有的代碼都採取解釋執行的方式,並監視代碼執行情況,然後對那些經常調用的方法啓動一個後臺線程,將其編譯爲本地代碼,並進行仔細優化。若方法不再頻繁使用,則取消編譯過的代碼,仍對其進行解釋執行。

3)運行時數據區

主要包括:方法區,堆,Java棧,PC寄存器,本地方法棧

在看這個運行時數據區的時候,我們先來了解一下數據結構裏面的棧和堆

棧(stack)與堆(heap)都是Java用來在Ram中存放數據的地方。與C++不同,Java自動管理棧和堆

什麼是棧?

棧式先進後出的。
棧的優勢是,存取速度比堆要快,僅次於直接位於CPU中的寄存器。但缺點是,存在棧中的數據大小與生存期必須是確定的。
在這裏插入圖片描述

什麼是堆?

堆是一種常用的樹形結構,是一種特殊的完全二叉樹。

java的棧存儲什麼?

棧中主要存放一些基本類型的變量(int, short, long, byte, float, double, boolean, char),例如 int a = 3;
另外,棧有一個很重要的特殊性,就是存在棧中的數據可以共享。
例如下面的代碼,a和b都是指向數值爲3的地址。

int a = 3;
int b = 3;

java的堆存儲什麼?

堆中主要是存包裝類,如Integer, String, Double等將相應的基本數據類型包裝起來的類。
還有new 出來的類的實例。

好了,到這裏也有大概的瞭解棧和堆了,現在正式看java虛擬機的運行時數據區有哪些。

1、方法區(Method Area)
存放所加載的類的信息(名稱、修飾符等)、類中的靜態變量;
存放類中定義爲final類型的常量、類中的Field信息、類中的方法信息,

在JDK8之前的Host Spot虛擬機的實現中,方法區也被稱爲永久區,又稱爲持久代。
在 Java8 中,永久區已經被 Metaspace 元空間取而代之。
關於兩者的區別和垃圾回收可以看我的這篇博客
垃圾回收詳解

當開發人員在程序中通過Class對象中的getName、isInterface等方法來獲取信息時,這些數據都來源於方法區域,同時方法區域也是全局共享的,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出OutOfMemory的錯誤信息。

2、java堆:
再java虛擬機啓動的時候舊建立java堆,他是java程序最主要的內存工作區域,幾乎所有new創建對象的實例都存放java堆中,注意:堆空間是所有線程共享的。

3、直接內存:
java的NIO庫允許java程序使用直接內存,從而提高性能,通常直接內存速度會優於java堆。讀寫頻繁的場合可能會考慮使用。

4、java棧:
也是虛擬機棧。每個虛擬機的線程都有一個私有的棧,一個線程的java棧在線程創建的時候被創建,java棧中保存着局部變量、方法參數、還有java的調用方法和返回值等。

5、本地方法棧:
與java棧很類似,最大不同是本地方法棧用於本地方法調用。java虛擬機允許java直接調用本地方法(通常本地方法爲C語言編寫)
本地方法棧服務的對象是JVM執行的native方法,而虛擬機棧服務的是JVM執行的java方法。

根據最開始的貼出來的java虛擬機的基本結構圖解,如下,我們可以知道
還有垃圾回收系統和PC寄存器。那我們再往下看看。
在這裏插入圖片描述

垃圾回收系統:
是java的核心,也是必不可少的,java有一套自己進行垃圾清理的機制,
開發者無需手動清理。

PC寄存器:
是每個線程私有的空間,java虛擬機會爲每個線程創建PC寄存器,在任意時刻,一個java線程總是在執行一個方法,這個方法被稱爲當前方法,如果當前方法不是本地方法,PC寄存器就會執行當前正在被執行的指令,如果是本地方法,則PC寄存器的值爲undefined。寄存器存放如當前執行環境指針、程序計數器、操作棧指針、計算的變量指針等信息。


最後看一下運行時數據區的圖解

圖片來自網絡
在這裏插入圖片描述

java虛擬機棧和本地方法棧的區別

什麼是java虛擬機棧

虛擬機棧是用於描述java方法執行的內存模型。
Java虛擬機棧是線程私有的, 生命週期與線程相同. 虛擬機棧存放棧幀, 棧幀用於存儲局部變量表, 部分結果值, 方法的初始化參數和返回信息, 方法的執行通過棧幀的壓棧和出棧實現.

什麼是本地方法棧

本地方法棧的功能和特點類似於虛擬機棧,均具有線程隔離的特點以及都能拋出StackOverflowError和OutOfMemoryError異常。
  不同的是,本地方法棧服務的對象是JVM執行的native方法,而虛擬機棧服務的是JVM執行的java方法。

共享問題

方法區和堆由所有線程共享
1、堆:存放所有程序在運行時創建的對象
2、方法區:當JVM的類裝載器加載.class文件,並進行解析,把解析的類型信息放入方法區。

Java棧和PC寄存器由線程獨享
1、JVM棧是線程私有的,每個線程創建的同時都會創建JVM棧,JVM棧中存放的爲當前線程中局部基本類型的變量(java中定義的八種基本類型:boolean、char、byte、short、int、long、float、double)、部分的返回結果以及Stack Frame,非基本類型的對象在JVM棧上僅存放一個指向堆上的地址
2、本地方法棧:存儲本地方法調用的狀態


JVM垃圾回收

Sun的JVMGenerationalCollecting(垃圾回收)原理是這樣的:
把對象分爲年輕代(Young)、年老代(Tenured)、持久代(Perm),對不同生命週期的對象使用不同的算法。(基於對對象生命週期分析)

Young(年輕代)
年輕代分三個區。一個Eden(伊甸園)區,兩個Survivor(倖存)區。
大部分對象在Eden區中生成。
當Eden區滿時,還存活的對象將被複制到Survivor區(兩個中的一個),
當這個Survivor區滿時,此區的存活對象將被複制到另外一個Survivor區,
當這個Survivor也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的對象,將被複制年老區(Tenured。需要注意,Survivor的兩個區是對稱的,沒先後關係,所以同一個區中可能同時存在從Eden複製過來對象,和從前一個Survivor複製過來的對象,而複製到年老區的只有從第一個Survivor去過來的對象。而且,Survivor區總有一個是空的。)

Tenured(年老代)
年老代存放從年輕代存活的對象。一般來說年老代存放的都是生命期較長的對象。

Perm(持久代)
用於存放靜態文件,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。持久代大小通過-XX:MaxPermSize=進行設置。

注意:
JDK8 HotSpot JVM 將移除永久區,使用本地內存來存儲類元數據信息並稱之爲:元空間(Metaspace)。

元空間的本質和永久代類似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制。

  -XX:MetaspaceSize:
  初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,
  同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;
  如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。
  
  -XX:MaxMetaspaceSize:
  最大空間,默認是沒有限制的。

 除了上面兩個指定大小的選項以外,還有兩個與 GC 相關的屬性:
  -XX:MinMetaspaceFreeRatio:
  在GC之後,最小的Metaspace剩餘空間容量的百分比,減少爲分配空間所導致的垃圾收集
  
  -XX:MaxMetaspaceFreeRatio:
  在GC之後,最大的Metaspace剩餘空間容量的百分比,減少爲釋放空間所導致的垃圾收集

爲什麼1.8要移除永久區,改爲元空間??

  • 字符串存在永久代中,現實使用中易出問題, 由於永久代內存經常不夠用或發生內存泄露,爆出異常 java.lang.OutOfMemoryError: PermGen

  • 類及方法的信息等比較難確定其大小,因此對於永久代的大小指定比較困難,太小容易出現永久代溢出,太大則容易導致老年代溢出。

  • 永久代會爲 GC 帶來不必要的複雜度,並且回收效率偏低。


JVM中GC什麼時候開始進行

GC 經常發生的區域是堆區,堆區還可以細分爲新生代、老年代,新生代還分爲一個 Eden 區和兩個 Survivor 區

“你能不能談談,java GC是在什麼時候,對什麼東西,做了什麼事情?”

在什麼時候:

1.新生代有一個Eden區和兩個survivor區,首先將對象放入Eden區,如果空間不足就向其中的一個survivor區上放,如果仍然放不下就會引發一次發生在新生代的minor GC,將存活的對象放入另一個survivor區中,然後清空Eden和之前的那個survivor區的內存。在某次GC過程中,如果發現仍然又放不下的對象,就將這些對象放入老年代內存裏去。

2.大對象以及長期存活的對象直接進入老年區。

3.當每次執行minor GC的時候應該對要晉升到老年代的對象進行分析,如果這些馬上要到老年區的老年對象的大小超過了老年區的剩餘大小,那麼執行一次Full GC以儘可能地獲得老年區的空間。

對什麼東西:
從GC Roots搜索不到,而且經過一次標記清理之後仍沒有復活的對象。

做什麼:
新生代:複製清理;
老年代:標記-清除和標記-壓縮算法;
永久代:存放Java中的類和加載類的類加載器本身。

垃圾回收算法有哪些??
1,引用計數 :原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,只用收集計數爲 0 的對象。此算法最致命的是無法處理循環引用的問題;

2,標記-清除 :此算法執行分兩階段。第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除;

此算法需要暫停整個應用,同時,會產生內存碎片;

3,複製算法 :此算法把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另外一個區域中;

此算法每次只處理正在使用中的對象,因此複製成本比較小,同時複製過去以後還能進行相應的內存整理,不會出現 “碎片” 問題。當然,此算法的缺點也是很明顯的,就是需要兩倍內存空間;

4,標記-整理 :此算法結合了 “標記-清除” 和 “複製” 兩個算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象並且把存活對象 “壓縮” 到堆的其中一塊,按順序排放。

此算法避免了 “標記-清除” 的碎片問題,同時也避免了 “複製” 算法的空間問題。

感謝以下博客~

https://blog.csdn.net/iG_xdd/article/details/79748230
https://blog.csdn.net/gao_sl/article/details/80317911
https://blog.csdn.net/qq_34872215/article/details/79660141
https://www.cnblogs.com/manayi/p/9293302.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章