刨根问底---一次OOM试验造成的电脑雪崩引发的思考

问题初现----电脑雪崩

在写「垃圾回收-实战篇」时,按书中的一个例子做了一次实验,我觉得涉及的知识点挺多的,所以单独拎出来与大家共享一下,相信大家看完肯定有收获。

画外音:尽信书不如无书,对每一个例子我们最好亲自试试,说不定有新的发现

实验是这样的:想测试在指定的栈大小(160k)下通过不断创建多线程观察其造成的 OOM 类型

画外音:造成 OOM 的原因有很多,将在本周的 「垃圾回收-实战篇」一文中做详细描述,这里不再赘述

实验的代码如下:

public class Test {
    private void dontStop() {
        while(true) {
        }
    }

    public void stackLeakByThread() {
        while (true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static  void main(String[] args) {
        Test oom = new Test();
        oom.stackLeakByThread();
    }
}

过了一会儿风扇狂转,不久就发生了 OOM,然后程序没有终止,用 Ctrl + C 也无法终止,会提示「the VM may need to be forcibly terminated」,这是什么鬼,如图示


电脑卡死了,鼠标键盘完全没法响应!
只好重启了电脑,然后我先在终端输入 top 命令,再执行以上的程序, 发现 CPU的负载达到了 800%!


在以上对问题的描述中至少有三个问题值得我们去思考

  1. 以上 while (true) 为啥会造成 cpu 负载 800%
  2. 在主线程发生 OOM 后我在终端用 Ctrl + C 试图终止 Java 进程的执行,但没成功,为啥中止信号不生效呢
  3. 主线程发生 OOM 后 Java 进程为啥不会停止运行

一个个来看

while (true) 与 cpu 负载的关系

首先我们要明白 **%CPU ** 代表的含义,它指的是进程占用一个核的百分比,如果进程启动了多个线程,多线程就会占用多个核,是可能超过 100% 的,但最多不超过 CPU核数 * 100%, 怎么查看逻辑 CPU 的个数

  • Linux 下可以用
cat /proc/cpuinfo| grep "processor"| wc -l
  • Mac 可以用
sysctl hw.logicalcpu

我的电脑是 Mac 的,用以上命令查了一下逻辑核心发现是 8 个, 而实验看到的 CPU 占有率是 800%,也就是说我们的实验程序打满了 8 个逻辑 CPU!有人说那是因为你在源源不断地创建线程啊,当然就打满了逻辑 CPU 了,那我们再来试验一下,只创建 7 个线程,加个主线程共 8 个,这 8 个主线程内部都只执行一个 while(true) {} ,如下

public class Test {
        private int threadCount = 0;
    private void dontStop() {
        while(true) {
        }
    }

    public void stackLeakByThread() {
        while (true) {
                        // 只创建 7 个线程, 加上主线程共 8 个线程
            if (threadCount > 7) {
                continue;
            }
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static  void main(String[] args) {
        Test oom = new Test();
        oom.stackLeakByThread();
    }
}

执行之后 %CPU 还是接近 800%(大家可以试验一下,这里不贴图了), 也就是说 8 个 while(true) 把 8 个核全部打满了,平均一个 while(true) 打满一个核 ,那么问题来了, 单个线程执行 while(true) 为啥会打满一个核呢,CPU 不是按时间片来分配各个进程的吗


如图示:操作系统按时间片的调度算法来给不同的进程分配 CPU 时间,如果某个进程时间片用完了,会让出 CPU 的控制权给其他的进程执行

首先,需要指明的是:CPU 确实是按时间片来给不同的进程分配它的控制权的

但 CPU 对时间片的分配策略是动态的, 具有偏向性的,简单理解如下:
Java 中的线程执行完系统分配的时间片后确实是会让出 CPU 的执行权,但别的进程会告诉系统自己没什么事情要做,不需要那么多的时间,这个时候系统就会切换到下一个进程,直到回到这个死循环的进程上,而 Java 进程无论什么时候都再循环,都会一直会报告有事情要做,系统就会把尽可能多的时间分给它(正所谓会哭的小孩有奶吃),系统会不断调高 while(true) 线程的优先级,提升它的 CPU 占用时间片,也就是说 while(true) 这个死循环用光了别的进程省下的时间,不让 CPU 有片刻休息的时间,导致 CPU 负载过高,这就像马太效应,勤奋的线程执行的越努力,其他懒惰的线程就越会被缩短时间片,越得不到机会!
画外音: Windows 系统中就存在一个称为「优先级推进器」(Priority Boosting,可以关闭)的功能,大致作用就是当系统发现一个线程执行得特别勤奋努力的话,可能会越过线程优先级优先为此线程分配执行时间

发生 OOM 后 Ctrl+C 为啥无法中止 Java 进程

上文提到,发生 OOM 后, 由于已经观察到 OOM 的现象,所以想把 Java 进程通过 Ctrl+C 杀死,但发现不起作用,如图示


为啥 Ctrl + C 这种通用的 kill 掉进程的方式不起作用呢,我在 Oracle 的论坛(见文末参考链接)找到了 Oracle 工程师的回答

The message "Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal UNKNOWN to handler- the VM may need to be forcibly terminated" is getting printed by the JVM's native signal handling code. The signal handler itself encountered OOM while making a Java up-call and that's why the JVM didn't get terminated with ctrl+c.

简单地说就是 JVM 中的信号处理器确实收到了终端发出的 Ctrl + C 的终止信号,但当它调用 Java 进程想中止时发生了 OOM 导致中断失败, 那为啥调用会发生 OOM 呢,我猜是因为信号处理器要启动一个线程来做这种终止通知的操作,而我们知道,当前已经无法再创建线程了(已经发生 unable to create new native thread 的错误了)

主线程发生 OOM 后 Java 进程为啥不会停止运行

最后一个问题,主线程发生 OOM 后居然 Java 进程没终止,这个该怎么解释

Main 主线程与其他的子线程并不是父子关系,而是平等的关系,所以主线程虽然因为 OOM 挂了,但其他子线程并不会停止运行,由于它们执行的 while(true),所以子线程会一直存在,既然它们一直存在,那对应的 Java 进程就会一直运行着。

那怎么让主线程终止运行后,其他线程也可立即结束呢,可以把这些子线程设置为守护线程,创建好 Thread thread 后,可以用 thread.setDaemon(true) 将其设置成守护线程,这样当主线程挂了,守护线程也会立即停止运行,原因嘛,也很简单,既然是守护线程,那被守护的线程都挂了,那守护线程也没存在的意义了

总结

本文通过一个 OOM 试验引出了三个值得思考的问题,相信大家应该学了不少知识点,这里还是要提醒一下大家,看到书中的 demo 时,最好能亲自去尝试一下,说不定你能有新的发现!纸上得来终觉浅,绝知此事要躬行!碰到问题最好穷追猛打,这样在每次试验中我们都能有收获!

参考

https://blog.csdn.net/russell_tao/article/details/7103012

https://blog.csdn.net/aitangyong/article/details/16858273

https://zhuanlan.zhihu.com/p/91573757

https://community.oracle.com/thread/4088001

本文由博客一文多发平台 OpenWrite 发布!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • Java SE 基础: 封装、继承、多态 封装: 概念:就是把对象的属性和操作(或服务)结合为一个独立的整体,并尽...
    Jayden_Cao阅读 2,048评论 0 8
  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,596评论 0 6
  • 一、进程和线程 进程 进程就是一个执行中的程序实例,每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程。...
    阿敏其人阅读 2,566评论 0 13
  • Java内存区域 Java虚拟机在运行程序时会把其自动管理的内存划分为以上几个区域,每个区域都有的用途以及创建销毁...
    架构师springboot阅读 1,723评论 0 5
  • 相识相知,如酒如诗。 有缘遇见,珍惜彼此。
    一切美好阅读 99评论 0 2