JVM内部的优化逻辑

解释执行:解释器逐条把字节码翻译成机器码并执行,跨平台的保证。

即时编译器:即时编译器先将字节码编译成对应平台的可执行文件,运行速度快。即时编译器会把这些热点代码编译成与本地平台关联的机器码,并且进行各层次的优化,保存到内存中

现在Java已经支持AOT编译,就是提前编译,不像JIT一样在运行期间,而是在编译期间就将字节码转换成机器码。在云原生中用的较多

JVM采取的是混合模式,解释加编译的方式,不常用的代码解释执行,小部分的热点代码编译执行,追求效率。

即时编译器的类型:HotSpot内置了两个JIT:client complier C1和server complierC2,缺点影响性能和增加启动时间

C1也称为Client Compiler,适用于执行时间短或者对启动性能有要求的程序

C2也称为Server Compiler,适用于执行时间长或者对峰值性能有要求的程序

分层编译也就是会结合C1的启动性能优势和C2的峰值性能优势,热点方法会先被C1编译,然后热点方法中的热点会被C2再次编译

开启分层编译参数,默认是解释器和其中一个配合

-XX:+TieredCompilation开启参数

分层编译的五大级别:

0 解释执行

1.简单的C1编译:仅仅使用我们的C1做一些简单的优化,不会开启Profiling(JVM的性能监控)

2.受限的C1编译代码:只会执行我们的方法调用次数(方法计数器)以及循环的回边次数(回边计数器)(多次执行的循环体)Profiling(监控)的C1编译

3.完全C1编译代码:我们Profiling里面所有的代码。也会被C1执行

4.C2编译代码:这个才是优化的级别。级别越高,我们的应用启动越慢,优化下来开销会越高,同样的,我们的峰值性能也会越高

通常C2 代码的执行效率要比 C1 代码的高出 30% 以上

简单C1,受限C1,完全C1效率逐步较低

热点代码:

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

如何找到热点代码?

判断一段代码是否是热点代码,是不是需要触发即时编译,这样的行为称为热点探测(Hot Spot Detection),探测算法有两种,分别如下:

  • 基于采样的热点探测(Sample Based Hot Spot Detection):虚拟机会周期的对各个线程栈顶进行检查,如果某些方法经常出现在栈顶,这个方法就是“热点方法”。好处是实现简单、高效,很容易获取方法调用关系。缺点是很难确认方法的 reduce,容易受到线程阻塞或其他外因扰乱。

  • 基于计数器热点探测(Counter Based Hot Spot Detection)

    为每个方法(甚至是代码块)建立计数器,执行次数超过阈值就认为是“热点方法”。优点是统计结果精确严谨。缺点是实现麻烦,不能直接获取方法的调用关系。

HotSpot 使用的是第二种——基于计数器的热点探测,并且有两类计数器:方法调用计数器(Invocation Counter )和回边计数器(Back Edge Counter )。

这两个计数器都有一个确定的阈值,超过后便会触发 JIT 编译。

方法调用

循环体代码,回边计数器目的就是触发栈上替换,OSR 实际上是一种技术,它指的是在程序执行过程中,动态地替换掉 Java 方法栈桢,从而使得程序能够在非方法入口处进行解释执行和编译后的代码之间的切换。也就是说,我只要遇到回边指令,我就可以触发执行切换。

逃逸分析?

对象不一定分配在堆上,但是之前写的复杂对象即使在栈上也会分配到堆里。

当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他方法或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。

这些是JIT的编译优化!!!!没有被JIT优化的不存在栈上分配!!!!

栈上分配:没有返回或者被全局引用,也就是没有被其他线程使用,就分配到栈上

锁消除:如果不会被其他线程访问,这个变量的读写就不会有竞争,同步措施就可以消除

标量替换:把一个对象拆开,把可能用到的成员变量恢复成原始类型来访问。

方法内联

Code Cache

JIT编译、JNI等都会编译代码到native code,其中JIT生成的native code占用了Code Cache的绝大部分空间,他是属于非堆内存的。本质是方法区,代码缓存的大小是固定的。一旦它满了,JVM就不会编译任何额外的代码,因为JIT编译器现在处于关闭状态。 幸运的是,JVM提供了一个 UseCodeCache 刷新选项来控制代码缓存区域的刷新。

忘了程序计数器:正在执行的指令地址

在JVM中,程序计数器是一个较小的内存区域,用于存储当前线程执行的字节码指令的地址。

栈帧:

  1. 局部变量表(Local Variables):存储了方法的参数(包括实例方法的this引用)和方法内部定义的局部变量。这些变量的存活期仅限于方法的执行过程。局部变量表的大小在编译期间就已确定,不会在运行时改变。

  2. 操作数栈(Operand Stack):或称为表达式栈,是一个后入先出(LIFO)栈,用于存储指令执行过程中的临时数据,如计算的中间结果。

  3. 动态链接(Dynamic Linking):或称为方法引用,每个栈帧内部包含一个指向运行时常量池中该栈帧所属方法的引用,以支持方法调用过程中的动态链接。这使得方法能够调用其他方法。

  4. 方法返回地址(Return Address):当一个方法调用结束后,程序需要知道接下来执行哪里的代码,方法的返回地址信息就是为了解决这个问题。它指向了方法被调用时的指令地址(即,调用者的位置)。

  5. 一些附加信息:例如,对于同步方法或同步块,栈帧中可能包含锁定同步对象的所有信息。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值