标签:
即时编译器概述
编译热点代码
基本调优:Client 或 Server
启动优化
可以看到,对于中等大小的GUI应用,使用client编译器,启动时间最短,有将近38.5%的提升。对于大型应用,使用各种编译器的启动时间差别不大。
批处理应用优化
观察上图,可以得出以下结论:
1)当股票数比较少的时候(1~100),client编译器完成的最快。
2)当股票数很多的时候,使用server编译器就变得更快了。
3)tiered compilation总是比server编译器要快(和client比,即使在股票数很少的情况下,性能相差也不大),这是因为,tieredcompilation会对一些执行次数较少的代码也进行编译(编译后比解释执行要快)。
长时间运行的应用优化
观察上图,可以得出以下结论:
1)由于测试周期是60秒,所以,即使warm-up period是0s,server编译器也有足够的时间来发现热点代码并进行编译,所以,server编译器总是比client编译器的吞吐量要高。
2)同样的tiered compilation总是比server编译器的吞吐量要高(原因见上文,批处理应用优化)。
Java和JIT 编译器版本
答案并不是这样的,而是需要根据情况确定。
使用32bit版本的优点主要有两个:1)占用内存少,因为对应引用都是32位的 2)性能高,因为CPU操作32位的内存引用要比操作64位的内存引用要快。缺点也有两个:1)使用堆内存的大小不能超过4GB(windows为3GB,Linux为3.5GB) 2)程序中,如果使用了大量的long和double变量,不能充分使用64位寄存器,不过这种情况比较少见。
一般来说,32bit JVM上的32bit编译器要比同样配置的64bit编译器要快5%~20%。
上图是对不同平台下,使用不同参数对应的编译器。
编译器的中间段优化
Code Cache优化
Java HotSpot(TM) 64-Bit Server VM warning: CodeCacheis full.
Compiler has been disabled.
Java HotSpot(TM) 64-Bit Server VM warning: Tryincreasing the
code cache size using -XX:ReservedCodeCacheSize=
从上图可以看到,Java7的Code Cache 通常是不够的,一般都需要进行加大。到底增大到多少,这个比较难给出精确值,一般是默认值的2倍或4倍。
编译阈值
探寻编译过程
timestamp compilation_idattributes (tiered_level) method_name size deopt
小技巧:使用jstat来查看编译器的行为
% jstat -compiler 5003
Compiled Failed Invalid Time FailedTypeFailedMethod
206 0 0 1.97 0
5003为JVM的进程ID,通过这个命令可以看到有多少个方法被编译了,编译失败有多少个,最后编译失败的方法名字是什么。
% jstat -printcompilation 50031000
CompiledSize Type Method
20764 1 java/lang/CharacterDataLatin1 toUpperCase
2085 1 java/math/BigDecimal$StringBuilderHelper getCharArray
还可以通过上面的命令来周期性(1000的单位为ms,也就是1秒执行一次)的打印编译信息,这样可以看到最后编译的方法名称。
timestamp compile_id COMPILESKIPPED: reason
发生错误的原因主要有两个:
1)Code cache filled:Code Cache空间已经满了,需要使用 ReservedCodeCache标志来增加空间
2)Concurrentclassloading:在编译的过程中,class发生了修改,JVM将会在后面重新编译它。
28015 850 net.sdo.StockPrice::getClosingPrice (5bytes)
28179 905 s net.sdo.StockPriceHistoryImpl::process(248 bytes)
28226 25 %net.sdo.StockPriceHistoryImpl::<init> @ 48 (156 bytes)
28244 935net.sdo.MockStockPriceEntityManagerFactory$MockStockPriceEntityManager::find(507 bytes)
29929 939net.sdo.StockPriceHistoryImpl::<init> (156 bytes)
106805 1568 ! net.sdo.StockServlet::processRequest(197 bytes)
通过上面的日志,可以得到下面的结论:
1)第一个方法getClosingPrice直到应用启动后的28s才进行了编译,之前已经有849个方法进行了编译。
2)process方法是synchronized
3)内部类的方法也会单独显示,比如:net.sdo.MockStockPriceEntityManagerFactory$MockStockPriceEntityManager::find
4)processRequest方法有异常处理
5)从StockPriceHistoryImpl的实现看,其中有一个大的loop,
public StockPriceHistoryImpl(String s, Date startDate, Date endDate) {
EntityManager em = emf.createEntityManager();
Date curDate = new Date(startDate.getTime());
symbol = s;
while (!curDate.after(endDate)) {
StockPrice sp = em.find(StockPrice.class, new StockPricePK(s, curDate));
if (sp != null) {
if (firstDate == null) {
firstDate = (Date) curDate.clone();
}
prices.put((Date)curDate.clone(), sp);
lastDate = (Date) curDate.clone();
}
curDate.setTime(curDate.getTime() + msPerDay);
}
}
这个loop执行的次数比构造函数本身多很多,因此这个loop会被采用OSR进行编译。因为OSR编译比较复杂(要在代码同时执行的时候进行编译,还要进行栈上替换),所以虽然它的编译ID很小(25,表明比较早就启动了编译),但是经过了较长时间才在编译日志中打印出来。
编译器的高级调优
编译线程
可以使用 -XX:CICompilerCount=N来调整编译线程的数目;如果使用的是tiered compilation,那么配置的1/3线程用于C1队列,其余的用于C2队列.
内联
我们可以通过增加这个大小,以便更多的方法可以进行内联;不过一般情况下,调优这个参数对于服务端应用的性能影响不大。
逃逸分析
比如:下面是一个阶乘类,负责存放阶乘的值和起始值。
public class Factorial {
private BigIntegerfactorial;
private int n;
public Factorial(int n) {
this.n = n;
}
public synchronizedBigInteger getFactorial() {
if (factorial == null)
factorial = ...;
return factorial;
}
}
将前100个数的阶乘存入数组:
ArrayList<BigInteger> list = new ArrayList<BigInteger>();
for (int i = 0; i < 100; i++){
Factorial factorial = new Factorial(i);
list.add(factorial.getFactorial());
}
对象factorial只会在for循环内部使用,于是JVM会对这个对象做比较多的优化:
1)不需要对函数getFactorial加锁
2)不需要将字段n保存在内存中,可以存放在寄存器中;同样的,factorial对象也可以保存在寄存器中
3)实际上JVM不会分配任何factorial对象,只需要维护每个对象的字段即可。
逃逸分析默认是打开的,只有在极少数的情况下,逃逸分析会出现问题。
反优化
Not Entrant Code
StockPriceHistory sph;
String log = request.getParameter("log");
if (log != null &&log.equals("true")) {
sph = new StockPriceHistoryLogger(...);
}
else {
sph = new StockPriceHistoryImpl(...);
}
// Then the JSP makes calls to:
sph.getHighPrice();
sph.getStdDev();
// and so on
如果开始有大量的 http://localhost:8080/StockServlet 调用(没有log参数),对象sph的真正类型就是StockPriceHistoryImpl 。JVM将会对这个类的构造函数进行内联,并做其它优化。
一段时间之后,有一个调用传入了log参数 http://localhost:8080/StockServlet?log=true , 这会导致之前的优化都变得无效(因为实现类发生了变化,变成StockPriceHistoryLogger) 。于是JVM会对原来编译的代码进行反优化,即:丢弃原来编译的代码,并对代码重新进行编译,然后替换原来编译的代码。上述过程的编译日志大致如下所示:
841113 25 %net.sdo.StockPriceHistoryImpl::<init> @ -2 (156 bytes) made not entrant
841113 937 snet.sdo.StockPriceHistoryImpl::process (248 bytes) made not entrant
1322722 25 %net.sdo.StockPriceHistoryImpl::<init> @ -2 (156 bytes) made zombie
1322722 937 snet.sdo.StockPriceHistoryImpl::process (248 bytes) made zombie
可以看到,OSR编译的构造函数和标准编译的process函数都首先进入made not entrant状态,一段时间后,进入到made zombie状态。
从名字上看,反优化不是一个好的事情,它应该会影响应用的性能。不过,根据实践证明,反优化对性能的影响有限。
40915 84 % 3net.sdo.StockPriceHistoryImpl::<init> @ 48 (156 bytes)
40923 3697 3net.sdo.StockPriceHistoryImpl::<init> (156 bytes)
41418 87 % 4net.sdo.StockPriceHistoryImpl::<init> @ 48 (156 bytes)
41434 84 % 3net.sdo.StockPriceHistoryImpl::<init> @ -2 (156 bytes) made not entrant
41458 3749 4net.sdo.StockPriceHistoryImpl::<init> (156 bytes)
41469 3697 3net.sdo.StockPriceHistoryImpl::<init> (156 bytes) made not entrant
42772 3697 3net.sdo.StockPriceHistoryImpl::<init> (156 bytes) made zombie
42861 84 % 3net.sdo.StockPriceHistoryImpl::<init> @ -2 (156 bytes) made zombie
看到上面有很多not entrant和zombie的消息,不要感到惊讶,这些都是正常的,说明JVM编译出了更高效的代码。
反优化Zombie Code
Tiered Complication 级别
标签:
原文地址:http://blog.csdn.net/qq_28674045/article/details/51896129