摘要
极客核心技术36讲系列,第1讲,看完之后的一些笔记。
要有 知其然更要知其所以然 的学习态度。
这篇文章主要是 记录了 Java的跨平台能力,或者说是Java的解释或编译运行的区别。
关键词:解释,编译,热点代码,热点探测,计数器,JIT、AOT、代码爆炸
2大特性:
对Java来说,最显著的特性有2个:
- 跨平台的能力。
- 有垃圾收集器(Garbage Collection)。通过GC来进行回收分配内存,大大减轻了开发者对内存管理操作的负担。
跨平台的能力:
Java 的宣传语是: Write once,run anywhere。
就是通过将 .java源码 用 -javac 命令编译成 .class的字节码(bytecode) 文件。 .class的字节码文件 和 基于特定平台的JVM 组成来实现跨平台的能力,这屏蔽了操作系统和硬件的细节。
java一般分为编译期 和 运行时。
- 编译期:
.java 文件 编译成 …class字节码文件 这段过程就是编译期。字节码文件还不能直接被机器执行。 而 C/C++ 编译后的 机器码 是可以直接被机器锁执行的。 - 运行时:
JVM会通过 类加载器(Class-Loader)来加载字节码,解释或者编译来运行。 在主流版本JDK8中,就是所谓的混合模式(-Xmixed),也就是解释和编译混合执行。
-解释执行:
JVM里内嵌有解释器(interpreter),可以将 字节码 转换成 机器码。
解释器逐条读入,逐条解释运行。
编译执行:
Oracle JDK里面,提供了HotSpot JVM,里面有JIT编译器(Just-In-Time Completion),也就是通常说的动态编译器,可以将热点代码编译成机器码。这部分就是编译执行。
热点代码:执行次数较多的代码。
热点探测的方法:(判断热点代码 是不是需要 触发 JIT编译)
基于采样的热点探测:虚拟机周期性地检查线程的栈顶,看方法是不是经常出现。
基于计数器的热点探测:虚拟机给每个方法甚至代码块都建立了一个计数器,统计一个方法的执行次数。超过一定的阈值就触发。
HotSpot JVM 使用的是 基于计数器的热点探测。
主要有2类计数器:
- 方法调用计数器:
在client模式下,阈值是1500次。
在server模式下,阈值是10000次。
可以通过虚拟机参数 -XX:CompileThreshold 设置。- 回边计数器:主要统计方法中 循环体代码 的执行次数。
还存在热点衰减,时间段内调用方法的次数减少,计数器的记录也就减少。
-
Oracle HotSpot JVM 内置了2个不同的 JIT 编译器。
C1:client模式。适用于对启动速度敏感的应用,比如Java桌面应用。调用的门限1500次 来收集信息进行编译。
C2:server模式。适用于长时间运行的服务端应用,这里默认采用分层编译(TieredCompilation)。会进行 上万次的调用 来收集足够的信息进行编译。 -
在Oracle JDK9时,还引入了一种AOT编译方式(Ahead-Of-Time)。直接把字节码 编译成 机器码,可以避免JIT预热方面的开销。
-
Oracle JDK 支持 分层编译 和 AOT 协助运行。
只有对执行频繁的代码才值得编译。
如果把所有的代码都编译就是显著增加代码的所占空间,导致 代码爆炸(code size explosion)
可以指定虚拟机参数进行模式选择:
- -Xint:只进行 解释执行。
- -Xcomp:关闭解释器。这会导致JVM启动变慢很多!有些优化方式,如果不进行profiling,就不能进行有效的优化。
JIT编译都很注重 profile 的信息。 profile信息是需要通过执行用户程序来获取的。
可以在解释器里实现收集profile的功能,等解释执行一段时间后,再触发JIT编译,
这样就可以很好的平衡收集profile与编译优化这两方面。
或者说JRockit VM:没有解释器,对所有Java 方法都做JIT编译。
收集profile再JIT编译器里面做。等收集到了足够的profile后触发重新编译,生成出优化的、不带profile的版本。
参考链接:
https://www.zhihu.com/question/37389356
https://blog.csdn.net/weixin_38608626/article/details/88350526
https://www.ibm.com/developerworks/cn/java/j-perry-java-platform-overview/index.html