深入理解JVM:晚期(运行期)优化

Java程序最初是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”(Hot Spot Code)。

1.即时编译器(JIT编译器)

为了提高热点代码的执行效率,在运行时,虚拟机会把这些热点代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器就是即时编译器(JIT编译器)。
在介绍即时编译器之前先来提出几个问题,然后带着这些问题,寻找文中的答案。

  • 为何HotSpot要使用解释器与编译器并存的架构?
  • 为何HotSpot要实现两个不同的即时编译器?
  • 程序什么时候使用解释器执行,什么时候使用编译器执行?
  • 哪些程序代码会被编译为本地代码?如何编译为本地代码?
  • 如何从外部观察即时编译器的编译过程和编译结果?

1.1解释器与编译器

1.1.1编译器并存架构

并不是所有的Java虚拟机都采用解释器和编译器并存的架构,但是很多的主流的商用虚拟机(HotSpot,J9等),都同时包含了解释器和编译器。

为什么要采用这种并存的架构呢?其实啊,解释器和编译器他们是各有优势:当程序需要快速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。然而随着程序的运行,编译器就会逐渐发挥作用,把越来越多的代码编译成本地机器码,用来提高程序的执行效率。
不过,在整个虚拟机执行过程中,解释器与编译器经常配合工作。

解释器与编译器的交互

1.1.2两个即时编译器

HotSpot虚拟机内置了两个即时编译器,分别是Client Compiler(C1编译器)和Server Compiler(C2编译器),目前主流的HotSpot虚拟机中,默认采用解释器与其中的一个编译器直接配合的方式工作,具体使用哪一个编译器,取决于虚拟机的运行模式。不过,用户可以使用“-client”,“-server”参数去强制指定虚拟机运行在Client模式或者Server模式。

不管采用哪一个编译器,这种搭配使用的工作模式在虚拟机中成为“混合模式”(Mixed Mode)。

$ java -version
java version "1.8.0_201"
Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)

即时编译器编译本地代码需要占用程序执行时间,为了编译更高效优化程度更高的代码,那么花费时间可能更长,解释器还要给人家收集性能监控信息,这对解释器的执行速度也有一定的影响。

1.1.3分层编译

那么有了影响怎么办?不用担心,总是有办法的。
为了在程序启动速度与运行效率之间达到最佳平衡,HotSpot虚拟机采用了“分层编译”的策略,不过是在JDK1.7后的Server模式虚拟机中作为默认编译策略被开启。那么分层编译时怎么个分层法呢,有什么依据条件呢?
分层编译根据编译器编译,优化的规模与耗时进行划分的。其中包含了以下几个编译层次:

  • 第0层,程序解释执行,解释器不开启性能监控功能,可触发第1层编译。
  • 第1层,也称C1编译,将字节码编译为本地机器码,进行简单,可靠的优化,如有必要还会加入性能监控逻辑。
  • 第2层(或2层以上),也是将字节码编译为本地机器码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

实施分层编译后,C1和C2编译器会同时工作,许多代码可能被编译多次,用C1编译器能获取更高的编译速度,用C2编译器能获得更好的编译质量。

1.2编译对象与触发条件

1.2.1热点代码

对于“热点代码”,上面已经有所提及,也就是被JIT编译的代码就是“热点代码”,那么就有这么两个问题值得思考。

  1. 什么样的代码被认为是热点代码?
  2. 如何检测这些热点代码?

先回答一下第一个问题:被即时编译器编译的“热点代码”有两类,也就是:

  • 被多次调用的方法
  • 被多次执行的循环体

对于被多次调用的方法,这个非常好理解,也就是方法调用次数多了,那么方法体内的代码执行次数也就多了。对于被多次执行的循环体,这个怎么理解呢,因为方法可能调用一次或者少量的几次,但是方法体内的循环体执行次数非常多,这也被认为是“热点代码”。

1.2.2热点探测

我们说被执行多次调用的方法或者被多次执行的循环体,被称为“热点代码”,那么这个“多次”到底是多少次,并不是一个精确的数字,那么如何统计这个次数?回答了这个问题,也就回答了即时编译的触发条件。
判断一段代码是不水热点代码,是不是需要触发即时编译,这样的行为称作:热点探测(Hot Spot Detection)。目前主要的热点探测判定方式有两种:

  1. 基于采样的热点探测
  2. 基于计数器的热点探测

在HotSpot虚拟机中,采用的是第二中判定方式:基于计数器的热点探测。它为每个方法设置了两类计数器:方法调用计数器和回边计数器。在确定虚拟机运行参数的情况下,这两个计数器都有确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。

  • 方法调用计数器
    这个很好理解,就是统计方法被调用的次数,在Client模式下,默认值是1500次,在Server模式下是10000次,这个阈值可以通过虚拟机参数-XX: CompileThreshold来认为设置。接下来看看整个JIT编译的交互流程图。

    方法调用计数器触发即时编译

  • 回边计数器
    这个计数器的作用是统计一个方法中循环体代码的执行次数,在字节码中遇到控制流向后跳转的指令成为“回边”。回边计数器统计的目的就是为了触发OSR编译。

OSR(栈上替换): 一旦判定代码段是热点代码,则解释器将发送一次请求编译器,进行编译,在编译成功之前 解释器仍旧运行着。 等编译完成后,直接将pc寄存器中方法的调用地址进行替换,替换为编译后的方法地址。

目前回边计数器的虚拟机参数-XX:BackEdgeThreshold并未使用起来,需要通过-XX:OnStackReplacePercentage来间接调整回边计数器的阈值。关于回边计数器的阈值计算,暂不做了解。看看回边计时器触发即时编译的流程图。

回边计数器触发即时编译

1.2.3编译过程

默认情况下,无论是方法调用产生的即时编译请求,还是OSR编译请求,虚拟机在代码编译未完成之前,都是按照解释方式继续执行,而编译动作是交给后台的编译线程进行。用户可以通过设置虚拟机参数-XX:-BackgroundCompilation来进行后台编译。禁止后台编译以后,一旦触发了JIT编译条件,执行线程向虚拟机提交编译请求后将会一直等待,直到编译完成后再开始执行编译器输出的本地代码。
主要来看看C1编译的大致过程。

C1编译过程架构

对于Server Compiler编译的速度无疑是比较缓慢的,但是依然要超过传统的静态优化编译的速度,但是相比C1编译器来说,编译代码的质量更高。

1.3查看及分析即时编译结果

先来看段测试代码,代码内容比较简单,具体如下:

public class CheckCompileResult {

    public static void main(String[] args) {
        for (int i = 0; i < 15000; i++){
            calcSum();
        }
    }

    public static int doubleValue(int i){
        for (int j = 0; j < 100000; j++);
        return i*2;
    }

    public static long calcSum(){
        long sum = 0;
        for (int i = 0; i <= 100; i++){
            sum += doubleValue(i);
        }
        return sum;
    }

}

虚拟机运行参数:-XX:+PrintCompilation,这个参数要求虚拟机在即时编译的时候将被编译成的本地代码的方法名称打印出来,通过运行上面程序,可以确认这段代码是否触发了即时编译。打印结果如下:

/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/bin/java -XX:+PrintCompilation "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=55847:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/lib/tools.jar:/Users/fengguoqiang/myproject/xihu/target/classes:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/boot/spring-boot-starter-web/2.2.4.RELEASE/spring-boot-starter-web-2.2.4.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/boot/spring-boot-starter/2.2.4.RELEASE/spring-boot-starter-2.2.4.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/boot/spring-boot/2.2.4.RELEASE/spring-boot-2.2.4.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/boot/spring-boot-autoconfigure/2.2.4.RELEASE/spring-boot-autoconfigure-2.2.4.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/boot/spring-boot-starter-logging/2.2.4.RELEASE/spring-boot-starter-logging-2.2.4.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/apache/logging/log4j/log4j-to-slf4j/2.12.1/log4j-to-slf4j-2.12.1.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/apache/logging/log4j/log4j-api/2.12.1/log4j-api-2.12.1.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/yaml/snakeyaml/1.25/snakeyaml-1.25.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/boot/spring-boot-starter-json/2.2.4.RELEASE/spring-boot-starter-json-2.2.4.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.10.2/jackson-datatype-jdk8-2.10.2.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.10.2/jackson-datatype-jsr310-2.10.2.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/com/fasterxml/jackson/module/jackson-module-parameter-names/2.10.2/jackson-module-parameter-names-2.10.2.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/boot/spring-boot-starter-tomcat/2.2.4.RELEASE/spring-boot-starter-tomcat-2.2.4.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/apache/tomcat/embed/tomcat-embed-core/9.0.30/tomcat-embed-core-9.0.30.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/apache/tomcat/embed/tomcat-embed-el/9.0.30/tomcat-embed-el-9.0.30.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.30/tomcat-embed-websocket-9.0.30.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/boot/spring-boot-starter-validation/2.2.4.RELEASE/spring-boot-starter-validation-2.2.4.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/jakarta/validation/jakarta.validation-api/2.0.2/jakarta.validation-api-2.0.2.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/hibernate/validator/hibernate-validator/6.0.18.Final/hibernate-validator-6.0.18.Final.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/jboss/logging/jboss-logging/3.4.1.Final/jboss-logging-3.4.1.Final.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/com/fasterxml/classmate/1.5.1/classmate-1.5.1.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/spring-web/5.2.3.RELEASE/spring-web-5.2.3.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/spring-beans/5.2.3.RELEASE/spring-beans-5.2.3.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/spring-webmvc/5.2.3.RELEASE/spring-webmvc-5.2.3.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/spring-aop/5.2.3.RELEASE/spring-aop-5.2.3.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/spring-context/5.2.3.RELEASE/spring-context-5.2.3.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/spring-expression/5.2.3.RELEASE/spring-expression-5.2.3.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/redisson/redisson/3.6.5/redisson-3.6.5.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/io/netty/netty-common/4.1.45.Final/netty-common-4.1.45.Final.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/io/netty/netty-codec/4.1.45.Final/netty-codec-4.1.45.Final.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/io/netty/netty-buffer/4.1.45.Final/netty-buffer-4.1.45.Final.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/io/netty/netty-transport/4.1.45.Final/netty-transport-4.1.45.Final.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/io/netty/netty-resolver/4.1.45.Final/netty-resolver-4.1.45.Final.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/io/netty/netty-resolver-dns/4.1.45.Final/netty-resolver-dns-4.1.45.Final.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/io/netty/netty-codec-dns/4.1.45.Final/netty-codec-dns-4.1.45.Final.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/io/netty/netty-handler/4.1.45.Final/netty-handler-4.1.45.Final.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/javax/cache/cache-api/1.1.1/cache-api-1.1.1.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/io/projectreactor/reactor-core/3.3.2.RELEASE/reactor-core-3.3.2.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/reactivestreams/reactive-streams/1.0.3/reactive-streams-1.0.3.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.10.2/jackson-dataformat-yaml-2.10.2.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/com/fasterxml/jackson/core/jackson-core/2.10.2/jackson-core-2.10.2.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/com/fasterxml/jackson/core/jackson-databind/2.10.2/jackson-databind-2.10.2.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/com/fasterxml/jackson/core/jackson-annotations/2.10.2/jackson-annotations-2.10.2.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/net/bytebuddy/byte-buddy/1.10.6/byte-buddy-1.10.6.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/jodd/jodd-bean/3.7.1/jodd-bean-3.7.1.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/jodd/jodd-core/3.7.1/jodd-core-3.7.1.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/boot/spring-boot-starter-redis/1.2.5.RELEASE/spring-boot-starter-redis-1.2.5.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/spring-context-support/5.2.3.RELEASE/spring-context-support-5.2.3.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/spring-core/5.2.3.RELEASE/spring-core-5.2.3.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/spring-jcl/5.2.3.RELEASE/spring-jcl-5.2.3.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/spring-tx/5.2.3.RELEASE/spring-tx-5.2.3.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/data/spring-data-redis/2.2.4.RELEASE/spring-data-redis-2.2.4.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/data/spring-data-keyvalue/2.2.4.RELEASE/spring-data-keyvalue-2.2.4.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/data/spring-data-commons/2.2.4.RELEASE/spring-data-commons-2.2.4.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/springframework/spring-oxm/5.2.3.RELEASE/spring-oxm-5.2.3.RELEASE.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/redis/clients/jedis/3.1.0/jedis-3.1.0.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/apache/commons/commons-pool2/2.7.0/commons-pool2-2.7.0.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/io/netty/netty-all/4.1.6.Final/netty-all-4.1.6.Final.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/cglib/cglib/3.2.4/cglib-3.2.4.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/ow2/asm/asm/5.1/asm-5.1.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/apache/ant/ant/1.9.6/ant-1.9.6.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/apache/ant/ant-launcher/1.9.6/ant-launcher-1.9.6.jar:/Users/fengguoqiang/Software/apache-maven-3.6.0/myrepo/org/projectlombok/lombok/1.18.10/lombok-1.18.10.jar com.max.xihu.jvm.CheckCompileResult
    218    1       3       java.lang.AbstractStringBuilder::<init> (12 bytes)
    219    2       3       java.lang.String::<init> (82 bytes)
    219    3     n 0       java.lang.System::arraycopy (native)   (static)
    219    6       2       java.lang.String::equals (81 bytes)
    219    4       3       java.nio.charset.CharsetEncoder::maxBytesPerChar (5 bytes)
    220    8       3       java.lang.StringBuilder::append (8 bytes)
    220   11       3       java.lang.String::indexOf (25 bytes)
    221   12  s    1       java.util.Vector::size (5 bytes)
    221   13       3       java.io.UnixFileSystem::normalize (75 bytes)
    221    7 %     4       java.lang.String::hashCode @ 24 (55 bytes)
    221    5 %     4       java.lang.String::indexOf @ 37 (70 bytes)
    222   10 %     4       sun.nio.cs.UTF_8$Encoder::encode @ 20 (359 bytes)
    225   20       3       java.lang.String::lastIndexOf (52 bytes)
    225   14       3       java.lang.String::length (6 bytes)
    225   25       3       java.lang.Math::min (11 bytes)
    225   23       3       java.util.Arrays::copyOfRange (63 bytes)
    226    9       4       java.lang.String::charAt (29 bytes)
    226   22       1       java.lang.StringCoding$StringEncoder::requestedCharsetName (5 bytes)
    226   15       3       java.lang.String::getBytes (27 bytes)
    227   30       4       java.lang.String::hashCode (55 bytes)
    227   29       3       java.lang.String::startsWith (72 bytes)
    227   27       4       java.lang.Object::<init> (1 bytes)
    227   26       3       java.lang.String::startsWith (7 bytes)
    227   28       3       java.lang.String::toCharArray (25 bytes)
    228   32       3       java.lang.AbstractStringBuilder::append (50 bytes)
    228   34       4       java.lang.String::indexOf (70 bytes)
    228   33       3       java.lang.String::getChars (62 bytes)
    229   36       3       java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)
    229   38       3       java.lang.String::indexOf (7 bytes)
    230   37       3       java.util.HashMap::hash (20 bytes)
    230   31       3       java.util.BitSet::get (69 bytes)
    230   24       3       java.util.Arrays::copyOf (19 bytes)
    231   35       1       java.lang.ref.Reference::get (5 bytes)
    231   21       1       sun.instrument.TransformerManager::getSnapshotTransformerList (5 bytes)
    231   16   !   3       java.lang.StringCoding::encode (120 bytes)
    233   39 %     3       com.max.xihu.jvm.CheckCompileResult::doubleValue @ 2 (18 bytes)
    233   40       3       java.lang.AbstractStringBuilder::newCapacity (39 bytes)
    233   41 %     4       com.max.xihu.jvm.CheckCompileResult::doubleValue @ 2 (18 bytes)
    234   17   !   3       java.lang.StringCoding$StringEncoder::encode (179 bytes)
    234   39 %     3       com.max.xihu.jvm.CheckCompileResult::doubleValue @ -2 (18 bytes)   made not entrant
    234   41 %     4       com.max.xihu.jvm.CheckCompileResult::doubleValue @ -2 (18 bytes)   made not entrant
    234   42       4       com.max.xihu.jvm.CheckCompileResult::doubleValue (18 bytes)
    235   43 %     4       com.max.xihu.jvm.CheckCompileResult::doubleValue @ 2 (18 bytes)
    236   18       3       java.lang.StringCoding::access$300 (8 bytes)
    236   44       3       com.max.xihu.jvm.CheckCompileResult::calcSum (26 bytes)
    236   19       3       java.lang.StringCoding::safeTrim (24 bytes)
    236   45 %     4       com.max.xihu.jvm.CheckCompileResult::calcSum @ 4 (26 bytes)
    238   46       4       com.max.xihu.jvm.CheckCompileResult::calcSum (26 bytes)
    239   44       3       com.max.xihu.jvm.CheckCompileResult::calcSum (26 bytes)   made not entrant

其中带有%的输出说明是由回边计数器触发的OSR编译。

2.编译优化技术

我们都知道,编译执行肯定比解释执行更快。解释执行字节码时不仅需要额外消耗时间,更重要的原因是虚拟机设计团队几乎把对代码的所有优化措施都集中在了即时编译器中。所以说即时编译器编译的本地代码会比Javac产生的字节码更优秀。即时编译器有非常多的优化技术,不进行一一列举,仅介绍几种比较常见的优化技术。

  • 语言无关的经典优化之一:公共子表达式消除。
  • 语言相关的经典优化之一:数组范围检查消除。
  • 最重要的优化技术之一:方法内联。
  • 最前沿的优化技术之一:逃逸分析。

2.1公共子表达式消除

什么是公共子表达式消除嘞?就是如果一个表达式E已经计算过了,并且从先前的计算到现在E中的所有变量的值都没有发生过变化,那么E的这次出现就成为公共子表达式。
因为出现了这种每次变量都不变,并且表达式的值都是一样的,那么干嘛要重新计算呢,干脆把前面计算的结果拿来用就行了。

  1. 局部公共子表达式消除
  2. 全局公共子表达式消除

这两个区别就是看优化的范围是仅限于程序的基本块内,还是说涵盖了多个基本块。
举个例子说明下:

int r = (a * b) * 7 + a + (a + b * a)

如果a * b的值在程序的执行过程中都不变,那么就可以优化为下面表达式

int r = E * 7 + a + (a + E)

不仅如此,还可以进行代数化简为:

int r = E * 8 + 2 * a

2.2数组范围检查消除

数组越界可能作为程序员都会遇到过,但是不同的语言可能有不同的处理方式。Java语言不像C,C++那样本质上是裸指针操作。Java是一门动态安全的语言,如果有个数组foo[],在Java中访问的话,例如foo[i]。系统会自动进行上下界的范围检查,如果i<0||i>foo.length的话,就会抛出运行时异常:java.lang.ArrayIndexOutOfBoundsException。抛出异常对于开发者来说是一件好事儿,即使没有编写防御型的代码,也至少不会出现指针溢出之类的攻击。

但是呢,对于虚拟机执行子系统,数组的读写都有有隐含的条件判断的,那么大量的数组访问,无疑会造成一种性能损失。无论如何,为了安全,数组边界的检查是必须要做的,但是数组边界的检查是不是必须在运行期间一次不落的都要检查,答案是否定的。比如说foo[3],只要编译期间根据数据流能确定foo.length的值,并判断下标3没有越界,那么执行期间就无须判断了。而且循环之内访问数组,如果编译期通过数据流确定循环的范围永远在[0,foo.length]之内,那整个循环中就可以把数组的上下界检查消除,节省了很多的条件判断操作。

除了数组的边界消除优化之外,还有一种避免思路—隐式异常处理。使用伪代码举个例子说明下:

if (foo != null){
	return foo.value;
}else{
	throw new NullPointerException();
}

使用隐式异常优化之后:

try{
	return foo.value;
}catch(segment_fault){
	uncommon_trap();//虚拟机注册一个Segment Fault的信号异常处理器
}

这样的话,只要foo不为空,那么就会减少一次非空判断的开销。当时当foo真的为空时,必须转到异常处理器中恢复并抛出NPE,这个过程需要从用户态转到内核态中处理,结束后,再返回到用户态。可想而知一次非空判断的开销和用户态与内核态之间的切换,哪个更耗时,毋庸置疑是后者了。所以foo经常为空的话,反而会让程序更慢。不过,HotSpot虚拟机可不笨,它会根据运行期收集的Profile信息自动选择最优方案。

2.3方法内联

方法内联是编译器最重要的优化手段之一,其实就是把目标方法的代码“复制”到发起调用方之后,避免发生真实的方法调用而已。这种优化不仅减少了方法的调用,而且还为其他优化手段建立了良好的基础。为什么这么说呢,先来看段代码,:

public static void foo(OBject obj){
	if (obj != null) {
		System.out.println("do it")
	}
}

public static void testInline(String[] args){
	Object obj = null;
	foo(obj);//调用foo方法
}

可以看得出,testInline方法内部都是些无用代码,如果不做内联,后续即使进行无用代码消除优化,也无法发现“Dead Code”,因为分开来看的话,两个方法里面的操作可能都有意义。

2.4逃逸分析

最后介绍的逃逸分析可谓是比较前沿的优化技术了。它并不是直接优化代码,而是为其他优化手段提供依据的分析技术
逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,可能会被其他方法引用,称为方法逃逸。例如:作为调用参数传递到其他方法中。还有被其他线程访问到,称为线程逃逸。例如:赋值给类变量或者其他线程中访问的实例变量。

如果能证明一个对象不会逃逸到方法或者线程之外,也就是不能被别的方法或者线程通过任何途径访问到这个对象,那么就可以为这个变量进行一些高效的优化。那么有哪些高效的优化呢?

  1. 栈上分配(Stack Allocation)
    在Java虚拟中,对象在堆上分配已经成为Java程序员的最清楚的常识了。而且虚拟机的垃圾回收也是针对堆来进行的。回收垃圾无论是筛选可收回对象,还是回收整理内存都需要消耗时间。如果确定一个对象不会逃逸出方法之外,那么就可以将该对象在栈上分配,对象所占用的内存空间随着栈帧的出栈而销毁。一般来说,程序中是有大量的不会逃逸的对象,如果能使用栈上分配,对于垃圾收集系统的压力将会小很多。

开启逃逸分析 (-XX:+DoEscapeAnalysis),JDK1.6以后默认开启。可通过参数-XX:+PrintEscapeAnalysis来查看分析结果。

  1. 同步消除(Synchronization Elimination)
    线程同步本身就是非常耗时的过程,如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那这个变量的读写肯定不会有竞争,那么就可以将同步措施进行消除。

开启同步消除(-XX:+EliminateLocks)

  1. 标量替换(Scalar Replacement)
    标量是指一个数据已经无法分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int,long等数值类型以及reference类型等)都不能在进行一一分解。相对来说,如果一个数据可以继续分解,就称为聚合量,Java中的对象就是典型的聚合量。如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆的话,那程序真正执行的时候,就不需要创建对象,而直接创建被这个方法用到的成员变量来替换。对象拆分后,不仅可进行栈上分配和读写,而且可以为后续进一步的优化创建条件。

开启标量替换(-XX:+EliminateAllocations),可通过参数-XX:PrintEliminateAllocations来查看标量替换情况。

原文地址:https://blog.csdn.net/weixin_30484149/article/details/110288450

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


jinfo 命令可以用来查看 Java 进程运行的 JVM 参数,命令如下:[root@admin ~]# jinfo --helpUsage: jinfo [option] &lt;pid&gt; (to connect to running process) jinfo [option] &lt;executable &lt;core&gt; (to connect to a core file) jinfo [option] [serve
原文链接:https://www.cnblogs.com/niejunlei/p/5987611.htmlJava Virtual Machine Stacks,线程私有,生命周期与线程相同,描述的是Java方法执行的内存模型:每一个方法执行的同时都会创建一个栈帧(Stack Frame),由于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法的执行就对应着栈帧在虚拟机栈中的入栈,出栈...
java 语言, 开发者不能直接控制程序运行内存, 对象的创建都是由类加载器一步步解析, 执行与生成与内存区域中的; 并且jvm有自己的垃圾回收器对内存区域管理, 回收; 但是我们已经可以通过一些工具来在程序运行时查看对应的jvm内存使用情况, 帮助更好的分析与优化我们的代码;jps查看系统中有哪些java进程jps 命令类似与 linux 的 ps 命令,但是它只列出系统中所有的 Java 应用程序。 通过 jps 命令可以方便地查看 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息
1.jvm的简单抽象模型:  2.类加载机制     双亲委派模型是为了防止jdk核心类库被篡改,如果需要打破可以重写Classloader.loadClass方法。r 双亲委派模型:一个类加载器收到一个类的加载请求,他会先判断自身是否已存在该类,如果不存在上抛给上一级类加载器ClassLoad
堆外内存JVM启动时分配的内存,称为堆内存,与之相对的,在代码中还可以使用堆外内存,比如Netty,广泛使用了堆外内存,但是这部分的内存并不归JVM管理,GC算法并不会对它们进行回收,所以在使用堆外内存时,要格外小心,防止内存一直得不到释放,造成线上故障。堆外内存的申请和释放JDK的ByteBuffe
1.springboot和tomcat2.springcloud的请求如何通过网关鉴权?3.springmvc启动时组件的加载顺序?4.mybatis如何同时更新三条记录5.hibernate实现级联更新6.一个web程序应用程序启动时的加载流程7.如何向www.baidu.com地址发出请求时,并获取相应?8.???9.谈谈你对tcp/iptelnetudp协
堆设置-Xms256M:初始堆大小256M,默认为物理内存的1/64-Xmx1024M:最大堆大小1024M,默认为物理内存的1/4,等于与-XX:MaxHeapSize=64M-Xmn64M:年轻代大小为64M(JDK1.4后支持),相当于同时设置NewSize和MaxNewSize为64M-XX:NewSize=64M:初始年轻代大小-XX:MaxNewSize=256M:最大年轻代大小(默认
一.概述收集算法(JVM之垃圾回收-垃圾收集算法)是内存回收的抽象策略,垃圾收集器就是内存回收的具体实现。JVM规范对于垃圾收集器的应该如何实现没有任何规定,因此不同的厂商、不同版本的虚拟机所提供的垃圾收集器差别较大,这里只看HotSpot虚拟机。就像没有最好的算法一样,垃圾收集器
Java中的堆是JVM所管理的最大的一块内存空间,主要用于存放各种类的实例对象,如下图所示: 在Java中,堆被划分成两个不同的区域:新生代(Young)、老年代(Old)。新生代(Young)又被划分为三个区域:Eden、S0、S1。 这样划分的目的是为了使JVM能够更好的管理堆内存中的对象,包
JVM深入理解JVM(4)——如何优化JavaGC「译」 PostedbyCrowonAugust21,2017本文翻译自SangminLee发表在Cubrid上的”BecomeaJavaGCExpert”系列文章的第三篇《HowtoTuneJavaGarbageCollection》,本文的作者是韩国人,写在JDK1.8发布之前,虽然有些地
 JVM深入理解JVM(2)——GC算法与内存分配策略 PostedbyCrowonAugust10,2017说起垃圾收集(GarbageCollection,GC),想必大家都不陌生,它是JVM实现里非常重要的一环,JVM成熟的内存动态分配与回收技术使Java(当然还有其他运行在JVM上的语言,如Scala等)程序员在提升开
运行时数据区  线程独有本地方法栈、虚拟机栈、程序计数器这些与线程对应的数据区会随着线程开始和结束创建和销毁  整体公有元数据区(又称方法区)、堆区会随着虚拟机启动而创建,随着虚拟机退出而销毁 
java整个堆大小设置:Xmx和Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍。永久代PermSize和MaxPermSize设置为老年代存活对象的1.2-1.5倍年轻代Xmx的设置为老年代存活对象的1-1.5倍老年代的内存大小设置为老年代存活对象的2-3倍BTW: Sun官方建议年轻代
栈顶缓存(Top-of-StackCashing)技术基于栈式架构得虚拟机所使用的零地址指令更加紧凑,但完成一项操作的时候必然使用更多的入栈和出栈指令,这同时也就意味着将需要更多的指令分派次数和内存读写次数 由于操作数是存储在内存重的,因此频繁地执行内存读/写操作必然影响速度。 综上
自用。同样的代码在不同的平台生成的机器码是不一样的,为什么java代码生成的字节码文件,能在不同的平台运行?因为不同版本的jdk里面的虚拟机会屏蔽不同操作系统在底层硬件与指令上的区别。栈:线程栈,局部变量存放栈内存区域。线程(分配一个栈)运行分配栈将局部变量放入内存。怎么放:栈
jconsole监控:1.java启动命令加上参数java-Djava.rmi.server.hostname=172.16.17.247-Dcom.sun.management.jmxremote-Dcom.sun.management.jmxremote.port=2099-Dcom.sun.management.jmxremote.authenticate=false-Dcom.sun.management.jmxremote.ssl=false -XX:+Unlock
类加载器分类publicclassStackStruTest{publicstaticvoidmain(String[]args){//对用户自定义个类来说:默认使用系统类加载器进行加载-----AppClassLoaderClassLoaderclassLoader=StackStruTest.class.getClassLoader();System.out.p
堆体系结构一个JVM实例只存在一个堆内存,堆内存的大小是可调节的。类加载器读取类文件后,需要把类、方法、常量、变量放在堆内存中,保存所有引用类型的真实信息,以方便执行器指向,堆内存分为三个部分:年轻代、老年代、永久代。Java7之前,堆内存在逻辑上分为:年轻代、老年代、永久代。物
JVM深入理解JVM(5)——虚拟机类加载机制 PostedbyCrowonAugust21,2017在Class文件中描述的各种信息,最终都需要加载到虚拟机中之后才能运行和使用。而虚拟机中,而虚拟机如何加载这些Class文件?Class文件中的信息进入到虚拟机中会发生什么变化?本文将逐步解答这
保存(持久化)对象及其状态到内存或者磁盘Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在