V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
RuzZ
V2EX  ›  Java

[提问]关于主线程创建 N 多的线程,抛出'OOME: unable to create new native thread'error 后, catch 住 error,然后清理线程,此时主线程的表现问题。

  •  1
     
  •   RuzZ · 2019-05-23 18:49:11 +08:00 · 2376 次点击
    这是一个创建于 2017 天前的主题,其中的信息可能已经有所发展或是发生改变。

    运行环境:

    java version "1.8.0_191"
    Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
    Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
    
    macOS 10.13.6
    

    一个朋友问,线程可不可以从 OOM 中恢复,然后给了一篇网上看到的文章,大意是如果一个线程发生了 OOM,是不影响其他线程的正常运行的。

    然后我就想,如果主线程疯狂创建线程,也会抛出 OOM,那么如果我 catch 住异常,然后把创建的线程 stop 掉,是不是可行,于是写了下面一段代码测试。

    JVM 参数:-Xms256m -Xmx256m -Xss1m

    public class TestThread {
    
        public static void main(String[] args) throws InterruptedException {
            List<Thread> list = new ArrayList<>();
            try {
                while (true) {
                    Thread t = new Thread(() -> {
                        System.out.println(Thread.currentThread().getName());
                        try {
                            Thread.sleep(Integer.MAX_VALUE);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    });
                    list.add(t);
                    t.start();
                }
    
            } catch (Throwable e) {
                e.printStackTrace();
                System.out.println("list size =================" + list.size());
                System.out.println("thread active =================" + Thread.activeCount());
                list.forEach(testThread -> {
                    testThread.stop();
                });
                System.out.println("stop thread over");
            }
            Thread.sleep(2000L);
            System.out.println("thread active =================" + Thread.activeCount());
            Thread.currentThread().getThreadGroup().list();
        }
    }
    

    部分日志如下:

    Thread-2024
    Thread-2025
    Thread-2026
    Thread-2027
    java.lang.OutOfMemoryError: unable to create new native thread
    	at java.lang.Thread.start0(Native Method)
    	at java.lang.Thread.start(Thread.java:717)
    	at com.xzy.test.TestThread.main(TestThread.java:25)
    list size =================2029
    thread active =================2030
    stop thread over
    thread active =================2
    java.lang.ThreadGroup[name=main,maxpri=10]
        Thread[main,5,main]
        Thread[Monitor Ctrl-Break,5,main]
    
    Process finished with exit code 0
    
    

    可以看到,在 catch 住异常后,主线程正常执行了剩下的内容,正常退出了(exit code 0)。然后我就想,如果这里的是线程池呢?这个时候测试的出来的结果就有点不理解了。

    JVM 参数依然是:-Xms256m -Xmx256m -Xss1m

    public class TestThreadPool {
    
        public static void main(String[] args) throws InterruptedException {
            List<ExecutorService> list = new ArrayList<>();
            try {
                while (true) {
                    ExecutorService executorService = Executors.newFixedThreadPool(10);
                    int i = 0;
                    while (i++ <= 10) {
                        executorService.submit(() -> {System.out.println(Thread.currentThread().getName());});
                    }
                    list.add(executorService);
                }
            } catch (Throwable e) {
                System.out.println("catch throwable");
                e.printStackTrace();
                System.out.println("============" + Thread.currentThread().getName() + " collect thread start!");
                System.out.println("list size ============" + list.size());
                list.forEach(ExecutorService::shutdownNow);
                System.out.println("============" + Thread.currentThread().getName() + " shutdown success!");
            }
            list.forEach(executorService -> {
                if (!executorService.isShutdown()) {
                    System.out.println("============ alive executorservice");
                }
            });
            Thread.sleep(2000L);
            System.out.println("thread active count ============" + Thread.activeCount());
            Thread.currentThread().getThreadGroup().list();
        }
    }
    

    部分日志输出如下:

    pool-203-thread-4
    pool-203-thread-5
    pool-203-thread-6
    pool-203-thread-7
    catch throwable
    java.lang.OutOfMemoryError: unable to create new native thread
    	at java.lang.Thread.start0(Native Method)
    	at java.lang.Thread.start(Thread.java:717)
    	at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:957)
    	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1367)
    	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
    	at com.xzy.test.TestThreadPool.main(TestThreadPool.java:20)
    ============main collect thread start!
    list size ============202
    ============main shutdown success!
    thread active count ============9
    java.lang.ThreadGroup[name=main,maxpri=10]
        Thread[main,5,main]
        Thread[Monitor Ctrl-Break,5,main]
        Thread[pool-203-thread-1,5,main]
        Thread[pool-203-thread-2,5,main]
        Thread[pool-203-thread-3,5,main]
        Thread[pool-203-thread-4,5,main]
        Thread[pool-203-thread-5,5,main]
        Thread[pool-203-thread-6,5,main]
        Thread[pool-203-thread-7,5,main]
    

    主线程没有退出,没理解错的话,因为pool-203线程池还在运行,但是为什么pool-203这个线程池会卡住呢,还是 catch 住异常之前的样子(都是新建了 7 个线程),我用 VisualVM 看了下线程的状态,发现pool-203-thread-这一批线程因为调用了java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#await()这个方法,都处于 waiting 状态。 visualVM 截图 看了眼 ThreadPoolExecutor,应该是java.util.concurrent.ThreadPoolExecutor#awaitTermination这里调用的,这个方法是有 shutdwon 请求时,阻塞等待所有任务完成执行。

    不知道我前面的分析有没有哪个地方有错。如果前面分析的没错的话,我的疑问是,为什么会卡在java.util.concurrent.ThreadPoolExecutor#awaitTermination这里呢,是因为创建线程过多发生了 oom,但是最后一组线程池没有正确创建完毕,导致主线程恢复过来后,无法彻底回收最后那组线程池吗?

    RuzZ
        1
    RuzZ  
    OP
       2019-05-23 18:50:03 +08:00
    不好意思,内容有点长,主要是自己也不知道怎么表达这个问题
    mononite
        2
    mononite  
       2019-05-23 23:09:00 +08:00
    list.add(executorService)是在 submit 后加的,最后抛异常的那个线程池就没有被加到 list 里,所以这个线程池没有被 shutdown。
    线程池默认创建的线程不是 Daemon 线程,如果 jvm 里没有退出的 Daemon 线程,jvm 是不会退出的。
    q253382683
        3
    q253382683  
       2019-05-24 09:00:38 +08:00
    gtmdryy
    vincentguc
        4
    vincentguc  
       2019-05-24 09:04:40 +08:00
    gtmdryy
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2783 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 14:50 · PVG 22:50 · LAX 06:50 · JFK 09:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.