关键词:
此文已由作者赵计刚薪授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
1、两种执行方式:
解释执行(运行期解释字节码并执行)
强制使用该模式:-Xint
编译为机器码执行(将字节码编译为机器码并执行,这个编译过程发生在运行期,称为JIT编译)
强制使用该模式:-Xcomp,下面是两种编译模式
client(即C1):只做少量性能开销比高的优化,占用内存少,适用于桌面程序。
server(即C2):进行了大量优化,占用内存多,适用于服务端程序。会收集大量的运行时信息。
注意:
32为机器默认选择C1,可在启动时添加-client或-server来指定,64位机器若CPU>2且物理内存>2G则默认为C2,否则为C1
Hotspot JVM执行代码的机制:对在执行过程中执行频率高的代码进行编译,对执行频率不高的代码继续解释执行
查看当前机器默认是client模式还是server模式,使用:"java -version"命令,如下
其中,mixed mode表示"解释执行+编译执行"的混合模式
2、解释执行
查看 第三章 类文件结构与javap的使用 中的inc()方法的执行
或者查看《深入了解java虚拟机(第二版)》P272-P275
3、编译执行
编译的对象
OSR编译:编译整段代码,但是只有循环体部分会执行机器码,其他部分还是解释执行
方法
方法中的循环体
触发条件(执行频率大于多少)
client:13995 server:10700
该阈值可通过-XX:OnStackReplacePercent(注意该OSRP只是一个计算回边计数阈值的中间值),回边计数阈值
client:CompileThreshold*OSRP/100
server:CompileThreshold*(OSRP-InterPreterProfilePercentage)/100
-XX:OnStackReplacePercent:140 InterPreterProfilePercentage:33
client:1500 server:10000
该阈值可通过-XX:CompileThreshold来指定
这里"方法调用的次数"是指一段时间(半衰周期)内的调用次数,如果半衰周期内,该次数没有达到阈值,则该次数减半。
-XX:-UseCounterDecay 关闭上述机制,即半衰周期的无穷大
-XX:CounterHalfLifeTime 半衰周期
方法调用计数器:方法被调用的次数
回边计数器:循环体内循环代码的执行次数(即for中代码的循环的次数)
方法编译执行
解释器调用方法时,检查是否有已经存在的编译版本,如果有,执行机器码,如果没有,方法调用计数器+1,然后判断方法调用计数器是否超过阈值,若超过,进行编译,后台线程进行编译,前台线程继续解释执行(即不会阻塞),直到下一次调用方法时,如果编译好了,就直接执行机器码,如果没编译好,就解释执行。
循环体编译执行
解释器执行到循环体时,检查是否有已经存在的编译版本,如果有,执行机器码,如果没有,回边计数器+1,然后判断回边计数器是否超过阈值,若超过,进行编译,后台线程进行编译,前台线程继续解释执行(即不会阻塞),直到下一次执行到循环体时,如果编译好了,就直接执行机器码,如果没编译好,就解释执行。
4、C1优化
说明:关于全部的优化技术列表,查看《深入理解java虚拟机(第二版)》P346-P347
只做少量性能开销比高的优化,占用内存少,主要的优化包括:
方法内联
冗余消除
复写传播
消除无用代码
类型继承关系分析(CHA,辅助)
去虚拟化
4.1、方法内联、冗余消除、复写传播、消除无用代码
4.1.1、方法内联
方法内联含义:假设方法A调用了方法B,把B的指令直接植入到A中。
static class B int value; final int get() return value; public void foo() y = b.get(); //do something z = b.get(); sum = y + z;
说明:在上述代码中,b是B的一个实例。
方法内联之后,
public void foo() y = b.value; //do something z = b.value; sum = y + z;
方法内联的条件:
get()编译后的字节数<=35byte(默认) -XX:MaxInlineSize=35指定
方法内联的地位:
优化系列中最一开始使用的方式(因为是很多其他优化手段的基础)
消除方法调用的成本(建立栈帧、避免参数传递、避免返回值传递、避免跳转)
4.1.2、冗余消除
冗余消除:如上边的两个b.value冗余(前提,在do something部分没有对b.value进行操作,这也是我们在做优化之前需要先收集数据的原因)
假设在do something部分没有对b.value进行操作,进行冗余消除后,
public void foo() y = b.value; //do something z = y; sum = y + z;
4.1.3、复写传播
当然,在冗余消除后,JIT对上述的代码进行分析,发现变量z没用(可以完全用y来代替),进行"复写传播"之后,
public void foo() y = b.value; //do something y = y; sum = y + y;
4.1.4、无用代码消除
在"复写传播"后,发现"y=y"是无用代码,所以可以进行"无用代码的消除"操作,消除之后,
public void foo() y = b.value; //do something sum = y + y;
需要说明的是,这里的"无用代码的消除"是在前三部优化的基础上来做的,而javac编译中"语义分析"部分的"无用代码的消除"是直接消除一些直接写好的代码(例如:if(false))
4.2、类型继承关系分析、去虚拟化
public interface Animal public void eat(); public class Cat implements Animal public void eat() System.out.println("cat eat fish"); public class Test public void methodA(Animal animal) animal.eat();
首先分析Animal的整个"类型继承关系",发现只有一个实现类Cat,那么在methodA(Animal animal)的代码就可以优化为如下,
public void methodA(Animal animal) System.out.println("cat eat fish");
但是,如果之后在运行过程中,"类型继承关系"发现Animal又多了一个实现类Dog,那么此时就不在执行之前优化编译好的机器码了,而是进行解释执行,即如下的"逆优化"。
逆优化:
当编译后的机器码的执行不再符合优化条件,则该机器码对应的部分回到解释执行。
eg.比如"去虚拟化",如果编译之后,发现类的实现方法多于一种了,此时就要执行"逆优化"
5、C2优化
进行了大量优化,占用内存多,适用于服务端程序,对于C2优化,除了具有C1的优化措施后,还有很多优化。
逃逸分析(辅助):
开启:-XX:+DoEscapeAnalysis
根据运行状况来判断方法中的变量是否会被方法或外部线程所读取,若不会,此变量是不逃逸的。基于此,C2在编译时会做:
标量替换:开启 -XX:+EliminateAllocations
栈上分配
同步削除:开启 -XX:+EliminateLocks
5.1、标量替换
含义:将一个java对象打散,根据程序,将该对象中的属性作为一个个标量来使用。
Point point = new Point(1,2); System.out.println("point.x:" + point.x + ",point.y:" + point.y); //do after
若在//do after中(即前边两句代码之后的所有代码中)再没有其他代码访问"point对象"了,则将"point对象"打散并进行标量替换,
int x = 1; int y = 2; System.out.println("point.x:" + x + ",point.y:" + y);
好处:
如果对象中定义的所有变量有的并没有被用到,"标量替换"可以节省内存
执行时,不需要寻找对象引用,速度会快
5.2、栈上分配
含义:确定一个方法的变量不会逃逸出当前方法之外(即该变量不会被其他方法引用),则该变量可以直接分配在栈上,随方法执行结束,栈帧消失,该变量也消失,减轻GC压力。
好处:
执行时,不需要根据对象引用去堆中找对象,速度会快
分配在栈上,随方法执行结束,栈帧消失,该变量也消失,减轻GC压力。
使用栈上分配,必须开启标量替换
5.3、同步削除
含义:确定一个方法的变量不会逃逸出当前线程之外(即该变量不会被其他线程使用),则对于该变量的同步策略就消除掉,如下,
synchronized(cat) //do xxx
若cat不会逃逸出当前线程,则同步块可以去掉,如下,
//do xxx
总结:
解释器:
程序启动速度比编译快
节省内存(不需要编译,所以不需要放置编译后的机器码)
JIT编译器:
时间长了,对于"热点代码"的执行会快
注意:
使用JIT而不是使用在编译期直接编译成机器码,除了解释器部分的两条有点外,还为了在运行期收集数据,有目的的进行编译
免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 糟糕的软件设计:幻想出来的问题
【推荐】 机器学习十大算法之EM算法
【推荐】 棋牌游戏大咖会:人工智能与运营、安全方面的那些事
jvm总括三-字节码字节码指令jit编译执行
JVM总括三-字节码、字节码指令、JIT编译执行 java文件编译后的class文件,java跨平台的中间层,JVM通过对字节码的解释执行(执行模式,还有JIT编译执行,下面讲解),屏蔽对操作系统的依赖。一个字节(8位)可以储存256... 查看详情
jvm(代码片段)
Java虚拟机(JVM)是运行Java字节码的虚拟机JVM可以理解的代码叫做字节码(.class文件)Java程序从源代码到运行一般分3步:.java文件->.class文件->机器可以执行的二进制机器码字节码到机器码的过程中,JVM类加载器首先加载字节... 查看详情
jvm--12---执行引擎----解释器jit编译器
...va代码编译和执行过程Java代码编译Java源代码==>JVM字节码Java字节码的执行JVM字节码==>执行解释器(In 查看详情
jvm的解释执行与编译执行
1、原理字节码无法直接交给硬件执行需要虚拟机翻译成机器码才能执行,“翻译”的策略有两种:解释执行和编译执行,又称即时编译(JIT)。解释执行,以解释方式运行字节码,解释执行的意思是读一句,... 查看详情
执行引擎(代码片段)
...译器解释器:当Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令执行。JIT(JustInTimeCompiler)编译器:就是虚拟机将源代码编译成和本地机器平... 查看详情
jvm的解释执行与编译执行
1、原理 字节码无法直接交给硬件执行需要虚拟机翻译成机器码才能执行,“翻译”的策略有两种:解释执行和编译执行又称即使编译(JIT)。解释执行是没执行一句字节码的时候把字节码翻译成机器码并执行,优点是启... 查看详情
虚拟机字节码执行引擎
...作数栈、动态链接、方法返回地址。执行引擎运行的所有字节码指令都只针对虚拟机栈顶的栈帧而言。局部变量表 局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数、方法内部定义的局部变量... 查看详情
什么是java字节码?采用字节码的好处是什么?(代码片段)
在Java中,JVM可以理解的代码就叫做字节码(即扩展名为.class的文件),它不面向任何特定的处理器,只面向虚拟机。Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言... 查看详情
jvm解释器与jit编译器如何并存?
...指令,从而诞生了实现解释器在运行时采用逐行解释字节码的执行程序。 解释器真正意义上所承担的角色就是一个运行时“翻译者”,将字节码文件中的内容“翻译”为对应平台的本地机器指令执行,当一条字节... 查看详情
javac编译与jit编译
Java程序最初是仅仅通过解释器解释执行的,即对字节码逐条解释执行,这种方式的执行速度相对会比较慢,尤其当某个方法或代码块运行的特别频繁时,这种方式的执行效率就显得很低。于是后来在虚拟机中引入了JIT编译器(... 查看详情
字节码是jvm的核心
...解释器+编译器并存的架构,下面来看看在这个基础上,字节码是什么样的存在.提示:上一篇讲述的是JVM是编译器+即时编译架构的下一篇将要讲述Class文件本文很短,最好的阅读顺序是这三篇一起看其实Java的解释器+编译器并... 查看详情
字节码是jvm的核心
...解释器+编译器并存的架构,下面来看看在这个基础上,字节码是什么样的存在.提示:上一篇讲述的是JVM是编译器+即时编译架构的下一篇将要讲述Class文件本文很短,最好的阅读顺序是这三篇一起看其实Java的解释器+编译器并... 查看详情
java平台的理解
一:java不仅仅是解释执行(java编译成字节码,在转换成机器码),还有动态编译,在程序运行时,利用jit编译将热点代码编译成机器码,这时候就是编译执行,不是解释执行二: 三:jdk8是解释和编译混合模式,也有server和... 查看详情
compileonce,runanywhere
...呢?Java源代码经过javac编译后生成屏蔽操作系统和硬件的字节码,再经由JVM编译、解释执行。Java语言对字节码文件提供了三种执行方式:解释执行,逐条代码边翻译边执行编译执行(动态编译),由JIT(存在于JVM中,Javaintime,Java即 查看详情
虚拟机字节码操作引擎-----基于栈的字节码解释引擎
...有解析和分派两种方式,那么虚拟机是如何执行方法中的字节码指令的?1.解释执行 谈是解释执行还是翻译执行没有意义了,只有确定了某种具体的java实现版本和执行引擎运行模式时,谈解释执行还是编译执行才比较贴切。... 查看详情
java源代码怎么执行的
...为:编写java文件源码通过javac编译器将java源码编译为字节码流通过解释器解释执行字节码随着时间推移,即时编译器(JIT)介入,把越来越多的字节码编译 查看详情
jvm中的jit
...意思,又称即时编译。 Java程序是先从源代码编译到字节码,然后由Java虚拟机来解释执行字节码。当Java虚拟机在解释执行一个Java 查看详情
深入理解dalvik虚拟机-解释器的执行机制
...器+JIT的方式,解释器就是虚拟机来对Javac编译出来的字节码,做译码、运行,而不是转化成CPU的指令集。由CPU来做译码,运行。可想而知。解释器的效率是相对较低的,所以出现了JIT(JustInTime),JIT是将运行次数较多的函数,... 查看详情