Jit编译:just in time 编译. Java代码只有在执行一段时间之后才会进行jit编译。异步
Hotspot会编译优化那些热点代码,以求最大的性能收益。性能
Jit编译的好处:优化
1. 执行一段时间后,能够统计出哪些代码的调用频次高。线程
2. 执行一段时间后,编译器能够得到代码的一些性能信息,来加大编译优化的力度。因此,如今的jit编译优化甚至可能比C语言的编译优化作的还要效果好。日志
jit编译器有俩种:C1(client),和C2(Server) 俩种编译器。code
C1:编译的时机要比C2快,编译的代码要比C2多。好处了能够在程序启动刚开始就能得到比较好的性能。应用启动时间快。server
C2:编译的时机要比C1晚,由于它但愿能够得到更多的统计信息,进行优化程度更高的优化处理。应用启动时间慢。队列
分层编译:程序在刚启动的时候进行C1编译,随着代码执行的时间增长,再慢慢进行C2编译。内存
应用运行的时间短就采用C1,不然使用C2。通常来讲,用分层编译老是没错的。资源
Jit编译后的字节码会保存在code-cache中,而code-cache的大小是有限的。使用C1或者分层编译所编译的代码较多,比较容易填满codecache,而C2编译的代码量不会那么多。能够经过-XX:ReservedCodeCacheSize=N 来设置code-cache的最大值。须要注意的是,任何内存区均可能进行内存保留,因此把最大值设大,可能会致使占用过多的内存。也就是由于内存的消耗,因此,咱们须要考虑机器资源来权衡jit编译的程度,来最大化应用的性能。
JVM有俩个参数:方法调用计数器和循环回边计数器。
标准编译:当方法调用计数器和循环回边计数器记录的总次数超过必定的阈值,就对一个方法进行jit编译。
栈上替换:若是方法计数器的值没有达到阈值,可是循环回边计数器达到的必定的值,会对这个循环进行编译,而不会对整个方法进行编译,并在方法栈上进行替换。
C1和C2在编译时机上的不一样主要是因为这俩个计数器的阈值是不一样的。另外,计数器的值还会周期性的减小,因此它表示的是最新的调用热度。jit编译的阈值是能够调节的,可是要考虑到调整完后对code-cache带来的消耗。
对jit编译状况进行统计的方法有:
1. 开启 -XX:+PrintCompilation
2. Jstat -compiler pid
jstat -printcompilation
编译线程:编译的操做是异步的,会有编译线程在后台对达到要求的代码进行编译。线程分为client线程和server线程,分别用于C1,C2编译器。分层编译俩种线程都有。编译线程的量是可调节的,可是这一般影响的是应用在热身期的性能。若是过了热身期,这些编译线程就不会在占用cpu了。
内联:内联带来的性能提高是巨大的,一方面是内联自己带来的方法调用的减小。另外一个是否重要的方面是,内联后的代码,又能够促进不少其余优化。内联的关闭-XX:-Inline (默认是开的)
常规的内联:当方法很小时会进行内联。小于35个字节或-XX:MaxInlineSize=N 所设的值。
频繁调用带来的内联:当调用很频繁时,若是小于325个字节或者-XX:MaxFreqInlineSize=N 所设定的值。
逃逸分析(-XX:+DoEscapeAnalysis,默认为 true) ,编译器会作一些很是复杂和激进的优化。好比把一些没有用到的变量的计算省略掉。
逆优化:指编译器对一些编译进行撤回。有两种逆优化的情形:代码状态分别为“made not entrant”(代码被丢弃)和“made zombie”(产生僵尸代码)
“made not entrant”(代码被丢弃)的发生有俩种状况:
1. 若是一个方法内部的一个逻辑分支一直被调用,而后进行了jit编译,若是这时出现了另外一个逻辑分支的调用,就会致使原来的编译代码失效,而后被丢弃。在编译的详情日志里也会出现made not entrant,而后会出现made zombie
2. 在分层编译中,C1编译的代码,接着被C2编译后,以前的jit编译的代码就会被丢弃。
“made zombie”(产生僵尸代码) :指上面被丢弃的代码被GC回收了。
分层编译级别:
C1和C2都有本身的编译队列,存储达到阈值的须要编译的代码。若是C2的队列满了,则这段代码会进行2,而后进行C1编译。等C2队列空闲后再进行C2编译。一样,若是C1编译队列满了,也会进行相似的操做。