JVM學習筆記之CodeCache


一. CodeCache簡介

從字面意思理解就是代碼緩存區,它緩存的是JIT(Just in Time)編譯器編譯的代碼,簡言之codeCache是存放JIT生成的機器碼(native code)。當然JNI(Java本地接口)的機器碼也放在codeCache里,不過JIT編譯生成的native code占主要部分。

大致在JVM中的分布如下:

大家都知道javac編譯器,把java代碼編譯成class字節碼,它和JIT編譯器的區別是,javac只是前端編譯(有的叫前期編譯),jvm是通過執行機器碼和底層交互的,這樣我們編寫的業務代碼才能生效。所以還要把字節碼class編譯成與本地平台相關的機器碼,這個過程就是后端編譯。

后端編譯根據具體的執行方式不同又分為兩種:

1.解釋執行

一行一行解釋成機器碼再執行,每次調用時都需要重新逐條解釋執行。

2.編譯執行(JIT)

將頻繁調用的方法或循環體編譯成機器碼后,進行多層優化,然后緩存到codeCache里,避免重復編譯。

兩種執行方式的區別很明顯,第一種在遇到頻繁調用的方法或代碼塊時執行效率很低,但是解釋執行可以節省內存(不存放到codeCache),立即執行。然后當程序運行一段時間后(達到一定的編譯次數),編譯執行即JIT優化,可以獲得更高的執行效率。

所以說二者是相輔相成的。

現在的Java虛擬機這兩種方式都包含(通過命令行java -version查看):

// mixed mode 解釋+編譯
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)

其實JIT編譯只是一個統稱,具體要看jvm是client端還是server端的,不同的端會分為C1C2編譯器,這兩種編譯器的區別下一篇會講到,這里先不展開。

二. JIT編譯優化

上面講到了JVM會對頻繁使用的代碼,即熱點代碼(Hot Spot Code),達到一定的閾值后會編譯成本地平台相關的機器碼,並進行各層次的優化,提升執行效率。

熱點代碼也分兩種:

  • 被多次調用的方法
  • 被多次執行的循環體

那閾值如何判斷呢?

  1. 方法計數器,統計被多次調用的方法次數,該計數器統計的並不是方法被調用的絕對次數,而是在一段時間內方法被調用的次數。server模式下默認是10000次,可以通過-XX:CompileThreshold來設置(client模式一般很少用到,默認是1500)。
  2. 回邊計數器,統計一個方法中循環體代碼執行的絕對次數,在字節碼中遇到控制流向后跳轉的指令稱為回邊,主要通過OnStackReplacePercentage設置。

編譯后進行優化,JIT的優化有很多種,比如:

  • 針對方法的優化,方法內聯(可以參考這篇文章Java開發規范之性能篇,關於inline的解釋)
  • 針對多次調用的循環體優化:棧上替換OSR(On-Stack Replace)
  • 無用代碼消除
  • 復寫傳播
  • 逃逸分析

更多JIT優化技術可參考jvm官網介紹

三. codeCache使用注意事項

上面主要講了codeCache的作用和JIT的關系,codeCache主要是存放JIT編譯后的機器代碼,codeCache的大小主要是通過下面的參數設置:

  • -XX:InitialCodeCacheSize 設置codeCache初始大小,一般默認是48M
  • -XX:ReservedCodeCacheSize 設置codeCache預留的大小,通常默認是240M

如果codeCache的內存滿了會進行回收,但在jdk1.8之前的jvm回收算法有點問題,當codeCache滿了之后會導致編譯線程無法繼續,並且消耗大量CPU導致系統運行變慢,現象就是系統響應增加,如果你也遇到這個問題建議直接升級成jdk8,或者調大codeCache內存。

codeCache的大小設置可以通過-XX:+PrintCodeCache參數查看調整,但這個參數只在JVM停止的時候打印codeCache使用情況,所以如果想實時監控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); // 根據pid獲取codeCache的使用情況
        System.out.println(codeCache);
    }

    /**
     * 獲取java進程id
     * @return
     */
    public static String getPid(){
        String name = ManagementFactory.getRuntimeMXBean().getName();
        return name.split("@")[0];
    }

    /**
     * 獲取java應用的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結果

contents={committed=2555904, init=2555904, max=251658240, used=2395648}

可以看到我本地的codeCahe配置,初始化是2555904,最大為251658240,已使用2395648

文章來源:http://javakk.com/201.html


免責聲明!

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



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