JVM性能優化

運行時優化

方法內聯

方法內聯,是指 JVM在運行時將調用次數達到一定閾值的方法調用替換為方法體本身 ,從而消除調用成本,并為接下來進一步的代碼性能優化提供基礎,是JVM的一個重要優化手段之一。

注:

  • C++的inline屬于編譯后內聯,但是java是運行時內聯

簡單通俗的講就是把方法內部調用的其它方法的邏輯,嵌入到自身的方法中去,變成自身的一部分,之后不再調用該方法,從而節省調用函數帶來的額外開支。

為什么會出現方法內聯呢?

之所以出現方法內聯是因為(方法調用)函數調用除了執行自身邏輯的開銷外,還有一些不為人知的額外開銷。 這部分額外的開銷主要來自方法棧幀的生成、參數字段的壓入、棧幀的彈出、還有指令執行地址的跳轉 。比如有下面這樣代碼:

public static void function_A(int a, int b){//do somethingfunction_B(a,b);}public static void function_B(int c, int d){//do something}public static void main(String[] args){function_A(1,2);}

則代碼的執行過程如下:

image.png

所以如果java中方法調用嵌套過多或者方法過多,這種額外的開銷就越多。

試想一下想get/set這種方法調用:

public int getI() {return i;}public void setI(int i) {this.i = i;}

很可能自身執行邏輯的開銷還比不上為了調用這個方法的額外開鎖。如果類似的方法被頻繁的調用,則真正相對執行效率就會很低,雖然這類方法的執行時間很短。這也是為什么jvm會在熱點代碼中執行方法內聯的原因,這樣的話就可以省去調用調用函數帶來的額外開支。

這里舉個內聯的可能形式:

 public int  add(int a, int b , int c, int d){return add(a, b) + add(c, d);}public int add(int a, int b){return a + b;}

內聯之后:

public int  add(int a, int b , int c, int d){return a + b + c + d;}

內聯條件

一個方法如果滿足以下條件就很可能被jvm內聯。

  • 熱點代碼。 如果一個方法的執行頻率很高就表示優化的潛在價值就越大。那代碼執行多少次才能確定為熱點代碼?這是根據編譯器的編譯模式來決定的。如果是客戶端編譯模式則次數是1500,服務端編譯模式是10000。次數的大小可以通過-XX:CompileThreshold來調整。
  • 方法體不能太大。jvm中被內聯的方法會編譯成機器碼放在code cache中。如果方法體太大,則能緩存熱點方法就少,反而會影響性能。熱點方法小于325字節的時候,非熱點代碼35字節以下才會使用這種方式
  • 如果希望方法被內聯, 盡量用private、static、final修飾 ,這樣jvm可以直接內聯。如果是public、protected修飾方法jvm則需要進行類型判斷,因為這些方法可以被子類繼承和覆蓋,jvm需要判斷內聯究竟內聯是父類還是其中某個子類的方法。

所以了解jvm方法內聯機制之后,會有助于我們工作中寫出能讓jvm更容易優化的代碼,有助于提升程序的性能。

逃逸分析

什么是“對象逃逸”?

對象逃逸的本質是對象指針的逃逸。

在計算機語言編譯器優化原理中,逃逸分析是指分析指針動態范圍的方法,它同編譯器優化原理的指針分析和外形分析相關聯。當變量(或者對象)在方法中分配后,其指針有可能被返回或者被全局引用,這樣就會被其他方法或者線程所引用,這種現象稱作指針(或者引用)的逃逸(Escape)。通俗點講,如果一個對象的指針被多個方法或者線程引用時,那么我們就稱這個對象的指針(或對象)的逃逸(Escape)。

什么是逃逸分析?

逃逸分析,是一種可以有效減少Java 程序中同步負載和內存堆分配壓力的跨函數全局數據流分析算法。通過逃逸分析,Java Hotspot編譯器能夠分析出一個新的對象的引用的使用范圍從而決定是否要將這個對象分配到堆上。 逃逸分析(Escape Analysis)算是目前Java虛擬機中比較前沿的優化技術了。

注意:逃逸分析不是直接的優化手段,而是代碼分析手段。

對象逃逸案例:

Xpublic User doSomething1() {User user1 = new User ();user1 .setId(1);user1 .setDesc("xxxxxxxx");// ......return user1 ;
}

對象未逃逸:

public void doSomething2() {User user2 = new User ();user2 .setId(2);user2 .setDesc("xxxxxxxx");// ...... 
}

基于逃逸分析的優化

當判斷出對象不發生逃逸時,編譯器可以使用逃逸分析的結果作一些代碼優化

  • 棧上分配:將堆分配轉化為棧分配。如果某個對象在子程序中被分配,并且指向該對象的指針永遠不會逃逸,該對象就可以在分配在棧上,而不是在堆上。在的垃圾收集的語言中,這種優化可以降低垃圾收集器運行的頻率。
  • 同步消除:如果發現某個對象只能從一個線程可訪問,那么在這個對象上的操作可以不需要同步。
  • 分離對象或標量替換。如果某個對象的訪問方式不要求該對象是一個連續的內存結構,那么對象的部分(或全部)可以不存儲在內存,而是存儲在CPU寄存器中。

標量替換

**標量:**不可被進一步分解的量,而JAVA的基本數據類型就是標量(比如int,long等基本數據類型) 。

聚合量: 標量的對立就是可以被進一步分解的量,稱之為聚合量。 在JAVA中對象就是可以被進一步分解的聚合量。

**標量替換:**通過逃逸分析確定該對象不會被外部訪問,并且對象可以被進一步分解時,JVM不會創建該對象,而是將該對象成員變量分解若干個被這個方法使用的成員變量所代替,這些代替的成員變量在棧幀或寄存器上分配空間,這樣就不會因為沒有一大塊連續空間導致對象內存不夠分配。

棧上分配案例:

虛擬機參數:

-XX:+PrintGC -Xms5M -Xmn5M -XX:+DoEscapeAnalysis

-XX:+DoEscapeAnalysis表示開啟逃逸分析,JDK8是默認開啟的

-XX:+PrintGC 表示打印GC信息

-Xms5M -Xmn5M 設置JVM內存大小是5M

 public static void main(String[] args){for(int i = 0; i < 5_000_000; i++){createObject();}}public static void createObject(){new Object();}

運行結果是沒有GC。

image.png

把虛擬機參數改成 -XX:+PrintGC -Xms5M -Xmn5M -XX:-DoEscapeAnalysis。關閉逃逸分析得到結果的部分截圖是,說明了進行了GC,并且次數還不少。

image.png

這說明了JVM在逃逸分析之后,將對象分配在了方法createObject()方法棧上。方法棧上的對象在方法執行完之后,棧楨彈出,對象就會自動回收。這樣的話就不需要等內存滿時再觸發內存回收。這樣的好處是程序內存回收效率高,并且GC頻率也會減少,程序的性能就提高了。

同步鎖消除

如果發現某個對象只能從一個線程可訪問,那么在這個對象上的操作可以不需要同步

虛擬機配置參數:-XX:+PrintGC -Xms500M -Xmn500M -XX:+DoEscapeAnalysis。配置500M是保證不觸發GC。

public static void main(String[] args){long start = System.currentTimeMillis();for(int i = 0; i < 5_000_000; i++){createObject();}System.out.println("cost = " + (System.currentTimeMillis() - start) + "ms");}public static void createObject(){synchronized (new Object()){}}

運行結果

 
cost = 6ms

把逃逸分析關掉:-XX:+PrintGC -Xms500M -Xmn500M -XX:-DoEscapeAnalysis

運行結果

cost = 270ms

說明了逃逸分析把鎖消除了,并在性能上得到了很大的提升。這里說明一下Java的逃逸分析是方法級別的,因為JIT ( just in time )即時編譯器的即時編譯是方法級別。

什么條件下會觸發逃逸分析?

對象會先嘗試棧上分配,如果不能成功分配,那么就去TLAB,如果還不行,就判定當前的垃圾收集器悲觀策略,可不可以直接進入老年代,最后才會進入Eden。

image.png

Java的逃逸分析只發在JIT的即時編譯中,因為在啟動前已經通過各種條件判斷出來是否滿足逃逸,通過上面的流程圖也可以得知對象分配不一定在堆上,所以可知滿足逃逸的條件如下,只要滿足以下任何一種都會判斷為逃逸。

一、對象被賦值給堆中對象的字段和類的靜態變量。
二、對象被傳進了不確定的代碼中去運行。

對象逃逸的范圍有:全局逃逸、參數逃逸、沒有逃逸;

TLAB前面的內容講過,在當前場景下做一個補充:

TLAB(Thread Local Allocation Buffer)

即線程本地分配緩存區,這是一個線程專用的內存分配區域。
由于對象一般會分配在堆上,而堆是全局共享的。因此在同一時間,可能會有多個線程在堆上申請空間。因此,每次對象分配都必須要進行同步(虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性),而在競爭激烈的場合分配的效率又會進一步下降。JVM使用TLAB來避免多線程沖突,在給對象分配內存時,每個線程使用自己的TLAB,這樣可以避免線程同步,提高了對象分配的效率。

每個線程會從Eden分配一大塊空間,例如說100KB,作為自己的TLAB。這個start是TLAB的起始地址,end是TLAB的末尾,然后top是當前的分配指針。顯然start <= top < end。

當一個Java線程在自己的TLAB中分配到盡頭之后,再要分配就會出發一次“TLAB refill”,也就是說之前自己的TLAB就“不管了”(所有權交回給共享的Eden),然后重新從Eden里分配一塊空間作為新的TLAB。所謂“不管了”并不是說就讓舊TLAB里的對象直接死掉,而是把那塊空間的控制權歸還給普通的Eden,里面的對象該怎樣還是怎樣。通常情況下,在TLAB中分配多次才會填滿TLAB、觸發TLAB refill,這樣使用TLAB分配就比直接從共享部分的Eden分配要均攤(amortized)了同步開銷,于是提高了性能。其實很多關注多線程性能的malloc庫實現也會使用類似的做法,例如TCMalloc。

到觸發GC的時候,無論是minor GC還是full GC,要收集Eden的時候里面的空間無論是屬于某個線程的TLAB還是不屬于任何TLAB都一視同仁,把Eden當作一個整體來收集里面的對象——把活的對象拷貝到survivor space(或者直接晉升到Old Gen)。在GC結束之后,每個Java線程又會重新從Eden分配自己的TLAB。周而復始。

TLAB分配的對象可以共享嗎?

答:只要是Heap上的對象,所有線程都是可以共享的,就看你有沒有本事訪問到了。在GC的時候只從root sets來掃描對象,而不管你到底在哪個TLAB中。

4.1 內存優化

4.1.1 內存分配

正常情況下不需要設置,那如果是促銷或者秒殺的場景呢?

每臺機器配置2c4G,以每秒3000筆訂單為例,整個過程持續60秒

image.png

4.1.2 內存溢出(OOM)

一般會有兩個原因:

(1)大并發情況下

(2)內存泄露導致內存溢出

4.1.2.1 大并發[秒殺]

瀏覽器緩存、本地緩存、驗證碼

CDN靜態資源服務器

集群+負載均衡

動靜態資源分離、限流[基于令牌桶、漏桶算法]

應用級別緩存、接口防刷限流、隊列、Tomcat性能優化

異步消息中間件

Redis熱點數據對象緩存

分布式鎖、數據庫鎖

5分鐘之內沒有支付,取消訂單、恢復庫存等

4.1.2.2 內存泄露導致內存溢出

ThreadLocal引起的內存泄露,最終導致內存溢出

public class TLController {
@RequestMapping(value = "/tl")
public String tl(HttpServletRequest request) {ThreadLocal<Byte[]> tl = new ThreadLocal<Byte[]>();// 1MBtl.set(new Byte[1024*1024]);return "ok";
}
}

(1)上傳到阿里云服務器

jvm-case-0.0.1-SNAPSHOT.jar

(2)啟動

java -jar -Xms1000M -Xmx1000M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=jvm.hprof  jvm-case-0.0.1-SNAPSHOT.jar

(3)使用jmeter模擬10000次并發

39.100.39.63:8080/tl

(4)top命令查看

top
top -Hp PID

(5)jstack查看線程情況,發現沒有死鎖或者IO阻塞的情況

jstack PID
java -jar arthas.jar   --->   thread

(6)查看堆內存的使用,發現堆內存的使用率已經高達88.95%

jmap -heap PID
java -jar arthas.jar   --->   dashboard

(7)此時可以大體判斷出來,發生了內存泄露從而導致的內存溢出,那怎么排查呢?

jmap -histo:live PID | more
獲取到jvm.hprof文件,上傳到指定的工具分析,比如heaphero.io

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/710618.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/710618.shtml
英文地址,請注明出處:http://en.pswp.cn/news/710618.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

babylonsjs入門-基礎模版

基于babylonjs封裝的一些功能和插件 &#xff0c;希望有更多的小伙伴一起玩babylonjs&#xff1b; 歡迎加群&#xff08;點擊群號傳送&#xff09;&#xff1a;464146715 官方文檔 中文文檔 案例傳送門 ? 懶得打字&#xff0c;你們直接去copy組件吧&#xff0c;主要看這2…

舊版android模擬器,37歲程序員被裁

前言 從18年畢業至今&#xff0c;就職過兩家公司&#xff0c;大大小小項目做了幾個&#xff0c;非常感謝我的兩位老大&#xff0c;在我的android成長路上給予我很多指導&#xff0c;亦師亦友的關系。 從年前至今參加面試了很多公司&#xff0c;也收到了幾家巨頭的offer&#…

transformer--編碼器2(前饋全連接層、規范化層、子層鏈接結構、編碼器層、編碼器)

前饋全連接層 什么是前饋全連接層: 在Transformer中前饋全連接層就是具有兩層線性層的全連接網絡 前饋全連接層的作用: 考慮注意力機制可能對復雜過程的擬合程度不夠,通過增加兩層網絡來增強模型的能力 code # 前饋全連接層 class PositionwiseFeedForward(nn.Module):de…

絕地求生:發現吃雞號被盜,怎么操作才是最正確的

首先閑游盒先了解一下盜號者的盜號流程 一般盜號的流程是先把你steam上的皮膚飾品出售&#xff0c;然后把余額轉走&#xff0c;再把steam賬號作為黑號進行出售。 所以當閑游盒發現號被盜的時候也分為兩種情況&#xff1a;一種是他正在出售商品的時候&#xff0c;你收到郵箱提示…

Linux命令行與shell腳本編程大全-3-4

第三部分高級shell腳本編程 第17章創建函數 17.1 腳本函數基礎 17.1.1 創建函數 在bash shell 腳本中創建函數的語法有兩種。第一種語法是使用關鍵字function&#xff0c;隨后跟 上分配給該代碼塊的函數名&#xff1a; function name { commands } 17.1.2 使用函數 要在腳…

代碼隨想錄算法訓練營Day46 | 139.單詞拆分、多重背包(卡碼網56.攜帶礦石資源)

139.單詞拆分 可以理解為一道 完全背包 排列 的題&#xff0c;dp數組定義和遞推公式部分腦子需要轉個彎 1、DP數組定義&#xff1a;一維滾動數組vector<bool>。dp[j]表示字符串s的[0, j]子串是否能夠匹配。 2、DP數組初始化&#xff1a;dp[0]初始化為true&#xff0c;其…

Multi-Head Attention詳解

文中大部分內容以及圖片來自&#xff1a;https://medium.com/hunter-j-phillips/multi-head-attention-7924371d477a 當使用 multi-head attention 時&#xff0c;通常d_key d_value &#xff08;d_model / n_heads&#xff09;&#xff0c;其中n_heads是頭的數量。研究人員稱…

01-Vue2 介紹與指令的使用

1. Vue核心 1.1. Vue簡介 1.1.1. 官網 中文官網Vue.js - 漸進式 JavaScript 框架 | Vue.js (vuejs.org)https://cn.vuejs.org/ 英文官網Vue.js - The Progressive JavaScript Framework | Vue.js (vuejs.org)https://vuejs.org/ 1.1.2. 介紹與描述 VUE是構建于用戶界面的漸進…

靶機滲透之sar

Name: Sar: 1Date release: 15 Feb 2020Author: LoveSeries: Sar Download: https://drive.google.com/open?id1AFAmM21AwiAEiVFUA0cSr_GeAYaxd3lQ 對于vulnhub中的靶機&#xff0c;我們都需先下載鏡像&#xff0c;然后導入VM&#xff0c;并將網絡連接改為NAT模式。首先我們…

UDP數據報套接字編程入門

目錄 1.TCP和UDP的特點及區別 1.1TCP的特點 1.2UDP的特點 1.3區別 2.UDP Socket的api的介紹 2.1DatagramSocket API 2.2DatagramPacket API 3.回顯客戶端與服務器 3.1回顯服務器 3.1.1UdpEchoServer類的創建 3.1.2服務器的運行方法start() 3.1.3main部分 3.1.4.完整…

C# CAD PaletteSet.Style各種外觀和行為樣式

ps.Style 是 Autodesk.AutoCAD.Windows.PaletteSet 類的一個屬性&#xff0c;用于定義調色板集&#xff08;PaletteSet&#xff09;的各種外觀和行為樣式。它可以是 PaletteSetStyles 枚舉類型的組合值 PaletteSetStyles 枚舉中包含以下一些選項&#xff1a; NameEditable&am…

統計子矩陣

一、題目描述 P8783 [藍橋杯 2022 省 B] 統計子矩陣 二、算法簡析 2.1 二維前綴和 我們知道&#xff0c;只要確定了矩陣的左上頂點和右下頂點&#xff0c;一個矩陣就被固定了。因此&#xff0c;我們可以遍歷這兩個頂點&#xff0c;達到遍歷所有子矩陣的目的&#xff0c;復雜…

AutoSAR(基礎入門篇)12.5-Dem

目錄 一、Dem簡介 二、Dem消抖 1、計數模式 1. 普通增減計數 2. 反向歸零增減模式

在微服務整合dubbo,以為微服務版的若依為例

在微服務整合dubbo&#xff0c;以為微服務版的若依為例 一、環境二、整合過程1、父模塊依賴2、生產者3、消費者 三、修改若依的服務調用方式為dubbo1、改造系統模塊2、改造認證授權中心 四、整合過程遇到的問題1、出現循環引用2、出現依賴沖突3、啟動出現端口號被占用4、出現某…

UVa11726 Crime Scene

題目鏈接 UVa11726 - Crime Scene 題意 給定n&#xff08;n≤100&#xff09;個物體&#xff0c;每個物體都是一個圓或者k&#xff08;k≤10&#xff09;邊形&#xff0c;用長度盡量小的繩子把它們包圍起來。 分析 孟加拉國Manzurur Rahman Khan (Sidky)大神出的難題&#xff…

MySQL 核心模塊揭秘 | 07 期 | 二階段提交 (1) prepare 階段

二階段提交的 prepare 階段&#xff0c;binlog 和 InnoDB 各自會有哪些動作&#xff1f; 本文基于 MySQL 8.0.32 源碼&#xff0c;存儲引擎為 InnoDB。 1. 二階段提交 二階段提交&#xff0c;顧名思義&#xff0c;包含兩個階段&#xff0c;它們是&#xff1a; prepare 階段。…

springboot-基礎-eclipse配置+helloword示例

備份筆記。所有代碼都是2019年測試通過的&#xff0c;如有問題請自行搜索解決&#xff01; 下一篇&#xff1a;springboot-基礎-添加model和controller的簡單例子常用注解含義 目錄 配置helloword示例新建項目創建文件 配置 spring boot官方有定制版eclipse&#xff0c;也就是…

BUUCTF AWD-Test1

打開靶場是這個有些簡陋的界面。 隨便點點&#xff0c;找到這個東西。 看到ThinkPHP&#xff0c;思路瞬間清晰&#xff0c;老熟人了。這個就是ThinkPHP漏洞。根據版本我們去找一下poc。 /index.php/?sIndex/\think\View/display&content%22%3C?%3E%3C?php%20phpinfo();…

SHELL 腳本: 導出NEO4j DUMP并上傳SFTP

前提 開通sftp賬號 安裝expect 示例 NEO4J_HOME/path/to/neo4j # neo4j 安裝目錄 DUMP_PATH/data/dump # DUMP本地保存目錄 DUMP_FILEneo4j_$(date %F).dump #導出文件名稱 UPLOAD_DIR/path/to/stfp/dump/ #上傳目錄 $NEO4J_HOME/bin/neo4j-admin dump --databaseneo4j --t…

Vue-5

Vue 3 的優勢 更容易維護&#xff08;組合式API&#xff09;更快的速度更小的體積更優的數據響應 創建 Vue 3 項目 前提環境條件&#xff1a;已安裝 16.0 或更高版本的 Node.js node -v創建一個 Vue 應用&#xff08;下面的指令將會安裝并執行 create-vue &#xff09; np…