解释型代码:程序可移植,相同的代码在任何有适当解释器的机器上,都能运行,可是速度慢。
编译型代码:速度快,电视不一样CPU平台的代码没法兼容。
java则是使用java的编译器先将其编译为class文件,也就是字节码;而后将字节码交由jvm(java虚拟机)解释执行。因为这个编译是在程序执行时进行的,所以被称为“即便编译”。java
对于程序来讲,一般只有一部分代码被常常执行,而应用的性能就取决于这些代码执行得有多快。这些关键代码段被称为应用的热点,代码执行得越多就被认为是越热。
所以JVM执行代码时,并不会当即编译代码:缓存
Client编译器和server编译器主要的区别在于编译代码的时机不一样。client编译器开启编译比server编译器要早。这意味着在代码执行的开始阶段,client编译器比server编译器要快,由于它的编译代码相比server编译器而言要多。
分层编译是综合了client和server的优势。在开启分层编译(-XX:+TieredCompilation)后代码先由client编译器编译,随着代码变热,由server编译器从新编译。jvm
JVM编译代码时,会在代码缓存中保留编译以后的汇编语言指令集。代码缓存的大小固定,因此一旦填满,JVM就不能编译更多代码了。
也就是说,若是代码缓存太小,那么就会有一些热点代码被编译了,而其余没有,最终致使应用的大部分代码都是解释运行(很是慢)。这个问题在使用client编译器或进行分层编译时很常见。
当代码缓存填满时,JVM一般会发出如下警告:性能
Java HotSopt(TM) 64-Bit Server VM warning:CodeCache is full.Compiler has bean disabled. Java HotSopt(TM) 64-Bit Server VM warning:Try increasing the code cache size using -XX:ReservedCodeCacheSize=
各平台代码缓存的默认大小:优化
jvm | jdk版本 | 大小 |
---|---|---|
32位client | Java8 | 32MB |
32位client | 分层编译,Java8 | 240MB |
64位client | 分层编译,Java8 | 240MB |
32位client | Java7 | 32MB |
32位server | Java7 | 32MB |
64位server | Java7 | 48MB |
64位server | 分层编译,Java7 | 48MB |
若是代码缓存设为1GB,JVM就会保留1GB的本地内存空间。若是是32位JVM,那么进程占用的总内存不能超过4GB(包括Java堆、JVM自身全部代码占用空间、分配给应用的本地内存、代码缓存)。
经过jconsole Memory(内存)面板的Memory Pool Code Cache图表,能够监控代码缓存。线程
一旦代码执行到必定次数,且达到了编译阈值,编译器就能够得到足够的信息编译代码了。
编译是基于两种JVM计数器的:方法调用计数器和方法中的循环回边计数器。回边实际上能够看做是循环完成执行的次数。
栈上替换:JVM能够在方法循环运行时进行编译,并在循环代码编译结束以后,JVM替换还在栈上的代码,循环的下一次迭代就会执行快的多的代码。
标准编译由-XX:CompileThreshold=N标志触发。使用client编译器时,N的默认值是1500,使用server编译器时为10000。
计数器会随着时间而减小,因此计数器只是方法或循环最新热度的度量。由此带来一个反作用是,执行不太频繁的代码可能永远不会编译。日志
-XX:+PrintCompilation
若是开启PrintCompilation,每次编译一个方法(或循环)时,JVM就会打印一行被编译的内容信息。
绝大多数编译日志的行具备如下格式:
timestamp compilation_id attributes (tiered_level) method_name size deopt
timestamp表示编译完成的时间
compilation_id内部的任务ID
attributes是一组5个字符长的串,表示代码编译的状态。若是给定的编译被赋予了特定属性,就会打印下面列表中所显示的字符,不然该属性就打印一个空格。
* % :编译为OSR
* s :方法是同步的
* !:方法有异常处理器
* b :阻塞模式时发生的编译
* n:为封装本地方法所发生的编译
tiered_level 若是程序没有使用分红编译的方式运行则为空,不然为数字,代表所完成编译的级别
method_name格式为:ClassName::method
而后是编译后代码大小(单位是字节)
最后,在某些状况下,编译日志的结尾会有一条信息,代表发生了某种逆优化,一般是“made not entrant”或”made zombie”code
135 1 n 0 java.lang.Thread::currentThread (native) (static) 136 2 3 java.util.Arrays::copyOf (19 bytes) 136 7 3 sun.nio.cs.UTF_8$Encoder::encode (359 bytes) 137 8 2 java.lang.String::hashCode (55 bytes)
使用jstat -compiler 进程ID 也能够看有多少方法被编译
使用jstat -printcompilation 5003 1000 表示进程ID为5003的程序每1秒输出一次最近被编译的方法server
当方法(或循环)适合编译时,就会进入到编译队列。队列则由一个或多个后台线程处理。编译队列是一种优先队列,即调用计数次数多的方法有更高的优先级。
当开启分层编译时,JVM默认开启多个client和server线程。对象
cpu数量 | C1的线程数(client) | C2的线程数(server) |
---|---|---|
1 | 1 | 1 |
2 | 1 | 1 |
4 | 1 | 2 |
8 | 1 | 2 |
16 | 2 | 6 |
32 | 3 | 7 |
64 | 4 | 8 |
128 | 4 | 10 |
编译器的线程数可经过-XX:CICompilerCount=N
标志来设置。对于分层编译来讲,设置的值中三分之一将用来处理client编译器队列,其他的线程(至少一个)用来处理server编译器队列。
使用分层编译时,线程数很容易超过系统限制,特别是有多个JVM同时运行的时候。在这种状况下,减小线程数有助于提升总体的吞吐量(尽管代价多是热身期会持续得更长)。
public class Point{ private int x,y; public int getX(){ return x; } public void setX(int i){ x = i;} }
若是你写下面的代码
Point p = getPoint(); p.setX(p.getX()*2);
编译后的代码本质上执行的是:
Point p = getPoint(); p.x = p.x *2;
方法是否内联取决于它有多热以及它的大小。
-XX:MaxInlineSize=N
默认是35字节,即只有方法小于35字节时第一次调用方法时就会被内联。
-XX:MaxFreqInlineSize=N
默认是325字节,即只有当一个方法频繁被调用而且小于325字节时会被内联。
-XX:+DoEscapeAnalysis
默认为true。逃逸分析可让JVM对一个对象根据代码来进行优化。