老年代调优
按照同样的思路对老年代进行调优,同样分析FullGC 频率和停顿时间,按照优化设定的目标进行老年代大小调整。
老年代调优流程
- 分析每次MinorGC 之后老年代空间占用变化,计算每次MinorGC之后晋升到老年代的对象大小
- 按照MinorGC频率和晋升老年代对象大小计算提升率即每秒钟能有多少对象晋升到老年代
- FullGC之后统计老年代空间被占用大小计算老年带空闲空间,再按照第2部计算的晋升率计算该老年代空闲空间多久会被填满而再次发生FullGC,同样观察FullGC 日志信息,计算FullGC频率,如果频率过高则可以增大老年代空间大小老解决,增大老年代空间大小应保持年轻代空间大小不变
- 如果在FullGC 频率满足优化目标而停顿时间比较长的情况下可以考虑使用CMS、G1收集器。并发收集减少停顿时间
栈溢出
栈溢出异常的排查(包括虚拟机栈、本地方法栈)基本和OOM的一场排查是一样的,导出异常的堆栈信息,然后使用mat或者Visual VM工具进行线下分析,找到出现异常的代码或者方法。
当线程请求的栈深度大于虚拟机栈所允许的大小时,就会出现StackOverflowError异常,二从代码的角度来看,导致线程请求的深度过大的原因可能有:方法栈中对象过大,或者过多,方法过长从而导致局部变量表过大,超过了-Xss参数的设置。
死锁排查
死锁的案例演示的代码如下:
public class DeadLock {
public static Object lock1 = new Object();
public static Object lock2 = new Object();
public static void main(String[] args){
Thread a = new Thread(new Lock1(),"DeadLock1");
Thread b = new Thread(new Lock2(),"DeadLock2");
a.start();
b.start();
}
}
class Lock1 implements Runnable{
@Override
public void run(){
try{
while(true){
synchronized(DeadLock.lock1){
System.out.println("Waiting for lock2");
Thread.sleep(3000);
synchronized(DeadLock.lock2){
System.out.println("Lock1 acquired lock1 and lock2 ");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
class Lock2 implements Runnable{
@Override
public void run(){
try{
while(true){
synchronized(DeadLock.lock2){
System.out.println("Waiting for lock1");
Thread.sleep(3000);
synchronized(DeadLock.lock1){
System.out.println("Lock2 acquired lock1 and lock2");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
上面的代码非常的简单,就是两个类的实例作为锁资源,然后分别开启两个线程,不同顺序的对锁资源资源进行加锁,并且获取一个锁资源后,等待三秒,是为了让另一个线程有足够的时间获取另一个锁对象。
运行上面的代码后,就会陷入死锁的僵局:

对于死锁的排查,若是在测试环境或者本地,直接就可以使用Visual VM连接到该进程,如下界面就会自动检测到死锁的存在

并且查看线程的堆栈信息。就能看到具体的死锁的线程:

线上的话可以上用Arthas也可以使用原始的命令进行排查,原始命令可以先使用jps查看具体的Java进程的ID,然后再通过jstack ID查看进程的线程堆栈信息,他也会自动给你提示有死锁的存在:

Arthas工具可以使用thread命令排查死锁,要关注的是BLOCKED状态的线程,如下图所示:

具体thread的详细参数可以参考如下图所示:

如何避免死锁
上面我们聊了如何排查死锁,下面我们来聊一聊如何避免死锁的发生,从上面的案例中可以发现,死锁的发生两个线程同时都持有对方不释放的资源进入僵局。所以,在代码层面,要避免死锁的发生,主要可以从下面的四个方面进行入手:
-
首先避免线程的对于资源的加锁顺序要保持一致
-
并且要避免同一个线程对多个资源进行资源的争抢
-
另外的话,对于已经获取到的锁资源,尽量设置失效时间,避免异常,没有释放锁资源,可以使用acquire() 方法加锁时可指定 timeout 参数
-
最后,就是使用第三方的工具检测死锁,预防线上死锁的发生
死锁的排查已经说完了,上面的基本就是问题的排查,也可以算是调优的一部分吧,但是对于JVM调优来说,重头戏应该是在Java堆,这部分的调优才是重中之重。
















