JVM代碼緩存區(qū)CodeCache原理及用法解析
一. CodeCache簡介
從字面意思理解就是代碼緩存區(qū),它緩存的是JIT(Just in Time)編譯器編譯的代碼,簡言之codeCache是存放JIT生成的機器碼(native code)。當然JNI(Java本地接口)的機器碼也放在codeCache里,不過JIT編譯生成的native code占主要部分。
大致在JVM中的分布如下:
大家都知道javac編譯器,把java代碼編譯成class字節(jié)碼,它和JIT編譯器的區(qū)別是,javac只是前端編譯(有的叫前期編譯),jvm是通過執(zhí)行機器碼和底層交互的,這樣我們編寫的業(yè)務(wù)代碼才能生效。所以還要把字節(jié)碼class編譯成與本地平臺相關(guān)的機器碼,這個過程就是后端編譯。
后端編譯根據(jù)具體的執(zhí)行方式不同又分為兩種:
1.解釋執(zhí)行
一行一行解釋成機器碼再執(zhí)行,每次調(diào)用時都需要重新逐條解釋執(zhí)行。
2.編譯執(zhí)行(JIT)
將頻繁調(diào)用的方法或循環(huán)體編譯成機器碼后,進行多層優(yōu)化,然后緩存到codeCache里,避免重復(fù)編譯。
兩種執(zhí)行方式的區(qū)別很明顯,第一種在遇到頻繁調(diào)用的方法或代碼塊時執(zhí)行效率很低,但是解釋執(zhí)行可以節(jié)省內(nèi)存(不存放到codeCache),立即執(zhí)行。然后當程序運行一段時間后(達到一定的編譯次數(shù)),編譯執(zhí)行即JIT優(yōu)化,可以獲得更高的執(zhí)行效率。
所以說二者是相輔相成的。
現(xiàn)在的Java虛擬機這兩種方式都包含(通過命令行java -version查看):
// mixed mode 解釋+編譯
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
其實JIT編譯只是一個統(tǒng)稱,具體要看jvm是client端還是server端的,不同的端會分為C1,C2編譯器,這兩種編譯器的區(qū)別下一篇會講到,這里先不展開。
二. JIT編譯優(yōu)化
上面講到了JVM會對頻繁使用的代碼,即熱點代碼(Hot Spot Code),達到一定的閾值后會編譯成本地平臺相關(guān)的機器碼,并進行各層次的優(yōu)化,提升執(zhí)行效率。
熱點代碼也分兩種:
- 被多次調(diào)用的方法
- 被多次執(zhí)行的循環(huán)體
那閾值如何判斷呢?
方法計數(shù)器,統(tǒng)計被多次調(diào)用的方法次數(shù),該計數(shù)器統(tǒng)計的并不是方法被調(diào)用的絕對次數(shù),而是在一段時間內(nèi)方法被調(diào)用的次數(shù)。server模式下默認是10000次,可以通過-XX:CompileThreshold來設(shè)置(client模式一般很少用到,默認是1500)。
回邊計數(shù)器,統(tǒng)計一個方法中循環(huán)體代碼執(zhí)行的絕對次數(shù),在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令稱為回邊,主要通過OnStackReplacePercentage設(shè)置。
編譯后進行優(yōu)化,JIT的優(yōu)化有很多種,比如:
- 針對方法的優(yōu)化,方法內(nèi)聯(lián)
- 針對多次調(diào)用的循環(huán)體優(yōu)化:棧上替換OSR(On-Stack Replace)
- 無用代碼消除
- 復(fù)寫傳播
- 逃逸分析
- 更多JIT優(yōu)化技術(shù)可參考jvm官網(wǎng)介紹
三. codeCache使用注意事項
上面主要講了codeCache的作用和JIT的關(guān)系,codeCache主要是存放JIT編譯后的機器代碼,codeCache的大小主要是通過下面的參數(shù)設(shè)置:
- -XX:InitialCodeCacheSize 設(shè)置codeCache初始大小,一般默認是48M
- -XX:ReservedCodeCacheSize 設(shè)置codeCache預(yù)留的大小,通常默認是240M
如果codeCache的內(nèi)存滿了會進行回收,但在jdk1.8之前的jvm回收算法有點問題,當codeCache滿了之后會導(dǎo)致編譯線程無法繼續(xù),并且消耗大量CPU導(dǎo)致系統(tǒng)運行變慢,現(xiàn)象就是系統(tǒng)響應(yīng)增加,如果你也遇到這個問題建議直接升級成jdk8,或者調(diào)大codeCache內(nèi)存。
codeCache的大小設(shè)置可以通過-XX:+PrintCodeCache參數(shù)查看調(diào)整,但這個參數(shù)只在JVM停止的時候打印codeCache使用情況,所以如果想實時監(jiān)控codeCache的使用情況,可以參考如下代碼:
package com.javakk; import java.io.File; import java.lang.management.ManagementFactory; import javax.management.MBeanServerConnection; import javax.management.ObjectName; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import com.sun.tools.attach.VirtualMachine; /** * 基于JMX在運行時查看codeCache使用情況 * @author 公眾號:Java老K */ public class CodeCacheTest { public static void main(String[] args) throws Exception { String pid = getPid(); // 先獲取java程序的pid String codeCache = getCodeCache(pid); // 根據(jù)pid獲取codeCache的使用情況 System.out.println(codeCache); } /** * 獲取java進程id * @return */ public static String getPid(){ String name = ManagementFactory.getRuntimeMXBean().getName(); return name.split("@")[0]; } /** * 獲取java應(yīng)用的codeCache使用情況 * @param pid * @throws Exception */ public static String getCodeCache(String pid) throws Exception { VirtualMachine vm = VirtualMachine.attach(pid); JMXConnector connector = null; try { String addr = "com.sun.management.jmxremote.localConnectorAddress"; String property= vm.getAgentProperties().getProperty(addr); if (property == null) { String agent = vm.getSystemProperties().getProperty("java.home") + File.separator + "lib" + File.separator + "management-agent.jar"; vm.loadAgent(agent); property = vm.getAgentProperties().getProperty(addr); } JMXServiceURL url = new JMXServiceURL(property); connector = JMXConnectorFactory.connect(url); MBeanServerConnection mbeanConn = connector.getMBeanServerConnection(); ObjectName obj = new ObjectName("java.lang:type=MemoryPool,name=Code Cache"); return mbeanConn.getAttribute(obj, "Usage").toString(); } finally { if(connector != null) { connector.close(); } vm.detach(); } } }
運行后可以查看contents結(jié)果
contents={committed=2555904, init=2555904, max=251658240, used=2395648}
可以看到我本地的codeCahe配置,初始化是2555904,最大為251658240,已使用2395648
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot3實現(xiàn)webclient的通用方法詳解
Spring Boot WebClient 是 Spring Framework 5 中引入的一個新的響應(yīng)式 Web 客戶端,用于異步和響應(yīng)式地與外部服務(wù)進行通信,下面我們就來看看SpringBoot3實現(xiàn)webclient的通用方法吧2024-04-04一文帶你掌握Java?LinkedBlockingQueue
LinkedBlockingQueue?是一個可選有界阻塞隊列,這篇文章主要為大家詳細介紹了Java中LinkedBlockingQueue的實現(xiàn)原理與適用場景,感興趣的可以了解一下2023-04-04SpringBoot異步使用@Async的原理以及線程池配置詳解
在項目中當訪問其他人的接口較慢時,不想程序一直卡在耗時任務(wù)上,想程序能夠并行執(zhí)行,我們可以使用多線程來并行的處理任務(wù),也可以使用spring提供的異步處理方式@Async,這篇文章主要給大家介紹了關(guān)于SpringBoot異步使用@Async的原理以及線程池配置的相關(guān)資料2021-09-09Java單例模式實現(xiàn)靜態(tài)內(nèi)部類方法示例
這篇文章主要介紹了Java單例模式實現(xiàn)靜態(tài)內(nèi)部類方法示例,涉及構(gòu)造函數(shù)私有化等相關(guān)內(nèi)容,需要的朋友可以了解下。2017-09-09