JVM與性能調(diào)優(yōu)

性能測(cè)試

在了解性能調(diào)優(yōu)之前,首先得知道什么是性能測(cè)試,我們的程序怎樣的性能表現(xiàn)才需要進(jìn)行性能調(diào)優(yōu)

一、性能測(cè)試概念

1.概念

用最低的資源換取最高的處理能力和低的響應(yīng)時(shí)間在一定環(huán)境下做性能需求

2.問(wèn)題
  • 環(huán)境很難真實(shí)
  • 需求一般很模糊
3.指標(biāo):
  • 響應(yīng)時(shí)間: 完成一個(gè)業(yè)務(wù)所需要的時(shí)間綜合(越短越好)
  • 吞吐量:?jiǎn)挝粫r(shí)間內(nèi)處理的業(yè)務(wù)數(shù)量(越多越好)
  • 資源利用率(CPU 、內(nèi)存、IO)

二、性能測(cè)試的標(biāo)準(zhǔn)

1.Tpc

給一個(gè)標(biāo)準(zhǔn)的業(yè)務(wù),比較完成業(yè)務(wù)的能力

2.Spec

物理處理能力,比較固定業(yè)務(wù)換算的指標(biāo)

三、性能測(cè)試的難點(diǎn)

1.用戶層面

用戶總希望在最小的代價(jià)下?lián)Q回最大的收益

2.代碼層面

項(xiàng)目一旦確定了架構(gòu),其性能也就確定了(開(kāi)發(fā)者不遵守規(guī)范體系進(jìn)行開(kāi)發(fā))

四、如何進(jìn)行性能測(cè)試

1.模擬用戶請(qǐng)求

模擬客戶端對(duì)服務(wù)端的多線程調(diào)用,使用Testng、jemeter等工具模擬高并發(fā)

2.性能測(cè)試工具的要求
  • 并發(fā)負(fù)載用戶
  • 參數(shù)化:避免緩存帶來(lái)的性能問(wèn)題
  • 關(guān)聯(lián):業(yè)務(wù)前后依賴
  • 事務(wù):通過(guò)函數(shù)來(lái)明確具體業(yè)務(wù)的時(shí)間范圍
  • 監(jiān)控:監(jiān)控負(fù)載和監(jiān)控資源

監(jiān)控負(fù)載可以計(jì)算出響應(yīng)時(shí)間和吞吐量

監(jiān)控資源的工具

  • jvm監(jiān)控工具:jrock、jmap、jprofile
  • zabbix
  • elk
  • Prometheus
  • top命令

五、性能測(cè)試模型

響應(yīng)時(shí)間隨著負(fù)載的上升先穩(wěn)定后上升,并且越來(lái)越快

TPS隨著負(fù)載的上升先到峰值,后穩(wěn)定,然后下降

TPS(Transction per second) 每秒處理請(qǐng)求的能力,從發(fā)起一次請(qǐng)求到服務(wù)器做出響應(yīng)的過(guò)程

QPS(Query per second)每秒查詢的次數(shù),一般針對(duì)一個(gè)特定的查詢,服務(wù)器在一秒中所處理的流量

TPS可以認(rèn)為是一種特殊的QPS

JVM相關(guān)概念

一、JVM是什么

jvm(java virtual machine) java虛擬機(jī),是保證java程序能夠在不同的操作系統(tǒng)上正常運(yùn)行的基礎(chǔ)。write once run everywhere

JRE(Java Runtime Envirment) java運(yùn)行環(huán)境,JRE中包含JVM

JDK通常我們?cè)陂_(kāi)發(fā)java項(xiàng)目所安裝的都是JDK,它包含了java類(lèi)庫(kù)以及JRE

Java程序在運(yùn)行的時(shí)候,首先將java文件編譯成class文件,JVM就負(fù)責(zé)將class文件解析成不同的操作系統(tǒng)所能理解的字節(jié)碼

二、為什么要學(xué)習(xí)JVM

Java開(kāi)發(fā)中,不需要管理對(duì)象的銷(xiāo)毀,JVM已經(jīng)幫我們處理好了,雖然在大多數(shù)情況下,我們不需要對(duì)JVM進(jìn)行任何操作,程序也能正常運(yùn)行,但是如果我們能夠了解類(lèi)是如何加載的,我們編寫(xiě)的java對(duì)象、方法是如何在JVM中運(yùn)行的,對(duì)象是如何進(jìn)行回收的,程序出現(xiàn)OOM問(wèn)題后我們?cè)撛趺崔k,那我們能開(kāi)發(fā)一個(gè)更健壯的系統(tǒng)。

三、JVM的具體構(gòu)成與原理

1.JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)
image.png

先分析進(jìn)程共享的區(qū)域

  • 堆:Java程序中所有的對(duì)象以及數(shù)組都在堆上進(jìn)行分配。
  • 方法區(qū):保存類(lèi)信息、常量、靜態(tài)變量、JIT編譯后的代碼。

在分析線程共享的區(qū)域

  • 虛擬機(jī)棧:線程執(zhí)行方法的區(qū)域,線程執(zhí)行方法的過(guò)程就是向虛擬機(jī)棧入棧和出棧的過(guò)程,每一個(gè)方法就是一個(gè)棧幀。虛擬機(jī)棧還包括:
    • 本地變量表:存放方法中的臨時(shí)變量,如果是引用對(duì)象,則存放其在堆中的實(shí)例地址的引用
    • 操作數(shù)棧:方法內(nèi)部進(jìn)行各種操作的指令
    • 動(dòng)態(tài)鏈接:把方法中的符號(hào)引用轉(zhuǎn)換為直接引用(類(lèi)加載中解析的過(guò)程是將靜態(tài)的符號(hào)引用轉(zhuǎn)換為直接引用)
    • 返回:每一個(gè)方法都有一個(gè)返回。
  • 本地方法棧:執(zhí)行本地方法的區(qū)域,其結(jié)構(gòu)與虛擬機(jī)棧類(lèi)似,只是執(zhí)行的是c/c++語(yǔ)言的方法
  • 程序計(jì)數(shù)器:保存當(dāng)前線程正在執(zhí)行的操作的指令的字節(jié)碼或者行號(hào)(CPU調(diào)度切換時(shí)使用)
2.類(lèi)加載

想要更好的理解JVM運(yùn)行時(shí)數(shù)據(jù)區(qū),必須了解這些數(shù)據(jù)是怎么加載到JVM中的。下面就介紹一下java中類(lèi)加載的過(guò)程。

java類(lèi)的加載主要分為3個(gè)步驟:加載連接和初始化,而連接又分為三個(gè)步驟:驗(yàn)證、準(zhǔn)備解析。具體來(lái)看一下每一個(gè)過(guò)程都做了那些事情。

  • 加載:
    • 根據(jù)類(lèi)的全限定名,讀取class文件中的二進(jìn)制數(shù)據(jù)流。
    • 將類(lèi)中的靜態(tài)數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
    • 在堆中創(chuàng)建一個(gè)java.lang.Class的對(duì)象作為訪問(wèn)這些數(shù)據(jù)的入口。
  • 驗(yàn)證:驗(yàn)證主要是驗(yàn)證類(lèi)的正確性。
    • 驗(yàn)證文件格式
    • 驗(yàn)證元數(shù)據(jù)
    • 驗(yàn)證字節(jié)碼
    • 驗(yàn)證符號(hào)引用
  • 準(zhǔn)備:將靜態(tài)變量在堆中進(jìn)行分配,并設(shè)置相應(yīng)對(duì)象的默認(rèn)值(類(lèi)的靜態(tài)變量保存在方法區(qū)中)
  • 解析:將類(lèi)中符號(hào)變量轉(zhuǎn)換為直接引用,這里會(huì)將一部分的符號(hào)引用轉(zhuǎn)化為直接引用。轉(zhuǎn)化這部分的方法調(diào)用必須是在程序運(yùn)行之前就有一個(gè)可以確定的調(diào)用版本。包括:靜態(tài)方法、私有方法、實(shí)例構(gòu)造方法、父類(lèi)方法。
  • 初始化:為在準(zhǔn)備階段的靜態(tài)變量進(jìn)行賦值(類(lèi)的其他成員變量會(huì)執(zhí)行構(gòu)造函數(shù)的時(shí)候,隨對(duì)象一起分配在內(nèi)存中)

符號(hào)引用:以一組符號(hào)來(lái)描述所引用的對(duì)象,可以是任何形式的字面量,只要在解析的時(shí)候能夠根據(jù)這個(gè)字面量無(wú)歧義的定位到目標(biāo)即可,能根據(jù)這個(gè)字符串定位到指定的數(shù)據(jù),比如java/lang/String

直接引用:直接指向目標(biāo)的指針、相對(duì)偏移量或者是一個(gè)間接定位的句柄

理解

這里簡(jiǎn)單分析一下類(lèi)加載之后,具體與JVM之間的關(guān)系以及JVM各個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)的聯(lián)系:

  • 類(lèi)加載之后將類(lèi)進(jìn)行拆分,把對(duì)應(yīng)的數(shù)據(jù)放在jvm運(yùn)行時(shí)數(shù)據(jù)區(qū)。
  • 一個(gè)類(lèi)初始化后,其對(duì)象頭中包括一個(gè)Class Pointer指向方法區(qū)中對(duì)應(yīng)的類(lèi)信息。
  • 虛擬機(jī)棧中的動(dòng)態(tài)連接就是把方法區(qū)中存放的方法的符號(hào)引用根據(jù)運(yùn)行時(shí)的狀態(tài)把其轉(zhuǎn)換為直接引用。


    image.png
3.GC回收

JVM內(nèi)存模型

先來(lái)看一下JVM內(nèi)存模型的圖

image.png

JDK1.8中內(nèi)存模型主要有一下幾個(gè)部分,簡(jiǎn)單說(shuō)明一下每個(gè)部分

  • JVM將整個(gè)堆分為兩個(gè)部分,新生代和老年代,其中新生代又分為Eden、S0S1區(qū)。
  • 對(duì)象的創(chuàng)建都在Eden區(qū)中進(jìn)行,S0S1是用來(lái)存放MinorGC后存活的對(duì)象。
  • 老年代用來(lái)存放新生代多次(默認(rèn)年齡是15,可以通過(guò)MaxTenuringThreshold修改)GC后存活的對(duì)象,或者是S0、S1存放不下的對(duì)象。

垃圾回收算法

首先先介紹一下,JVM如何判斷哪個(gè)對(duì)象是否需要回收,這里有兩種算法,一個(gè)是引用計(jì)數(shù)法,一個(gè)是可達(dá)性算法。

  • 引用計(jì)數(shù)法:對(duì)一個(gè)對(duì)象而言,只要程序中有持有該對(duì)象的引用,就把引用計(jì)數(shù)+1,釋放該對(duì)象就-1,當(dāng)該對(duì)象的引用計(jì)數(shù)為0時(shí),說(shuō)明該對(duì)象沒(méi)有被引用,可以被GC。缺點(diǎn):不能解決循環(huán)引用的問(wèn)題。finalize
  • 可達(dá)性算法:通過(guò)GCRoot對(duì)象,向下尋找,看某個(gè)對(duì)象是否可達(dá),如果不可達(dá),則可以被GC。(垃圾回收的時(shí)候會(huì)再調(diào)用finalize方法,可以在該方法中將該對(duì)象與GCRoot關(guān)聯(lián)。)

JVM中使用的可達(dá)性算法,那么有哪些對(duì)象可以作為GCRoot呢?

  • 虛擬機(jī)棧中本地變量表所引用的對(duì)象
  • 方法區(qū)中類(lèi)靜態(tài)變量引用的屬性
  • 本地方法棧中引用的對(duì)象
  • 方法區(qū)中常量引用的對(duì)象

方法區(qū)中的對(duì)象是隨著JVM進(jìn)程的存在而存在的,他不會(huì)被回收,虛擬機(jī)棧和本地方法棧的變量是當(dāng)前正在執(zhí)行的方法,變量也不會(huì)被回收,所以他們可以作為GCRoot。

1.標(biāo)記清除算法

找出內(nèi)存中需要回收的對(duì)象,并標(biāo)記出來(lái),然后清除他們。缺點(diǎn):會(huì)造成內(nèi)存不連續(xù)

2.標(biāo)記整理算法

找出內(nèi)存中需要回收的對(duì)象,并標(biāo)記出來(lái),然后把存活的對(duì)象向一邊移動(dòng),然后清空另一邊。缺點(diǎn):

3.復(fù)制回收算法

將內(nèi)存區(qū)域分為兩個(gè)部分,每次只使用一塊,當(dāng)一塊使用完了之后,將不需要回收的對(duì)象復(fù)制到另一塊內(nèi)存中。缺點(diǎn):內(nèi)存利用率低且如果有大量對(duì)象存活的時(shí)候,復(fù)制會(huì)消耗很多資源。

垃圾回收器

image.png
1.Serial/SerialOld垃圾收集器

單線程收集器,回收垃圾的時(shí)候會(huì)觸發(fā)STW。新生代使用復(fù)制回收算法,老年代使用標(biāo)記整理算法。

優(yōu)點(diǎn):簡(jiǎn)單高效。

缺點(diǎn)GC會(huì)暫停用戶線程。

使用場(chǎng)景:?jiǎn)魏薈PU

2.ParNew垃圾收集器

多線程收集器,回收垃圾的時(shí)候會(huì)觸發(fā)STW。新生代使用,采用復(fù)制回收算法。

優(yōu)點(diǎn):多CPU情況下,比Serial效率高。

缺點(diǎn):會(huì)觸發(fā)STW,單核CPU效率低。

使用場(chǎng)景Server模式下首選的新生代收集器。

3.Parallel Scavenge /Parallel Old垃圾收集器

ParNew一樣是多線程收集器,但是它更注重吞吐量(運(yùn)行用戶代碼的時(shí)間 / (運(yùn)行用戶代碼的時(shí)間 + 垃圾回收時(shí)間))

新生代使用復(fù)制回收算法,老年代使用標(biāo)記整理算法

4.CMS(Concurrent Mark Sweep)垃圾收集器

CMS是以獲取最短回收停頓時(shí)間為目標(biāo)的收集器 采用標(biāo)記清除算法,真?zhèn)€步驟分為:

  • 初始標(biāo)記(STW)
  • 并發(fā)標(biāo)記(并發(fā))
  • 重新標(biāo)記(STW)
  • 并發(fā)清除(并發(fā))

整個(gè)過(guò)程中,并發(fā)標(biāo)記和并發(fā)清除可以和用戶線程一起執(zhí)行,降低了回收停頓的時(shí)間。

優(yōu)點(diǎn):并發(fā)收集,低停頓

缺點(diǎn):產(chǎn)生大量的空間碎片,并發(fā)階段會(huì)降低吞吐量

5.G1垃圾收集器

JDK7中開(kāi)始使用,新生代和老年代使用同一個(gè)垃圾回收器。使用該垃圾收集器的時(shí)候,Java內(nèi)存布局和其他收集器有很大的區(qū)別,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region),新生代和老年代不再是物理隔離了,他們都是一部分Region

其過(guò)程可一分為下面幾步:

  • 初始標(biāo)記
  • 并發(fā)標(biāo)記
  • 最終標(biāo)記
  • 篩選回收 對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的GC停頓時(shí)間制定回收計(jì)劃。
    image.png

總結(jié):

上面所列舉的垃圾收集器可以進(jìn)行簡(jiǎn)要分類(lèi)

  • 串行收集器:SerialSerial Old 適用于內(nèi)存比較小的嵌入式設(shè)備。
  • 并行收集器[吞吐量?jī)?yōu)先]: Parallel ScavangeParallel Old多條垃圾回收線程并行工作,適合多CPU條件。
  • 并發(fā)收集器[停頓時(shí)間優(yōu)先]:CMSG1 用戶線程和垃圾收集線程同時(shí)執(zhí)行(但不一定是并行的,可能交替執(zhí)行),垃圾收集線程執(zhí)行的時(shí)候不會(huì)停頓用戶線程,適用于對(duì)時(shí)間要求比較高的場(chǎng)景,比如Web應(yīng)用。
image.png

四、如何對(duì)JVM進(jìn)行調(diào)優(yōu)

1.常用參數(shù)

在上面已經(jīng)介紹了有關(guān)JVM的內(nèi)存結(jié)構(gòu)以及垃圾回收等相關(guān)的知識(shí),那么對(duì)于我們開(kāi)發(fā)者來(lái)說(shuō),如何去設(shè)置這些參數(shù)呢?下面的表格里,我列出了應(yīng)該算是比較常用的一些命令。根據(jù)這些命令,我們可以很輕松的在IDE或者Tomcat中去配置這些參數(shù)。

image.png

2.常用命令

常用的查看JVM相關(guān)數(shù)據(jù)的命令有以下幾個(gè):

  • jps 查看當(dāng)前運(yùn)行的java進(jìn)程,jps -l 可以打印程序的全路徑
image.png
  • jstat -gc/class/compiler/gcutil pid interval count 查看當(dāng)前pid的gc信息、class信息、編譯信息、gc匯總等,interval 表示每隔多少毫秒輸出一次,count表示總共輸出幾次
image.png
  • jinfo -flag MaxHeapSize pid 查看當(dāng)前進(jìn)程的最大堆內(nèi)存大小
image.png
  • jmap -heap pid 打印當(dāng)前進(jìn)程的所有堆棧信息,還可以使用jmap -dump:formate=b ,file=heap.hprof pid導(dǎo)出當(dāng)前的堆棧信息到指定文件中。
image.png
  • jstack pid 打印當(dāng)前pid所有的線程信息

3.常用工具

  • jconsole pid 打開(kāi)一個(gè)工具并且連接到當(dāng)前的java進(jìn)程,可以在工具中查看當(dāng)前堆、線程等一些信息

image.png
  • jvisualvm 控制臺(tái)輸入該命令后后會(huì)啟動(dòng)一個(gè)客戶端,在左側(cè)列表和選擇本地的進(jìn)程進(jìn)行連接
image.png

那么當(dāng)我們的項(xiàng)目出現(xiàn)OutOfMemeryError或者StackOfFlowError等錯(cuò)誤的時(shí)候如何定位到問(wèn)題呢?

  • 第一種辦法是我們上面提到的jmap命令,它可以dump出當(dāng)前java進(jìn)程的堆棧信息,然后進(jìn)行分析。
  • 第二種辦法就是在啟動(dòng)java進(jìn)程的時(shí)候設(shè)置一些參數(shù),讓jvm能夠在發(fā)生異常的時(shí)候輸出hprof文件到本地。
    • -XX:+HeapDumpOnOutOfMemoryError
    • -XX:HeapDumpPath=dump.hprof

得到hprof文件后我們需要借助一些工具來(lái)進(jìn)行分析,這里推薦Eclipse Memory Analyzer工具(下載地址),安裝好之后直接open file打開(kāi)hprof后就會(huì)自動(dòng)對(duì)其進(jìn)行分析。

image.png

可以看到這里提供了很多的工具,你可以具體的查看來(lái)分析可能的問(wèn)題。

4.總結(jié)

對(duì)于JVM的調(diào)優(yōu),沒(méi)有一個(gè)確定的辦法,只能根據(jù)具體的問(wèn)題做出具體的分析。但是只要你對(duì)JVM內(nèi)存模型、垃圾收集等具體的原理了解清楚,當(dāng)出現(xiàn)問(wèn)題的時(shí)候,你就知道該從哪里下手,如何能快速的定位到問(wèn)題。

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 內(nèi)存溢出和內(nèi)存泄漏的區(qū)別 內(nèi)存溢出:out of memory,是指程序在申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存空間供其使用,...
    Aimerwhy閱讀 799評(píng)論 0 1
  • Java和C++之間有一堵由內(nèi)存動(dòng)態(tài)分配和垃圾收集技術(shù)所圍成的“高墻”,墻外面的人想進(jìn)來(lái),墻里面的人想出來(lái)。 對(duì)象...
    胡二囧閱讀 1,321評(píng)論 0 4
  • 又是一年秋招季,哎呀媽呀我被虐的慘來(lái)~這不,前幾陣失蹤沒(méi)更新博客,其實(shí)是我偷偷把時(shí)間用在復(fù)習(xí)課本了(霧 堅(jiān)持在社區(qū)...
    tengshe789閱讀 2,149評(píng)論 0 8
  • 涌baby閱讀 249評(píng)論 0 0
  • 1,今天的時(shí)間觀念非常強(qiáng),每個(gè)時(shí)間段的安排都非常的合理。 2,經(jīng)常翻看自己的計(jì)劃,提醒自己還有哪些事情沒(méi)有做。 3...
    花兒的書(shū)房閱讀 467評(píng)論 0 0

友情鏈接更多精彩內(nèi)容