性能優化系列二:JVM概念及配置

1、虛擬機組成

 

虛擬機主要由三部分組成:編譯器(執行引擎),堆與棧。java

1. 編譯器

編譯器分爲即時編譯器與解釋器。算法

即時編譯器將代碼編譯成本地代碼存於code區。所以它快,但它有內存限制!服務器

解釋器逐行解釋字節碼,至關於腳本順序執行,很慢,性能約爲C語言的80%。優化的一部分是使代碼儘早進入編譯器。將部分代碼內聯(函數散開於代碼中,與編譯器無關)。多線程

2. 棧

棧是JVM的函數棧。全部函數必分配於棧。棧中一個幀就是一個函數,因函數之間互相調用,棧幀中包含參數,返回地址,返回值等。第一個參數必然是this指針。遞歸函數會造成大量的棧幀,搞很差會溢出了。棧的大小能夠配置,太大並很差。它是惟一能夠配置的地方,其它就不可優化了。併發

3. 堆

堆是優化的重點,理解爲全部對象在堆中。堆劃分爲年輕代,老年代,持久代(java8metaspace-使用系統內存,而不是虛擬機堆內存,再也不是perm,也就不是持久代了)。年輕代又分爲倖存區兩個或多個(其中一個必空)及eden(伊甸園區)。app

初始的對象在伊甸園區,通過GC收集後,進入存活區,已被收集的對象固然哪也不去,被銷燬了。存活區通過15次收集還存活,則進入老年代。存活區滿了後交換存活區,並清空原存活區。函數

老年代最大,因它要放置大對象及長久不消失的對象。工具

從以上能夠看出來堆的優化,就是合理的設置堆內存空間的大小,使之少發生GC,不浪費,不擁擠,不抖動。性能

JVM的參數分爲-X-XX-D三種類型。虛擬機自身劃分爲客戶端與server模式。咱們固然要用server模式。server模式中編譯器默認爲混合模式,也就是即時編譯器JIT與解釋器混合使用。不須要改它。優化

2、運行原理

1. 執行引擎中的編譯器,解釋器執行流程

說明:

編譯流程把咱們編寫的源代碼通過詞法分析器、語法分析器、語義分析器、字節碼生成器處理後最終生成JVM的字節碼

執行引擎的編譯器和解釋器獲取到編譯好的JVM字節碼之後執行

編譯器會對JVM的字節碼進行機器無關的優化(如將字符串的拼接優化爲append方式)、機器相關優化、寄存器分配器(將JVM字節碼優化爲計算機的邏輯門)存放於code區運行

解釋器是一行一行的取出代碼來執行,性能很慢

2. 內存分配-線程模型

 

說明:

在Java中一個線程就會相應有一個線程棧與之對應。

堆是全部線程共享的。

棧是運行單位,信息都是跟當前線程(或程序)相關信息的。包括局部變量、程序運行狀態、方法返回值等等;

而堆只負責存儲對象信息。

棧幀模型

 

棧幀中包含方法索引、輸入輸出參數、本地變量、返回地址,返回值等。第一個參數必然是this指針。遞歸函數會造成大量的棧幀,搞很差會溢出了。棧的大小能夠配置,太大並很差。它是惟一能夠配置的地方,其它就不可優化了。

3、內存管理

1. 內存分配-堆區介紹

說明:

堆是優化的重點,理解爲全部對象在堆中。堆劃分爲年輕代,老年代,持久代(java8是metaspace-使用系統內存,而不是虛擬機堆內存,再也不是perm,也就不是持久代了)。年輕代又分爲倖存區兩個或多個(其中一個必空)及eden(伊甸園區)。

初始的對象在伊甸園區,通過GC收集後,進入存活區,已被收集的對象固然哪也不去,被銷燬了。存活區通過15次收集還存活,則進入老年代。存活區滿了後交換存活區,並清空原存活區。

老年代最大,因它要放置大對象及長久不消失的對象。

注意

 Eden區滿了進行的是minor GC,老年代滿了進行full GC,進行full GC時整個系統會出現卡頓現象,因此堆優化的重點是合理配置堆空間,減小GC,尤爲是full GC

2. 堆內存分配

 

Perm 默認大小64M
NewRatio配比
SurvivorRatio配比
Xmx,Xms,Xmn

4、垃圾回收策略

1. 收集器

1.Serial GC。年輕代使用的收集器,單線程,全部的線程暫停(stop the world)。通常用於Client模式的JVM中。

2.ParNew GC。年輕代使用的收集器,是在SerialGC的基礎上,增長了多線程機制。

3.Parrallel Scavenge GC。年輕代使用的收集器,吞吐量優先收集器,吞吐量=程序運行時間/(JVM執行回收的時間+程序運行時間), 運行100分鐘,GC佔用1分鐘,吞吐量=99%。server模式JVM默認配置。

4.ParallelOld。老年代使用的收集器,使用了標記整理算法,是JDK1.6中引進的。

5.Serial Old。老年代使用的收集器,CMS收集器失敗後的備用收集器。

6.CMS。老年代使用的收集器,又稱響應時間優先回收器,使用標記清除算法。他的回收線程數爲(CPU核心數+3)/4,因此當CPU核心數爲2時比較高效些。CMS分爲4個過程:初始標記、併發標記、從新標記、併發清除

7. G1收集器。年輕代和老年代都使用的收集器,屬於現代收集器。面向服務器- server、多CPU,多核、並行與併發、創建可預測的停頓時間模型

2. 肯定垃圾-可達性分析

在主流的商用程序語言中(Java和C#),都是使用可達性分析算法判斷對象是否存活的。

這個算法的基本思路就是經過一系列名爲GC Roots的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。

下圖對象obj8, obj9, obj10它們到GC Roots是不可達的,因此它們將會斷定爲是可回收對象。

 

3. GC算法

3.一、標記-清除(Mark-Sweep)算法

標記-清除算法將垃圾回收分爲兩個階段:

①.標記階段:首先標記出全部須要回收的對象。

②.清除階段:標記完成後,統一回收被標記的對象

缺點:

①.效率問題:標記清除過程效率都不高。

②.空間問題:標記清除以後會產生大量的不連續的內存碎片(空間碎片太多可能會致使之後在程序運行過程當中須要分配較大的對象時,沒法找到足夠的連續的內存空間而不得不提早觸發另外一次垃圾收集動做。)

 

3.2 、標記-整理(Mark-Compact)算法

1).標記階段:首先標記出全部須要回收的對象。與「標記-清除」同樣

2).讓存活的對象向內存的一段移動。而不跟「標記-清除」直接對可回收對象進行清理

3).再清理掉邊界之外的內存。

因爲老年代存活率高,沒有額外內存對老年代進行空間擔保,那麼老年代只能採用標記-清理算法或者標記整理算法。

 

3.3 複製(Copying)算法

3.1.算法思想:
1).將現有的內存空間分爲兩塊,每次只使用一塊.

2).當其中一塊用完的時候,就將還存活的對象複製到另一塊上去。

3).再把已使用過的內存空間一次清理掉。

3.2.優勢:

1).因爲是每次都對整個半區進行內存回收,內存分配時沒必要考慮內存碎片問題。

2).只要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。

3.3.缺點:

1).內存減小爲原來的一半,太浪費了。

2).對象存活率較高的時候就要執行較多的複製操做,效率變低。

3).若是不使用50%的對分策略,老年代須要考慮的空間擔保策略。

 

 

3. 四、分代收集算法

以上三種算法的綜合:

在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,選用:複製算法

在老年代中由於對象存活率高、沒有額外空間對它進行分配擔保,就必須使用「標記-清除」或者「標記-整理」算法來進行回收。

4. 工具

•jps

•jmap
•Jstat•Jvisualvm: window下啓動遠程監控,並在被監控服務端,啓動jstatd服務。

4.1 jmap工具使用結果說明

 命令格式:

jmap [option] <pid>
(to connect to running process)
jmap [option] <executable <core>
(to connect to a core file)
jmap [option] [server_id@]<remote server IP or hostname>
(to connect to remote debug server)

可選項:

where <option> is one of:
<none> to print same info as Solaris pmap
-heap to print java heap summary
-histo[:live] to print histogram of java object heap; if the "live"
suboption is specified, only count live objects
-clstats to print class loader statistics
-finalizerinfo to print information on objects awaiting finalization
-dump:<dump-options> to dump java heap in hprof binary format
dump-options:
live dump only live objects; if not specified,
all objects in the heap are dumped.
format=b binary format
file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
-F force. Use with -dump:<dump-options> <pid> or -histo
to force a heap dump or histogram when <pid> does not
respond. The "live" suboption is not supported
in this mode.
-h | -help to print this help message
-J<flag> to pass <flag> directly to the runtime system

使用示例:

jmap -heap 11892 其中11892是java程序的進程id

運行結果:

Heap Configuration: #堆內存初始化配置
        MinHeapFreeRatio = 40 #-XX:MinHeapFreeRatio設置JVM堆最小空閒比率
        MaxHeapFreeRatio = 70 #-XX:MaxHeapFreeRatio設置JVM堆最大空閒比率
        MaxHeapSize = 100663296 (96.0MB) #-XX:MaxHeapSize=設置JVM堆的最大大小
        NewSize = 1048576 (1.0MB) #-XX:NewSize=設置JVM堆的‘新生代’的默認大小
        MaxNewSize = 4294901760 (4095.9375MB) #-XX:MaxNewSize=設置JVM堆的‘新生代’的最大大小
        OldSize = 4194304 (4.0MB) #-XX:OldSize=設置JVM堆的‘老生代’的大小
        NewRatio = 2 #-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
        SurvivorRatio = 8 #-XX:SurvivorRatio=設置年輕代中Eden區與Survivor區的大小比值
        PermSize = 12582912 (12.0MB) #-XX:PermSize=<value>:設置JVM堆的‘持久代’的初始大小
        MaxPermSize = 67108864 (64.0MB) #-XX:MaxPermSize=<value>:設置JVM堆的‘持久代’的最大大小
Heap Usage:
        New Generation (Eden + 1 Survivor Space): #新生代區內存分佈,包含伊甸園區+1個Survivor區
        capacity = 30212096 (28.8125MB)
        used = 27103784 (25.848182678222656MB)
        free = 3108312 (2.9643173217773438MB)
        89.71169693092462% used
Eden Space: #Eden區內存分佈
        capacity = 26869760 (25.625MB)
        used = 26869760 (25.625MB)
        free = 0 (0.0MB)
        100.0% used
From Space: #其中一個Survivor區的內存分佈
        capacity = 3342336 (3.1875MB)
        used = 234024 (0.22318267822265625MB)
        free = 3108312 (2.9643173217773438MB)
        7.001809512867647% used
To Space: #另外一個Survivor區的內存分佈
        capacity = 3342336 (3.1875MB)
        used = 0 (0.0MB)
        free = 3342336 (3.1875MB)
        0.0% used
tenured generation: #當前的Old區內存分佈
        capacity = 67108864 (64.0MB)
        used = 67108816 (63.99995422363281MB)
        free = 48 (4.57763671875E-5MB)
        99.99992847442627% used
Perm Generation: #當前的 「持久代」 內存分佈(java8已經沒有持久代的概念了)
        capacity = 14417920 (13.75MB)
        used = 14339216 (13.674942016601562MB)
        free = 78704 (0.0750579833984375MB)
        99.45412375710227% used
相關文章
相關標籤/搜索