from:http://www.tuicool.com/articles/r6Z7vaj
JVM自动监控这所有方法的执行,如果某个方法是热点方法,JVM就计划把该方法的字节码代码编译成本地机器代码,编译成机器代码的过程是在独立线程中执行的,不会影响程序的执行,这个过程就是JIT(just in time)。
JIT针对下面的几种方式进行优化
- 把bytecode编译成本地代码
- 单态调度(monomorphic dispatch),当个对象的类和其父类间有方法重写时,JVM调用对象的方法可以通过对象的类型路径来判断应该调用父类的方法还是子类的方法,对此JIT进行优化,这种优化是C++所不具备的,C++中需要查找虚函数表。
- 循环展开(loop unrolling)
- 类型锐化
- 逃逸分析(escape analysis)
- 移除无用代码(这个现在IDE会提示我们的,比如:intellij idea)
- Intrinsics
- 分支预测
- 方法内联(inlining,对性能的提升很大),默认情况,<= 35字节码的方法可以进行内联,通过这个来修改内联方法的最大值:-XX:MaxInlineSize=,通过-XX:FreqInlineSize=来设置频繁调用方法的临界值
这些优化方法通常是层层依赖的,所以当JIT优化后的代码被JVM应用,就会开始尝试进行更上一层次的优化。因此我们写代码的时候,应该尽量往这些优化方式上面靠。
输出JIT编译过的方法
在JVM启动参数中添加如下的启动参数:
-XX:+PrintCompilation
输出内容类似这样:
31 23 s! sun.misc.URLClassPath::getLoader (136 bytes)
- 第1列 31:为JVM启动后到该方法被编译相隔的时间,单位为毫秒
- 第2列 23:编译ID,用来跟踪一个方法的编译、优化、深度优化
- 第3列 s!:s是指该方法是synchronized,感叹号是指该方法有对异常的处理
- 第4列 sun.misc.URLClassPath::getLoader:被编译的方法
- 第5列 (136 bytes):方法的字节大小
输出JIT编译的细节信息
通过添加参数-XX:+PrintCompilation,可以看到的信息其实并不具体,比如:那些方法进行了内联,内联后的二进制代码是怎么样的都没有。而要输出JIT编译的细节信息,就需要在JVM启动参数中添加这个参数:
-XX:+LogCompilation -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+PrintAssembly
输出的编译信息,默认情况是在启动JVM的目录下一个名为:hotspot_pid<PID>.log的文件
如果想指定文件路径和文件名的话,可以再添加一个启动参数:
-XX:LogFile=<pathto file>
输出的是一个很大的xml文件,可能有几百兆,内容大致如下:
<nmethodcompile_id='2' compiler='C1' level='3' entry='0x00000001023fe240' size='1224' address='0x00000001023fe0d0' relocation_offset='288' insts_offset='368' stub_offset='880' scopes_data_offset='1032' scopes_pcs_offset='1104' dependencies_offset='1200' nul_chk_table_offset='1208' method='java/lang/String hashCode ()I' bytes='55' count='512' backedge_count='8218' iicount='512' stamp='0.350'/>
而且内容很难读懂,建议使用JITWatch( https://github.com/AdoptOpenJDK/jitwatch/ )的可视化界面来查看JIT编译的细节信息。同时JITWatch还可以给出很多优化建议,给我们有效的优化代码提供参考,详见下文。
JIT编译模式
C1: 通常用于那种快速启动的GUI应用,对应启动参数:-client
C2: 通常用于长时间允许的服务端应用,对应启动参数:-server
分层编译模式(tiered compilation):这是自从Java SE 7以后的新特性,可通过添加启动参数来开启:
-XX:+TieredCompilation
这个特性在应用启动阶段使用C1模式以达到快速启动的效果,一旦应用程序运行起来以后,C2模式将取代C1模式,以进行更深度的优化。在Java SE 8中,这个特性是默认的。
JITWatch
前面也提到了,JITWatch可以通过可视化界面来帮助我们分析JVM输出的JIT编译输出日志,还可以帮助我们静态分析jar中的代码是否符合JIT编译优化的条件,还可以以曲线图形的方式展示JIT编译的整个过程中的一些指标,非常好用的工具。
下载
JITWatch需要在github上把代码clone下来,然后用maven来运行,地址为: https://github.com/AdoptOpenJDK/jitwatch/
运行JITWwatch
在代码根目录下执行 launchUI.sh(
Linux/Mac)或则 launchUI.bat(windows)
如果你使用maven,也可以在代码根目录下这样运行(其他运行方式,请参考JITWatch的github首页)
mvncleancompileexec:java
如果你使用的是mac,而且idk版本是jdk7,且运行mvn clean compile exec:java时出现下面的错误和异常时:
Causedby: java.lang.NullPointerException atcom.sun.t2k.MacFontFinder.initPSFontNameToPathMap(MacFontFinder.java:339) atcom.sun.t2k.MacFontFinder.getFontNamesOfFontFamily(MacFontFinder.java:390) atcom.sun.t2k.T2KFontFactory.getFontResource(T2KFontFactory.java:233) atcom.sun.t2k.LogicalFont.getSlot0Resource(LogicalFont.java:184) atcom.sun.t2k.LogicalFont.getSlotResource(LogicalFont.java:228) atcom.sun.t2k.CompositeStrike.getStrikeSlot(CompositeStrike.java:86) atcom.sun.t2k.CompositeStrike.getMetrics(CompositeStrike.java:132) atcom.sun.javafx.font.PrismFontUtils.getFontMetrics(PrismFontUtils.java:31) atcom.sun.javafx.font.PrismFontLoader.getFontMetrics(PrismFontLoader.java:466) atjavafx.scene.text.Text.<init>(Text.java:153) atcom.sun.javafx.scene.control.skin.Utils.<clinit>(Utils.java:52) ... 13 more [ERROR] Failedto executegoalorg.codehaus.mojo:exec-maven-plugin:1.5.0:java (default-cli) onprojectjitwatch-ui: Anexceptionoccuredwhile executingtheJavaclass. null: InvocationTargetException: Exceptionin Applicationstartmethod: ExceptionInInitializerError: NullPointerException -> [Help 1]
请在org.adoptopenjdk.jitwatch.launch.LaunchUI类的main函数开头处添加下面的代码(或者直接使用我fork修改好的 JITWatch ):
final Class<?> macFontFinderClass = Class.forName("com.sun.t2k.MacFontFinder"); final java.lang.reflect.FieldpsNameToPathMap = macFontFinderClass.getDeclaredField("psNameToPathMap"); psNameToPathMap.setAccessible(true); if (psNameToPathMap.get(null) == null) { psNameToPathMap.set( null, new java.util.HashMap<String, String>()); } final java.lang.reflect.FieldallAvailableFontFamilies = macFontFinderClass.getDeclaredField("allAvailableFontFamilies"); allAvailableFontFamilies.setAccessible(true); if (allAvailableFontFamilies.get(null) == null) { allAvailableFontFamilies.set( null, new String[] {}); }
然后重新运行即可看到JITWatch的界面。
Reference
http://www.oracle.com/technetwork/articles/java/architect-evans-pt1-2266278.html
https://www.chrisnewland.com/images/jitwatch/HotSpot_Profiling_Using_JITWatch.pdf