如何进行JVM调优?
根据业务需求进行JVM规划和预调优
没有最好的垃圾收集器,只有最合适的。所以需要根据自己的业务场景选择适合自己的收集器。
如果你的系统追求“响应时间”那么你完全可以选择PN+CMS,G1垃圾收集器,提升用户体验。
如果追求“吞吐量”可以选择并发类型收集器PS+PO。
吞吐量指:用户代码执行时间/用户代码执行时间+垃圾回收时间 。运用场景:大数据运算,数据挖掘等
响应时间:b/s模式用户的响应时间,又指FGC的STW的停顿时间
1.硬件上的升级,最直接的可以升级CPU,CPU核数越高越好
2.JVM内存设置,提前预估设置年轻代,老年代的大小。当然需要经过大量的测试。
3.开启GC日志,记录每天GC发生情况,当出现问题时好定位。建议划分日志的大小,将一个大的日志文件分成几个小的来存,这样在查找的时间方便。或者按每天生成一个,这种需要考虑自己线上的业务量,如果每天日志几个G也不好查找。
4.分析GC日志,有条件的可以使用Jconsole,Jvisualvm,jhat,jprofiler等可视化监控工具,没有条件的通过DOS命令如jps,jstack,jmap都是非常常用的命令
优化JVM的运行环境
这里举几个列子来说明(非正常生产配置):
1.某中型在线图书网站,原来的服务器32位,内存32G,8核CPU,在高峰时间用户响应缓慢,后升级到64位,内存64G结果更加卡顿了,反而效率比没升级之前还要低。
首先先来分析在没升级前为什么会慢?
高峰时期大量用户涌入,数据从磁盘load到内存,导致内存不足频繁GC,发生STW,导致响应变慢
为什么升级后更加卡了?
升级内存后,虽然内存变大,只是发生STW的时间后延了,但是一旦发生GC,则GC扫描的内存更大,STW更长了
这是你就要看你的JVM用的是哪种收集器了,一般出现这种情况应该是用了并发型的PS+PO,这种内存越大发生
GC时相对的STW就越长,所以你需要换成CMS+PN或者是G1来处理,用户线程和垃圾收集线程并发进行,这样就
提高了响应时间
2.运维人员反应线上服务器CPU经常100%
这里提供一下查找思路:
1.使用top命令找出占用cpu最大的那个进程
2.使用top -Hq查找进程中线程的占用情况
3.使用jstack查看各线程的运行情况,一般查看是否有线程死锁,重点关注线程的 WAITING BLOCKED状态,找出
长期占用的线程
4.使用jmap命令有条件的dump出日志,没有条件的使用 jmap -histo等命令可以查找出对象实例占用情况
然后在分析。
JVM运行过程中产生的各种问题
像比较常见的OOM问题:
1.分配内存大小并打印明细的GC日志,一般将初始内存和最大内存设置为同一个值,可以使用一下命令
java -Xms200M -Xmx200M -XX:+PrintGCDetails
2.使用top指令查看进程
top
pid为进程唯一id,除了pid,%cpu,%mem 分别为占用cpu的大小,内存的大小
3.top -Hp 观察进程中的线程,哪个线程CPU和内存占比高
top -Hp pid
4.jps定位具体java进程,jstack 定位线程状况,重点关注:WAITING BLOCKED
我这里模拟了一段简单死锁代码
public class Tets {
public synchronized void print(){
List list=new ArrayList();
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(new Byte[1024*1024*1]);
System.out.println("死锁了");
}
}
public static void main(String[] args) {
Tets t=new Tets();
for (int i = 0; i < 100; i++) {
new Thread(()->{
t.print();
}).start();
}
}
}
使用jps 查找 进程号
接着使用 jstack 19308 获取进程中的线程状态,我们可以看到有大量的线程BCLOCKED阻塞住了,被的线程号为[0x000000002967f000] 阻塞了,那么我们可以分析一定是[0x000000002967f000]这个线程发生了问题,那我们就去找这个[0x000000002967f000]线程,发现这个线程的状态一直在RUNNABLE,那么说明[0x000000002967f000]线程发生了死锁
4.线程的名称(尤其是线程池)都要写有意义的名称
定义有意义的线程name方便后续问题的定位,比如你使用线程池的话,一般不建议使用juc提供的4中线程池,创建线程池用
ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4,
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(4),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
怎么样自定义线程池里的线程名称?(自定义ThreadFactory)
各个参数在这里不解释是什么意思了,那么关注第6个参数创建ThreadFactory,一般我们需要直接定义ThreadFactory的实现,并重写newThread方法创建线程。
5.使用jmap 查看产生了多小对象
jmap - histo 4655 | head -20
取前20条,这里可以列出具体是哪个对象,当然条件允许也可以dump出文件 (一般线上不建议dump,dump很占资源,可以会造成卡死)
jmap -dump:format=b,file=xxx pid :
当然这里只是列出了查找问题的思路,实际上的线上定位问题远比这个要复杂的多,但是基本上也离不开这几种方法,只要掌握这些常用的指定根据自己遇到的问题应该很快能够定位