jvm启动cpu和负载高分析
我们知道,java进程在启动的时候经常会看到cpu会跑到百分之一百多,如果同一台机器有很多个java进程在同一时刻启动,每个进程cpu都是百分之一百多。为什么java启动会这么耗费CPU时间呢
原因如下:
主要是和jvm加载类文件有关,jvm在启动的时候会装载并连接所有除反射以外的类,而class文件是二进制的文件,需要从磁盘加载到内存然后解析,这种解析是很耗费cpu的,那么class文件越多,cpu耗费就越高,这正好解释了为什么同样输出Hello world,不同程序cpu占用率差别很大。这个推论正好也解释了之前遇到的另外一个线上项目的现象:每次启动后有大约1分钟左右系统的cpu和负载很高,过了1分钟后就恢复正常了
那么java虚拟机都是按需加载类的,那么我的main函数里面完全没有用到任何其它类,为何还是会加载呢?
今天针对这个问题继续查阅相关资料,发现原来所谓的“使用”,并不单单指我们通常编码所说看到的new、方法调用,而且还包括“依赖”。
举个简单的例子
package common; import java.util.*; public class CommonTester { public static void main(String[] args) { System.out.println("Hello, world......"); } /** * 这个函数在main里面并没有调用,但jvm还是会加载ArrayList类,因为CommonTester内依赖了ArrayList类 * @return */ public List<String> createStringList(){ return new ArrayList<String>(100); } }
以上这个例子中,CommonTester类在main函数中并没有调用crateStringList方法,但因为CommonTester类依赖ArrayList类,那么在加载CommonTester的时候发现其依赖ArrayList类,就会先加载ArrayList类。
所以,jvm最开始启动的时候肯定是先加载main函数所在的类,但是在加载这个类的时候发现依赖其它类,就会先加载其它类;加载其它类的时候也是这样处理,就像一个链式反应一样,所以基本上除了反射的类外,大部分类在jvm启动后,运行main函数前就已经加载完毕了。
为了验证这个推论,重新做了验证:
1)main函数所在的类注释所有其它代码,只保留main函数里面的打印语句
验证结果:cpu占用和自己写的简单的hello world程序一样,占用率大约1%左右
2)main函数所在的类注释所有其它代码,但保留import语句和main函数里面的打印语句
验证结果:和上面的一样,这个也很好理解,只保留import语句,因为这些语句并没有用,编译的时候这些语句都被优化掉了
jvm加载类的时候是多线程还是单线程 ?
验证这个问题流程如下
1)使用java -verbose启动程序,这样程序运行时就会打印加载的类
2)使用strace跟踪,就可以知道哪些线程加载了类
具体的命令为:strace -T -f -q -s 1024 -o strace.txt java -verbose .............(省略号代表具体的程序运行相关参数)
通过查看strace输出的结果,发现如下现象:
1)多个线程都会去加载类
2)同一线程需要用到的类如果还没有加载,则自己会去加载,不会再委托给其它线程加载
所以结论是jvm是通过多线程去加载类