JVM手动调优的完整过程(包含cpu飙升、OOM问题定位等详细步骤)一

一、前置知识补充(关于线程池的使用,熟悉的同学可以直接跳过本节)

1、线程池的创建和使用:

阿里最新开发手册关于并发创建线程池时有如下强制规定:

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1) FixedThreadPool和SingleThreadPool: 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2) CachedThreadPool: 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

因此我们需要从根本上避免因代码不规范而产生的OOM!

下面我们介绍如何正确的使用线程池!
使用来创建线程池,其构造函数如下(7个参数最全的那个构造方法):

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize :线程池中核心线程数的最大值;
maximumPoolSize :线程池中能拥有最多线程数;
keepAliveTime :表示空闲线程的存活时间;
unit :表示keepAliveTime的单位;
workQueue :用于缓存任务的阻塞队列;
threadFactory :指定创建线程的工厂;
handler:任务拒绝处理器;

前四个参数过于简单不再做任何解释,下面主要讲解后续三个参数!

workQueue :BlockingQueue是一个阻塞队列接口,其主要有以下三种队列实现:

1)SynchronousQueue
SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。(A任务进入队列,B任务必须等A任务被移除之后才能进入队列,否则执行异常策略。你来一个我扔一个,所以说SynchronousQueue没有任何内部容量。)

2)LinkedBlockingQueue
LinkedBlockingQueue是一个无界缓存等待队列。当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。(所以在使用此阻塞队列时maximumPoolSizes就相当于无效了),每个线程完全独立于其他线程。生产者和消费者使用独立的锁来控制数据的同步,即在高并发的情况下可以并行操作队列中的数据。

注:这个队列需要注意的是,虽然通常称其为一个无界队列,但是可以人为指定队列大小,而且由于其用于记录队列大小的参数是int类型字段,所以通常意义上的无界其实就是队列长度为 Integer.MAX_VALUE,且在不指定队列大小的情况下也会默认队列大小为 Integer.MAX_VALUE;

3)ArrayBlockingQueue
ArrayBlockingQueue是一个有界缓存等待队列,可以指定缓存队列的大小(必须指定大小),当正在执行的线程数等于corePoolSize时,多余的元素缓存在ArrayBlockingQueue队列中等待有空闲的线程时继续执行,当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSizes时,再有新的元素尝试加入ArrayBlockingQueue时会报错。

区别:
LinkedBlockingQueue:可以指定大小,也可以不指定;底层是链表结构;
ArrayBlockingQueue:必须指定大小;底层是数组结构;

threadFactory:自定义线程池名称、线程名称!(方便根据名称直接定位问题!)后面代码直接演示!

handler:ThreadPoolExecutor的拒绝策略,其主要包括以下默认的4种,当然也可以自定义!

1)AbortPolicy
ThreadPoolExecutor中默认的拒绝策略就是AbortPolicy。大于最大线程数时直接抛出异常。

2)CallerRunsPolicy
CallerRunsPolicy在任务被拒绝添加后,会调用当前线程池的所在的线程去执行被拒绝的任务(即调用线程池的线程,例如main线程)。

3)DiscardPolicy
采用这个拒绝策略,会让被线程池拒绝的任务直接抛弃,不会抛异常也不会执行。

4)DiscardOldestPolicy
DiscardOldestPolicy策略的作用是,当任务呗拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。

线程池的执行过程大致如下:

线程池按以下行为执行任务
1. 当线程数小于核心线程数时,创建线程。
2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
3. 当线程数大于等于核心线程数,且任务队列已满
- 若线程数小于最大线程数,创建线程
- 若线程数等于最大线程数,执行任务拒绝处理器(抛出异常,拒绝任务)

2、实例演示:

自定义线程池:

package com.taosunpool;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 自定义ThreadFactory
 */
public class UserThreadFactory implements ThreadFactory {

    //线程组名称
    private final String namePrefix;

    //线程自增序号
    private final AtomicInteger nextId = new AtomicInteger(1);

    // 定义线程组名称,在 jstack 问题排查时,非常有帮助
    UserThreadFactory(String whatFeaturOfGroup) {
    namePrefix = "From UserThreadFactory's " + whatFeaturOfGroup + "-Worker-"; }

    @Override
    public Thread newThread(Runnable task) {
        String name = namePrefix + nextId.getAndIncrement();
        Thread thread = new Thread(null, task, name, 0);
        return thread;
    }
}

Task任务类:

package com.taosunpool;

public class DemoTask implements Runnable {
    private String name;
    DemoTask(String name){
        this.name = name;
    }
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println("线程名称:"+ Thread.currentThread().getName()+"; "+this.getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

测试延迟队列(使用默认的拒绝策略AbortPolicy):

1)SynchronousQueue

package com.taosunpool;

import java.util.Iterator;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DemoThreadPool {
    public static void main(String[] args) {
        //创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(1,
                        2,
                        3L,
                        TimeUnit.SECONDS,
                        new SynchronousQueue<>(),
                        new UserThreadFactory("DemoTestThreadPool"),
                        new ThreadPoolExecutor.AbortPolicy());
        int i = 0;
        for (;;){
            ++i;
            System.out.println("添加: task"+i);
            executor.execute(new DemoTask("task"+i));
            Iterator iterator = executor.getQueue().iterator();
            while (iterator.hasNext()){

                System.out.println("列表:"+((DemoTask)iterator.next()).getName());
            }
        }

    }
}

结果如下:
在这里插入图片描述
结果分析:
(1)添加: task1时,此时线程池会创建一个From UserThreadFactory’s DemoTestThreadPool-Worker-1线程执行task1;
(2)添加: task2时,由于此时task1还在执行中,且corePoolSize=1,所以会将task2任务“放入“SynchronousQueue队列中,因为此队列不具备存储功能(最大容量为1),所以会创建线程From UserThreadFactory’s DemoTestThreadPool-Worker-2执行task2;
(3)添加: task3时,此时maximumPoolSize=2,已无法在创建新的线程去执行任务,拒绝策略AbortPolicy直接抛出异常阻塞程序!

注:task1、task2执行任务是异步的!

2)LinkedBlockingQueue

a.不设置队列大小

package com.taosunpool;

import java.util.Iterator;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DemoThreadPool {
    public static void main(String[] args) {
        //创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(1,
                        2,
                        3L,
                        TimeUnit.SECONDS,
                        new LinkedBlockingDeque<>(),
                        new UserThreadFactory("DemoTestThreadPool"),
                        new ThreadPoolExecutor.AbortPolicy());
        int i = 0;
        for (;;){
            ++i;
            System.out.println("添加: task"+i);
            executor.execute(new DemoTask("task"+i));
            Iterator iterator = executor.getQueue().iterator();
            while (iterator.hasNext()){

                System.out.println("列表:"+((DemoTask)iterator.next()).getName());
            }
        }

    }
}

结如下:
在这里插入图片描述
结果分析:
(此时程序已被终止!)
可以看到程序在不断的向队列中添加新的任务,这是非常危险的!最终的结果将会导致OOM的发生!

b.设置固定的队列大小为5

package com.taosunpool;

import java.util.Iterator;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DemoThreadPool {
    public static void main(String[] args) {
        //创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(1,
                        2,
                        3L,
                        TimeUnit.SECONDS,
                        new LinkedBlockingDeque<>(5),
                        new UserThreadFactory("DemoTestThreadPool"),
                        new ThreadPoolExecutor.AbortPolicy());
        int i = 0;
        for (;;){
            ++i;
            System.out.println("添加: task"+i);
            executor.execute(new DemoTask("task"+i));
            Iterator iterator = executor.getQueue().iterator();
            while (iterator.hasNext()){

                System.out.println("列表:"+((DemoTask)iterator.next()).getName());
            }
        }

    }
}

结果如下:
在这里插入图片描述
结果分析:
(1)添加: task1,线程池创建From UserThreadFactory’s DemoTestThreadPool-Worker-1线程执行task1;
(2)添加: task2,此时corePoolSize=1,由于核心线程数已满,因此会将task2放入队列中,因此队列输出:列表:task2;
(3)添加: task3,由于核心线程数已满,同样会将其放入队列中,因此输出:列表:task2 列表:task3 ;

(4)添加: task7,由于此时队列已满(task2,task3,task4,task5,task6),因此会创建新的线程From UserThreadFactory’s DemoTestThreadPool-Worker-2来执行task7;
(5)添加: task8,由于此时最大线程数,和队列都满载了,所以策略模式直接抛出异常不再接受新的task,但线程池中已有线程会将队列中的任务执行完成。

3)ArrayBlockingQueue(有界队列)

package com.taosunpool;

import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DemoThreadPool {
    public static void main(String[] args) {
        //创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(1,
                        2,
                        3L,
                        TimeUnit.SECONDS,
                        new ArrayBlockingQueue<>(5),
                        new UserThreadFactory("DemoTestThreadPool"),
                        new ThreadPoolExecutor.AbortPolicy());
        int i = 0;
        for (;;){
            ++i;
            System.out.println("添加: task"+i);
            executor.execute(new DemoTask("task"+i));
            Iterator iterator = executor.getQueue().iterator();
            while (iterator.hasNext()){

                System.out.println("列表:"+((DemoTask)iterator.next()).getName());
            }
        }

    }
}

结果如下:
在这里插入图片描述
结果分析:
结果分析同上面一样,再次就不重复!

测试拒绝策略:

1)AbortPolicy
上面队列已经演示!

2)CallerRunsPolicy

package com.taosunpool;

import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DemoThreadPool {
    public static void main(String[] args) {
        //创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(1,
                        2,
                        3L,
                        TimeUnit.SECONDS,
                        new ArrayBlockingQueue<>(3),
                        new UserThreadFactory("DemoTestThreadPool"),
                        new ThreadPoolExecutor.CallerRunsPolicy());
        int i = 0;
        for (;;){
            ++i;
            System.out.println("添加: task"+i);
            executor.execute(new DemoTask("task"+i));
            Iterator iterator = executor.getQueue().iterator();
            while (iterator.hasNext()){

                System.out.println("列表:"+((DemoTask)iterator.next()).getName());
            }
        }

    }
}

结果如下:
在这里插入图片描述
结果分析:
(1)添加: task1,线程池创建From UserThreadFactory’s DemoTestThreadPool-Worker-1线程执行task1;
(2)添加: task2,此时corePoolSize=1,由于核心线程数已满,因此会将task2放入队列中,因此队列输出:列表:task2;
(3)添加: task3,由于核心线程数已满,同样会将其放入队列中,因此输出:列表:task2 列表:task3 ;

(4)添加: task5,由于此时队列已满,因此创建新的线程From UserThreadFactory’s DemoTestThreadPool-Worker-2执行task5,输出:列表:task2 列表:task3 列表:task4 ;
(5)添加: task6,此时最大线程数、队列数都已经满载,因此策略模式触发,“线程名称:main; task6“,即主线程main执行了task6,输出: 列表:task3 列表:task4 ;
注:看下面的打印结果
线程名称:From UserThreadFactory’s DemoTestThreadPool-Worker-1; task2,说明此时执行task1的线程已经完成任务,并从队列中取出task2执行;

后续结果就是一个反复执行的过程!此策略模式就是在线程、队列满载时会调用启动线程池的主线程来执行任务,这个策略的缺点就是可能会阻塞主线程。任务不会被抛弃但会阻塞主线程!

3)DiscardPolicy

package com.taosunpool;

import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DemoThreadPool {
    public static void main(String[] args) {
        //创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(1,
                        2,
                        3L,
                        TimeUnit.SECONDS,
                        new ArrayBlockingQueue<>(3),
                        new UserThreadFactory("DemoTestThreadPool"),
                        new ThreadPoolExecutor.DiscardPolicy());
        int i = 0;
        for (;;){
            ++i;
            System.out.println("添加: task"+i);
            executor.execute(new DemoTask("task"+i));
            Iterator iterator = executor.getQueue().iterator();
            while (iterator.hasNext()){

                System.out.println("列表:"+((DemoTask)iterator.next()).getName());
            }
        }

    }
}

结果如下:
在这里插入图片描述
结果分析:
(1)添加: task1,线程池创建From UserThreadFactory’s DemoTestThreadPool-Worker-1线程执行task1;
(2)添加: task2,task3,task4时都会被放进队列中,由于程序执行太快此过程无法捕获到哈哈!
(3)注意看图中:
在这里插入图片描述
task5任务被From UserThreadFactory’s DemoTestThreadPool-Worker-2线程执行时,此时队列中的任务是:task2,task3,task4,此时的执行流程和上面分析的过程都一致!即核心线程–》队列—》最大线程 这个过程!
但注意看此时的任务添加数“添加: task64201“,任务已经添加到了64201个,在这个过程中,task1、task5正在被执行,task2,task3,task4在队列中,其他的任务已经被策略模式全部丢弃!

4)DiscardOldestPolicy

package com.taosunpool;

import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DemoThreadPool {
    public static void main(String[] args) {
        //创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(1,
                        2,
                        3L,
                        TimeUnit.SECONDS,
                        new ArrayBlockingQueue<>(3),
                        new UserThreadFactory("DemoTestThreadPool"),
                        new ThreadPoolExecutor.DiscardOldestPolicy());
        int i = 0;
        for (int j=0;j<10;j++){
            ++i;
            System.out.println("添加: task"+i);
            executor.execute(new DemoTask("task"+i));
            Iterator iterator = executor.getQueue().iterator();
            while (iterator.hasNext()){

                System.out.println("列表:"+((DemoTask)iterator.next()).getName());
            }
        }

    }
}

结果如下:
在这里插入图片描述
结果分析:
(1)由于前期执行都一样,所以直接从队列满载分析
(2)添加: task5时,线程池会创建From UserThreadFactory’s DemoTestThreadPool-Worker-2来执行task5,此时队列中存储信息为:
列表:task2,列表:task3,列表:task4;
(3)添加: task6,注意看此时队列的输出结果:列表:task3,列表:task4,列表:task6;
(4)添加: task7,注意看此时队列的输出结果:列表:task4,列表:task6,列表:task7;
(5)添加: task8,注意看此时队列的输出结果:列表:task6,列表:task7,列表:task8
(6)添加: task9,注意看此时队列的输出结果:列表:task7,列表:task8,列表:task9;
(7)添加: task10,注意看此时队列的输出结果:列表:task8,列表:task9,列表:task10;

由以上结果可以看出,每次添加新任务时,都会将队列中最小的任务,即最老的任务,移除!然后添加新的任务进入队列中!

由,
线程名称:From UserThreadFactory’s DemoTestThreadPool-Worker-1; task1
线程名称:From UserThreadFactory’s DemoTestThreadPool-Worker-2; task5
线程名称:From UserThreadFactory’s DemoTestThreadPool-Worker-1; task8
线程名称:From UserThreadFactory’s DemoTestThreadPool-Worker-2; task9
线程名称:From UserThreadFactory’s DemoTestThreadPool-Worker-1; task10
这些打印结果得出是被移除了,并未被执行!

总结:

(1)我们可以自定义线程组、线程名称(方便定位问题);

(2)线程池队列形式有三种:
SynchronousQueue:只能“暂存”一个数据,且存入数据后必须立刻要有对应的线程来消费,否则会出错,因此不能设置最大线程数,必须无界;
LinkedBlockingQueue:无界队列(不指定大小时),也可指定大小变成有界队列;
ArrayBlockingQueue:有界队列,必须指定大小;

(3)4种策略模式:
AbortPolicy:线程、队列满载后,再有新任务进入直接抛异常

CallerRunsPolicy:线程、队列满载后,再有新任务进入会调用主线程来执行任务,后续再有任务进来,若没有空余线程则会进入阻塞状态!这个策略的缺点就是可能会阻塞主线程

DiscardPolicy:线程、队列满载后,再有新任务进入则直接抛弃这些任务!

DiscardOldestPolicy:线程、队列满载后,再有新任务进入则移除满载队列中最老的任务,将新任务插入!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值