java jvm 原理


JVM 原理

  1. JVM簡介

    1
    2
    3
    JVM是虛擬機,也是一種規范,他遵循着馮·諾依曼體系結構的設計原理(馮·諾依曼體系結構中,指出計算機處理的數據和指令都是二進制數,采用存儲程序方式不加區分的存儲在同一個存儲器里,並且順序執行,指令由操作碼和地址碼組成,操作碼決定了操作類型和所操作的數的數字類型,地址碼則指出地址碼和操作數).

    從DOS到window8,從unix到ubuntu和CentOS,還有MAC OS等等,不同的操作系統指令集以及數據結構都有着差異,而JVM通過在操作系統上建立虛擬機,自己定義出來的一套統一的數據結構和操作指令,把同一套語言翻譯給各大主流的操作系統,實現了跨平台運行,可以說JVM是Java的核心,是java可以一次編譯到處運行的本質所在.
  2. JVM的組成和運行原理

    1
    2
    3
    4
    5
    JVM的畢竟是個虛擬機,是一種規范,雖說符合馮諾依曼的計算機設計理念,但是他並不是實體計算機,所以他的組成也不是什么存儲器,控制器,運算器,輸入輸出設備.

    在我看來,JVM放在運行在真實的操作系統中表現的更像應用或者說是進程,他的組成可以理解為JVM這個進程有哪些功能模塊,而這些功能模塊的運作可以看做是JVM的運行原理.

    JVM有多種實現,例如: Oracle的JVM,HP的JVM和IBM的JVM等,而在本文中研究學習的則是使用最廣泛的Oracle的HotSpot JVM.
  3. JVM在JDK中的位置

    1
    2
    JDK是Java開發的必備工具箱,JDK其中有一部分是JRE,JRE是JAVA運行環境,JVM則是JRE最核心的部分.
    如圖:JDK Standard Edtion

JDK Standard Edtion

1
2
3
4
5
6
7
8
9
10
11
12
13
從最底層的位置可以看出來JVM有多重要,而實際項目中JAVA應用的性能優化,OOM等異常的處理最終都得從JVM這兒來解決

HotSpot是Oracle關於JVM的商標,區別於IBM,HP等廠商開發的JVM

Java HotSpot Client VM和Java HotSpot Server VM是JDK關於JVM的兩種不同的實現,前者可以減少啟動時間和內存占用,而后者則提供更加優秀的程序運行速度

例如:
[root@10 ~]# java -version
java version "1.8.0_73"
Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)

HotSpot的版本號是25.73-b02,類型是Java HotSpot Server VM

  1. JVM的組成
    1
    JVM由4大部分組成: ClassLoader,Runtime Data Area,Execution Engine,Native Interface

jvm.組成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
JVM組成部分說明:
* ClassLoader
ClassLoader是負責加載class文件,class文件在文件開頭有特定的文件標示,並且ClassLoader只負責class文件的加載,至於它是否可以運行,則由Execution Engine決定

* Native Interface
Native Interface是負責調用本地接口的.它的作用是調用不同語言的接口給JAVA用,他會在Native Method Stack中記錄對應的本地方法,然后調用該方法時就通過Execution Engine加載對應的本地lib

* Execution Engine
Execution Engine是執行引擎,也叫Interpreter.Class文件被加載后,會把指令和數據信息放入內存中,Execution Engine則負責把這些命令解釋給操作系統

* Runtime Data Area
Runtime Data Area則是存放數據的,分為五部分:Stack,Heap,Method Area,PC Register,Native Method Stack.幾乎所有的關於java內存方面的問題,都是集中在這塊
* Stack
Stack是java棧內存,它等價於C語言中的棧,棧的內存地址是不連續的,每個線程都擁有自己的棧.
棧里面存儲着的是StackFrame,在《JVM Specification》中文版中被譯作java虛擬機框架,也叫做棧幀.
StackFrame包含三類信息: 局部變量,執行環境,操作數棧
* 局部變量用來存儲一個類的方法中所用到的局部變量
* 執行環境用於保存解析器對於java字節碼進行解釋過程中需要的信息,包括:上次調用的方法,局部變量指針和操作數棧的棧頂和棧底指針
* 操作數棧用於存儲運算所需要的操作數和結果
StackFrame在方法被調用時創建,在某個線程中,某個時間點上,只有一個框架是活躍的,該框架被稱為Current Frame,而框架中的方法被稱為Current Method,其中定義的類為Current Class.局部變量和操作數棧上的操作總是引用當前框架.當Stack Frame中方法被執行完之后,或者調用別的StackFrame中的方法時,則當前棧變為另外一個StackFrame.Stack的大小是由兩種類型:固定和動態的,動態類型的棧可以按照線程的需要分配
* Heap
Heap是用來存放對象信息的,和Stack不同,Stack代表着一種運行時的狀態.換句話說,棧是運行時單位,解決程序該如何執行的問題,而堆是存儲的單位,解決數據存儲的問題.
Heap是伴隨着JVM的啟動而創建,負責存儲所有對象實例和數組的.堆的存儲空間和棧一樣是不需要連續的,它分為Young Generation和Old Generation(也叫Tenured Generation)兩大部分.Young Generation分為Eden和Survivor,Survivor又分為From Space和 ToSpace.

和Heap經常一起提及的概念是PermanentSpace,它是用來加載類對象的專門的內存區,是非堆內存,和Heap一起組成JAVA內存,它包含MethodArea區(在沒有CodeCache的HotSpotJVM實現里,則MethodArea就相當於GenerationSpace)

在JVM初始化的時候,我們可以通過參數來分別指定,PermanentSpace的大小,堆的大小,以及Young Generation和Old Generation的比值,Eden區和From Space的比值,從而來細粒度的適應不同JAVA應用的內存需求
* PC Register
PC Register是程序計數寄存器,每個JAVA線程都有一個單獨的PC Register,他是一個指針,由Execution Engine讀取下一條指令.如果該線程正在執行java方法,則PC Register存儲的是正在被執行的指令的地址;如果是本地方法,PC Register的值沒有定義.PC寄存器非常小,只占用一個字寬,可以持有一個returnAdress或者特定平台的一個指針
* Method Area
Method Area在HotSpot JVM的實現中屬於非堆區,非堆區包括兩部分:Permanet Generation和Code Cache,而Method Area屬於Permanert Generation的一部分.
Permanent Generation用來存儲類信息,比如說:class definitions,structures,methods,field,method(data and code)和constants.
Code Cache用來存儲Compiled Code,即編譯好的本地代碼,在HotSpot JVM中通過JIT(Just In Time)Compiler生成,JIT是即時編譯器,他是為了提高指令的執行效率,把字節碼文件編譯成本地機器代碼
* Native Method Stack
Native Method Stack是供本地方法(非java)使用的棧.每個線程持有一個Native Method Stack

  1. JVM的運行原理簡介

    1
    2
    3
    4
    Java程序被javac工具編譯為.class字節碼文件之后,我們執行java命令,該class文件便被JVM的Class Loader加載,可以看出JVM的啟動是通過JAVA Path下的java.exe或者java進行的.

    JVM的初始化,運行到結束大概包括這么幾步:
    調用操作系統API判斷系統的CPU架構,根據對應CPU類型尋找位於JRE目錄下的/lib/jvm.cfg文件,然后通過該配置文件找到對應的jvm.dll文件(如果我們參數中有-server或者-client,則加載對應參數所指定的jvm.dll,啟動指定類型的JVM),初始化jvm.dll並且掛接到JNIENV結構的實例上,之后就可以通過JNIENV實例裝載並且處理class文件了.class文件是字節碼文件,它按照JVM的規范,定義了變量,方法等的詳細信息,JVM管理並且分配對應的內存來執行程序,同時管理垃圾回收.直到程序結束,一種情況是JVM的所 大專欄  java jvm 原理有非守護線程停止,一種情況是程序調用System.exit(),JVM的生命周期也結束
  2. JVM的內存管理

    1
    JVM中的內存管理主要是指JVM對於Heap的管理,這是因為Stack,PC Register和Native Method Stack都是和線程一樣的生命周期,在線程結束時自然可以被再次使用.雖然說Stack的管理不是重點,但是也不是完全不講究的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    * 棧的管理
    JVM允許棧的大小是固定的或者是動態變化的.Stack的設置是通過-Xss來設置其大小.

    我們一般通過減少常量,參數的個數來減少棧的增長,在程序設計時,我們把一些常量定義到一個對象中,然后來引用他們可以體現這一點.另外,少用遞歸調用也可以減少棧的占用

    棧是不需要垃圾回收的,盡管說垃圾回收是java內存管理的一個很熱的話題,棧中的對象如果用垃圾回收的觀點來看,他永遠是live狀態,是可以reachable的,所以也不需要回收,他占有的空間隨着Thread的結束而釋放

    另外棧上有一點得注意的是,對於本地代碼調用,可能會在棧中申請內存,比如C調用malloc(),而這種情況下,GC是管不着的,需要我們在程序中,手動管理棧內存,使用free()方法釋放內存

    關於棧一般會發生以下兩種異常:
    1.當線程中的計算所需要的棧超過所允許大小時,會拋出StackOverflowError
    2.當Java棧試圖擴展時,沒有足夠的存儲器來實現擴展,JVM會報OutOfMemoryError
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    * 堆的管理
    堆的管理要比棧管理復雜的多,我通過堆的各部分的作用,設置,以及各部分可能發生的異常,以及如何避免各部分異常

    下圖是Heap和PermanentSapce的組合圖,其中Eden區里面存着是新生的對象,From Space和To Space中存放着是每次垃圾回收后存活下來的對象,所以每次垃圾回收后,Eden區會被清空.存活下來的對象先是放到From Space,當From Space滿了之后移動到To Space.當To Space滿了之后移動到Old Space.

    Survivor的兩個區是對稱的,沒先后關系,所以同一個區中可能同時存在從Eden復制過來對象,和從前一個Survivor復制過來的對象,而復制到年老區的只有從第一個Survivor復制過來的對象.而且,Survivor區總有一個是空的.同時,根據程序需要,Survivor區是可以配置為多個的(多於兩個),這樣可以增加對象在年輕代中的存在時間,減少被放到年老代的可能

    Old Space中則存放生命周期比較長的對象,而且有些比較大的新生對象也放在Old Space中

    堆的大小通過-Xms和-Xmx來指定最小值和最大值,通過-Xmn來指定Young Generation的大小(一些老版本也用-XX:NewSize指定(即下圖中的Eden加FromSpace和ToSpace的總大小)),然后通過-XX:NewRatio來指定Eden區的大小,在Xms和Xmx相等的情況下,該參數不需要設置.通過-XX:SurvivorRatio來設置Eden和一個Survivor區的比值

    堆異常分為兩種:
    1.Out of Memory(OOM)
    2.Memory Leak(ML)
    Memory Leak最終將導致OOM.實際應用中表現為:從Console看,內存監控曲線一直在頂部,程序響應慢,從線程看,大部分的線程在進行GC,占用比較多的CPU,最終程序異常終止,報OOM.OOM發生的時間不定,有短的一個小時,有長的10天一個月的.
    關於異常的處理,確定OOM/ML異常后,一定要注意保護現場,可以dump heap,如果沒有現場則開啟GCFlag收集垃圾回收日志,然后進行分析,確定問題所在.如果問題不是ML的話,一般通過增加Heap,增加物理內存來解決問題,是的話,就修改程序邏輯

jvm.heap

  1. JVM垃圾回收
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    JVM中會在以下情況觸發回收: 對象沒有被引用,作用域發生未捕捉異常,程序正常執行完畢,程序執行了System.exit(),程序發生意外終止

    JVM中標記垃圾使用的算法是一種根搜索算法.簡單的說,就是從一個叫GC Roots的對象開始,向下搜索,如果一個對象不能達到GC Roots對象的時候,說明它可以被回收了.這種算法比一種叫做引用計數法的垃圾標記算法要好,因為它避免了當兩個對象啊互相引用時無法被回收的現象

    JVM中對於被標記為垃圾的對象進行回收時又分為了一下3種算法:
    * 標記清除算法
    該算法是從根集合掃描整個空間,標記存活的對象,然后在掃描整個空間對沒有被標記的對象進行回收,這種算法在存活對象較多時比較高效,但會產生內存碎片
    * 復制算法
    該算法是從根集合掃描,並將存活的對象復制到新的空間,這種算法在存活對象少時比較高效
    * 標記整理算法
    標記整理算法和標記清除算法一樣都會掃描並標記存活對象,在回收未標記對象的同時會整理被標記的對象,解決了內存碎片的問題

    JVM中,不同的內存區域作用和性質不一樣,使用的垃圾回收算法也不一樣,所以JVM中又定義了幾種不同的垃圾回收器:
    * Serial GC
    從名字上看,串行GC意味着是一種單線程的,所以它要求收集的時候所有的線程暫停.這對於高性能的應用是不合理的,所以串行GC一般用於Client模式的JVM中
    * ParNew GC
    是在Serial GC的基礎上,增加了多線程機制.但是如果機器是單CPU的,這種收集器是比Serial GC效率還低
    * Parrallel Scavenge GC
    這種收集器又叫吞吐量優先收集器,而吞吐量=程序運行時間/(JVM執行回收的時間+程序運行時間),假設程序運行了100分鍾,JVM的垃圾回收占用1分鍾,那么吞吐量就是99%.Parallel Scavenge GC由於可以提供比較不錯的吞吐量,所以被作為了server模式JVM的默認配置
    * ParallelOld
    ParallelOld是老生代並行收集器的一種,使用了標記整理算法,是JDK1.6中引進的,在之前老生代只能使用串行回收收集器
    * Serial Old
    Serial Old是老生代client模式下的默認收集器,單線程執行,同時也作為CMS收集器失敗后的備用收集器
    * CMS
    CMS又稱響應時間優先回收器,使用標記清除算法.他的回收線程數為(CPU核心數+3)/4,所以當CPU核心數為2時比較高效些.CMS分為4個過程:初始標記,並發標記,重新標記,並發清除
    * GarbageFirst(G1)
    比較特殊的是G1回收器既可以回收Young Generation,也可以回收Tenured Generation.它是在JDK6的某個版本中才引入的,性能比較高,同時注意了吞吐量和響應時間


    默認的GC種類可以通過jvm.cfg或者通過jmap dump出heap來查看,一般我們通過jstat -gcutil [pid] 1000可以查看每秒gc的大體情況,或者可以在啟動參數中加入:-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log來記錄GC日志

    GC中有一種情況叫做Full GC,以下幾種情況會觸發Full GC也叫MajorGC:
    * Tenured Space空間不足以創建打的對象或者數組,會執行FullGC,並且當FullGC之后空間如果還不夠,那么會OOM:java heap space
    * Permanet Generation的大小不足,存放了太多的類信息,在非CMS情況下回觸發FullGC.如果之后空間還不夠,會OOM:PermGen space。
    * CMS GC時出現promotion failed和concurrent mode failure時,也會觸發FullGC.promotion failed是在進行Minor GC時,survivor space放不下,對象只能放入舊生代,而此時舊生代也放不下造成的;concurrent mode failure是在執行CMS GC的過程中同時有對象要放入舊生代,而此時舊生代空間不足造成的
    * 判斷MinorGC后,要晉升到TenuredSpace的對象大小大於TenuredSpace的大小,也會觸發FullGC
    可以看出,當FullGC頻繁發生時,一定是內存出問題了


    注意: 下圖中連線代表兩個回收器可以同時使用

jvm.gc


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM