线程池OOM异常

现象

MAC
现象
unable to creat new native thread
window
测试前 在启动测试类之前先将JVM内存调整小一点,不然很容易将电脑跑出问题
在idea里:Run -> Edit Configurations VM options修改成-Xms10M -Xmx10M
(-Xms10M Java Heap内存初始化值 -Xmx10M Java Heap内存最大值)
window执行情况

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space。
代码

public static void main(String[] args) throws InterruptedException {
    
    
        AtomicInteger i = new AtomicInteger(0);
        for (; ; ) {
    
    
            Executors.newFixedThreadPool(10).submit(() -> System.out.println(i.getAndIncrement()));
            Thread.sleep(4);
        }
    }

循环创建线程池 而并没有关闭线程池,导致线程池的实例会一直存在

源码

public static ExecutorService newFixedThreadPool(int nThreads) {
    
    
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}
public LinkedBlockingQueue() {
    
    
        this(Integer.MAX_VALUE); //  MAX_VALUE = 0x7fffffff
}

   public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
    
    
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

原因分析

Java常见的GC Root
Java 进行GC的时候会从GC root进行可达性判断,常见的GC Root有如下:

  1. 通过System Class Loader或者Boot Class Loader加载的class对象,通过自定义类加载器加载的class不一定是GC Root
  2. 处于激活状态的线程
  3. 栈中的对象
  4. JNI栈中的对象
  5. JNI中的全局对象
  6. 正在被用于同步的各种锁对象
  7. JVM自身持有的对象,比如系统类加载器等。
    在调查内存泄漏原因的时候可以根据GC Root来推导

当执行一个Runnable时,会先创建一个ThreadPoolExecutor中的内部类Worker对象,将这个Runnable对象作为Worker对象的一个成员变量

 Worker(Runnable firstTask) {
    
    
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
 private volatile ThreadFactory threadFactory;       
 public ThreadFactory getThreadFactory() {
    
    
        return threadFactory;
    }       

所以 当线程在执行的时候引用关系如下ThreadPoolExecutor->Worker->thread
一个运行的线程是作为GC ROOT的,不会被GC; 所以要主动shutdown

newSingleThreadExecutor不会有此问题,因为FinalizableDelegatedExecutorService 重写了finalize函数,也就是说这个类会在被GC回收之前,先执行线程池的shutdown方法。

java.util.concurrent.Executors

public static ExecutorService newSingleThreadExecutor() {
    
    
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

static class FinalizableDelegatedExecutorService
        extends DelegatedExecutorService {
    
    
        FinalizableDelegatedExecutorService(ExecutorService executor) {
    
    
            super(executor);
        }
        protected void finalize() {
    
    
            super.shutdown();
        }
    }

线程池的使用注意事项

ThreadPoolExecutor-线程池如何保证线程不被销毁

猜你喜欢

转载自blog.csdn.net/eluanshi12/article/details/109240465