JVM第一彈

JVM第一彈

基本概念

JVM是可運行java代碼的假想計算機,包括一套字節碼指令集,一組寄存器,一個棧,一個垃圾回收、堆和一個存儲方法域。JVM是運行在操做系統之上的,它與硬件沒有直接的交互。java

運行過程

咱們都知道Java代碼源文件,經過編譯器可以產生相應的.Class字節碼文件,而字節碼文件又經過Java虛擬機中的解釋器,編譯成特定機器上的機器碼。c++

① Java源文件 ——> 編譯器 ——> 字節碼文件 ② 字節碼文件 ——> JVM ——> 機器碼程序員

每種平臺的解釋器是不一樣的,可是虛擬機是相同的,這也就是java爲何可以跨平臺的緣由了。當一個程序從開始運行,這時虛擬機就開始實例化了,多個程序 啓動就會存在多個虛擬機實例。 程序退出或者關閉,則虛擬機實例消亡,多個虛擬機實例之間數據不能共享。算法

類加載器

什麼是類的加載? 類的加載是指將類的字節碼文件數據讀入到內存中,將其放在運行時數據區的方法區內,而後在堆區建立一個java.lang.Class對象,用來封裝類在方法區內的數據結構。 類的加載的最終產品是位於堆區內中的Class對象,Class對象封裝了類在方法區內的數據結構,而且向java程序員提供了訪問方法區內的數據結構的接口。json

類加載器包括:瀏覽器

  1. 啓動類加載器(BootStrap) ——主要有C++進行實現的。用來加載jdk安裝目錄下的:jre/lib下的可執行jar包。 也能夠經過設置 -XbootClasspath來動態指定jar包位置。在java代碼中沒法獲取到該對象。
String str = new String("HelloWorld");
System.out.println(str.getClass().getClassLoader());

//控制檯打印null
  1. 擴展類加載器(ExtClassLoader)服務器

    ——是java代碼實現的,用來加載java安裝目錄下 jre/lib/ext 目錄中的可執行jar包。數據結構

  2. 應用程序類加載器(AppClassLoader)多線程

    ——是java代碼實現的,用來加載用戶編寫的代碼。咱們新建一個類,獲取其類加載器就是AppClassLoaderjvm

public class MyClassLoaderTest {

    public static void main(String[] args) {
        String str = new String("HelloWorld");
        // 打印null
        System.out.println(str.getClass().getClassLoader());


        // 打印sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(MyClassLoaderTest.class.getClassLoader());
        // 打印sun.misc.Launcher$ExtClassLoader@4554617c
        System.out.println(MyClassLoaderTest.class.getClassLoader().getParent());
        // 打印null
        System.out.println(MyClassLoaderTest.class.getClassLoader().getParent().getParent());

    }

}

由上述代碼可見: AppClassLoader extend ExtClassLoader extend BootstrapClassLoader

  1. 用戶自定義類加載器 —— 用戶編寫類繼承自 java.lang.ClassLoader

爲了防止用戶自定義類與jdk自帶的類衝突,jdk內有雙親委派機制和沙箱機制。

雙親委派機制

上述過程當中,咱們認識到了類加載器之間的繼承關係。當java在加載類的時候,由AppClassLoader委派其父類ExtClassLoader進行加載,ExtClassLoader會再次委派其父類BootStrapClassLoader進行加載, 若是BootStrapClassLoader找到該類那麼加載該類返回該類的Class對象,可是,若是此時BootStrapClassLoader沒有找到該類, 那麼就須要ExtClassLoader自身進行加載,若是ExtClassLoader找到該類那麼加載該類返回該類的Class對象, 可是,若是ExtClassLoader也沒有找到該類,那麼就要由AppClassLoader進行加載。 若是最後AppClassLoader也沒有找到該類,那麼就會拋出 ClassNotFoundException

類加載器沒有向下尋找,沒有getChild只有getParent

若是你本身定義了一個與jdk自帶類名包名一致的類,那麼java也不會去加載該類。

JVM結構

JVM內存區域主要分爲

  • 線程私有區域
  1. 程序計數器
  2. 虛擬機棧
  3. 本地方法區
  • 線程共享區域
  1. Java堆
  2. 方法區
  • 直接內存

生命週期

  1. 線程私有數據區域生命週期與線程相同,依賴用戶線程的啓動/結束而建立/銷燬。
  2. 線程共享區域隨着虛擬機的啓動/關閉 而 建立/銷燬。

方法區和堆是全部線程共享的內存區域;而java棧、本地方法棧和程序計數器是運行時線程私有的內存區域。

  • 方法區 主要存放靜態變量,常量,Class類模板(接口定義,構造函數),運行時常量池。

  • java堆(Heap),是Java虛擬機所管理的內存中最大的一塊。Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一目的就是存放對象實例,幾乎全部的對象實例都在這裏分配內存。又被稱做爲運行時數據區。

  • 程序計數器(Program Counter Register),是一塊比較小的內存空間,它的做用能夠看作是當前線程所執行的字節碼的行號指示器。每一個線程都有一個私有的,能夠理解爲它是一個指針,指向方法字節碼地址,用來標記下一個要執行的方法字節碼地址。

  • JVM棧(JVM Stacks),與程序計數器同樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命週期與線程相同,線程結束棧內存也就釋放了,對於棧來講不存在來及回收的問題。主要保存八大基本數據類型的變量、對象的引用變量以及實例方法。虛擬機棧描述的是Java方法執行的內存模型:每一個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於存儲局部變量表、操做棧、動態連接、方法出口等信息。每個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。

  • 本地方法棧(Native Method Stacks),與c/c++交互的一塊區域,本地方法棧(Native Method Stacks)與虛擬機棧所發揮的做用是很是類似的,其區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native方法服務。

垃圾回收器

  • Serial收集器,串行收集器是最古老,最穩定以及效率高的收集器,可能會產生較長的停頓,只使用一個線程去回收。
  • ParNew收集器,ParNew收集器其實就是Serial收集器的多線程版本。
  • Parallel收集器,Parallel Scavenge收集器相似ParNew收集器,Parallel收集器更關注系統的吞吐量。
  • Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和「標記-整理」算法
  • CMS收集器,CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。
  • G1收集器,G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高機率知足GC停頓時間要求的同時,還具有高吞吐量性能特徵

調優命令

Sun JDK監控和故障處理命令有 jps jstat jmap jhat jstack jinfo

  • jps,JVM Process Status Tool,顯示指定系統內全部的HotSpot虛擬機進程。
  • jstat,JVM statistics Monitoring是用於監視虛擬機運行時狀態信息的命令,它能夠顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。
  • jmap,JVM Memory Map命令用於生成heap dump文件
  • jhat,JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML服務器,生成dump的分析結果後,能夠在瀏覽器中查看
  • jstack,用於生成java虛擬機當前時刻的線程快照。
  • jinfo,JVM Configuration info 這個命令做用是實時查看和調整虛擬機運行參數。

調優工具

經常使用調優工具分爲兩類

  1. jdk自帶監控工具:jconsole和jvisualvm
  • jconsole,Java Monitoring and Management Console是從java5開始,在JDK中自帶的java監控和管理控制檯,用於對JVM中內存,線程和類等的監控
  • jvisualvm,jdk自帶全能工具,能夠分析內存快照、線程快照;監控內存變化、GC變化等。
  1. 第三方有:MAT(Memory Analyzer Tool)、GChisto。
  • MAT,Memory Analyzer Tool,一個基於Eclipse的內存分析工具,是一個快速、功能豐富的Java heap分析工具,它能夠幫助咱們查找內存泄漏和減小內存消耗
  • GChisto,一款專業分析gc日誌的工具

你知道哪些JVM性能調優

  • 設定堆內存大小 -Xmx:堆內存最大限制。

  • 設定新生代大小。 新生代不宜過小,不然會有大量對象涌入老年代 -XX:NewSize:新生代大小 -XX:NewRatio 新生代和老生代佔比 -XX:SurvivorRatio:伊甸園空間和倖存者空間的佔比

  • 設定垃圾回收器 年輕代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC

何時出現棧溢出

遞歸操做,程序沒有出口會一直進行壓棧操做

爲何會出現棧溢出

棧的深度不夠了

堆內存

邏輯上分爲

  • 新生區
  • 養老區
  • 永久區

物理上分爲

新生區 、 養老區、 永久區

又將新生區分爲了三個區

  • 伊甸園區(80%)
  • 倖存者from區(10%)
  • 倖存者to區(10%)

新new的對象都放在伊甸園區,存活率2%,其餘對象都被垃圾回收器回收 沒有被垃圾回收倖存下來的對象將會保存到倖存者區 當伊甸園區內存不足時,會進行輕量級(minor GC)垃圾回收,將倖存者from區和伊甸園區的還在用的對象移動到倖存者to區, 而後清空倖存者from區和伊甸園區,倖存者from區清空以後會交換from區和to區,保證to區始終是空的。注意from區向to區移動以前會判斷對象的年齡, 若是大於15,直接移動到養老區。年齡計數的原理:垃圾回收器回收一次,倖存活一次加一歲。 若是養老區的內存也不夠用了,就會觸動重量級GC(full GC)將養老區和新生區全量級回收垃圾對象。若是FullGC以後養老區的內存仍是不夠用,那麼會引起OOM。

若是程序一開始就new了一個比伊甸園區大的對象,伊甸園區沒有足夠的空間存放應該如何存放呢?此時會將對象存放到養老區,若是養老區也不夠存儲,那麼會引起OOM。

對象分配規則

對象優先分配在Eden區,若是Eden區沒有足夠的空間時,虛擬機執行一次Minor GC。

大對象直接進入老年代(大對象是指須要大量連續內存空間的對象)。這樣作的目的是避免在Eden區和兩個Survivor區之間發生大量的內存拷貝(新生代採用複製算法收集內存)。

長期存活的對象進入老年代。虛擬機爲每一個對象定義了一個年齡計數器,若是對象通過了1次Minor GC那麼對象會進入Survivor區,以後每通過一次Minor GC那麼對象的年齡加1,知道達到閥值對象進入老年區。

動態判斷對象的年齡。若是Survivor區中相同年齡的全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象能夠直接進入老年代。 空間分配擔保。每次進行Minor GC時,JVM會計算Survivor區移至老年區的對象的平均大小,若是這個值大於老年區的剩餘值大小則進行一次Full GC,若是小於檢查HandlePromotionFailure設置,若是true則只進行Monitor GC,若是false則進行Full GC。

產生OOM的緣由?

  • java設置的堆內存不夠,能夠經過設置 -Xms -Xmx 來調整堆內存的大小
  • java內存中建立了大量的大對象,而且長時間不能被垃圾回收器回收

java8與元數據

在java8中,永久代已經移除了,被「元數據」(元空間)的區域所取代。元空間的本質和永久代相似,元空間與永久代的最大區別在於: **元空間並不在虛擬機中,而是使用本地內存。**所以,默認狀況下,元空間的大小僅受本地內存限制。 類的源數據放入本定內存中,字符串和類的靜態變量放到java堆中,這樣能夠加載多少類的元數據就再也不由MaxPermSize控制,而由系統的實際可用空間來控制。

垃圾回收與算法

若是肯定垃圾

  1. 引用計數法 在java中,引用和對象是有關聯的。若是要操做對象則必須用引用進行。所以,一個簡單的方法就是經過引用計數來判斷一個對象是否能夠回收。簡單來講,即一個對象若是沒有任何與之關聯的引用,即他們的引用計數都不爲0,則說明對象不太可能再被用到,那麼這個對象就是可回收對象。

  2. 可達性分析 爲了解決引用計數法的循環引用問題,java使用了可達性分析的方法。經過一系列的「GC roots」對象做爲起點搜索,若是在「GC roots」和一個對象之間沒有可達路徑,則稱該對象是不可達的。

注意 不可達並不等價於可回收對象,不可達對象變爲可回收對象至少要通過兩次標記過程。兩次標記後還是可回收對象,則將面臨回收。

  1. 標記清楚算法

最基礎的垃圾回收算法,分爲兩個階段:標記清楚。標記階段是標記出來全部要回收的對象,清楚階段回收被標記的對象所佔的空間。

該算法的缺點: 內存碎片化嚴重,垃圾清理完成後,形成不少內存空間不連續。後續可能發生大對象不能找到可利用的問題。

MajorGC使用該算法

  1. 複製算法 爲了解決標記清楚算法內存碎片化的缺陷而提出的算法。按照內存容量將內存劃分爲等大小的兩塊。每次只使用其中一塊,當這一塊內存滿後將尚存活的對象複製到另外一塊上去,把已使用的內存清理掉。

MinorGC使用該算法

缺點: 這種算法雖然實現簡單,內存效率高,不易產生碎片,可是最大的問題是能夠用內存被壓縮到了本來的一半。且存活對象增多的話,copying算法的效率也大大下降。

  1. 標記整理算法 結合以上兩個算法,爲了不缺陷而提出。標記階段和標記清楚算法相同,標記後不是清理對象,而是將存活對象移向內存的一端。而後清楚端邊界的對象.

  1. 分代收集算法 分代收集算法是目前大部分JVM所採用的方法,其核心思想是根據對象村花的不一樣生命週期將內存劃分爲不一樣的域,通常狀況下將GC堆劃分爲老生代和新生代。老生代的特色是每次垃圾回收只有少許對象須要被回收,新生代的特色是每次垃圾回收是都有大量垃圾須要被回收,所以能夠根據不一樣區域採用不一樣的算法。

6.1. 新生代與複製算法

目前大部分的JVM的GC對於新生代都採起了copying方法,由於新生代中每次垃圾回收都要回收大部分對象, 即要複製的操做比較少,但一般並非按照1:1來劃分新生代。通常將新生代劃分爲一塊較大的Eden空間和兩個比較小的Surviror空間(FromSpace,ToSpace),每次使用Eden空間和其中的一塊Surivor空間,當進行回收時,將該兩塊空間中還存活的對象複製到另一塊Survivor空間中。

6.2 老年代與標記複製算法 而老年代由於每次只回收少許的對象,所以採用Mark-Compact算法。

  1. JAVA虛擬機提到過的處於方法區的永生帶,它用來存儲class類,常量、方法描述等。對永生代的回收主要包括廢棄常量和無用的類

  2. 對象的內存分配主要在新生代的EdenSpace和SurvivorSpace的FormSpace(Survivor目前存放對象的那一塊),少數狀況會直接分配到老生代。

  3. 當新生代的EdenSpace和FromSpace空間不足時就會發生一次GC,進行GC後,EdenSpace和FromSpace區的存活對象會被移動到ToSpace,而後將EdenSpace和FromSpace進行清理。

  4. 若是ToSpace沒法足夠存儲某個對象,則將這個對象存儲到老生代。

  5. 進行GC後,使用的即是EdenSpace和ToSpace了,如此反覆循環。

  6. 當對象在Survivor區躲過一次GC後,其年齡就會+1。默認狀況下年齡達到15的對象就會移動到老生代中

Java中的四種引用

強引用

在Java中最多見的就是強引用,把一個對象賦值給一個引用變量,這個引用變量就是一個強引用。 當一個對象被強引用變量引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的,即便該對象之後永遠都不會被用到,JVM也不會回收。所以強引用是形成Java內存泄漏主要緣由之一。

軟引用

軟引用須要使用SoftReference類來實現,對於只有軟引用的對象來講,當系統內存足夠時他不會被回收,當系統內存足夠用時,它不會被回收,當系統內存不足時它會被回收。軟引用一般用在對內存敏感的程序中。

弱引用

弱引用須要用WeakReference類來實現,它比軟引用的生存期更短,對於只有弱引用的對象來講,只要垃圾回收機制一運行,無論JVM的內存空間足夠,總會回收該對象佔用的內存。

虛引用

**虛引用須要PhantomReference類來實現,它不能單獨使用,必須和引用隊列聯合使用。**虛引用的主要做用是跟蹤對象被垃圾回收的狀態。

相關文章
相關標籤/搜索