前几天,发现一台阿里云服务器上的Web服务不可用。远程SSH登录不上,尝试几次登录上去之后,执行命令都显示

-bash: fork: Cannot allocate memory

一看以为是内存泄漏导致溢出。因为执行不了任何命令, 只能通过控制台重启服务器恢复服务。

初步排查

服务恢复后,查看系统日志,linux系统日志路径/var/log/messages,可通过journalctl命令查看,如

journalctl --since="2019-06-12 06:00:00" --until="2019-06-12 10:00:00"`

可查看since之后,until之前时间段的日志。除了发现crond[14954]: (CRON) CAN'T FORK (do_command): Cannot allocate memory 这个错误日志,未见其它异常(下面的sshd[10764]: error: fork: Cannot allocate memory应是ssh登录执行命名失败的日志)

通过阿里云-云监控-主机监控查看内存使用率指标,这段时间内,内存使用率一直在40%以下,基本可排除内存溢出的可能。

通过搜索查阅到进程数超过操作系统限制可能导致bash: fork: Cannot allocate memory的报错(参考: https://blog.csdn.net/wangshuminjava/article/details/80603847 )。通过ps -eLf|wc -l查看当前进程线程数(ps -ef只打印进程,ps -eLf会打印所有的线程), 只有1000多个,故障时刻系统到底运行了多少线程已无从得知,只能持续跟进监测。

问题定位

几天后,再次通过ps -eLf|wc -l查看,发现线程数已达16000多个。直接执行ps -eLf可看到大量tomcat进程所产生的线程,猜测是不是线程死锁导致大量线程未完成一直hung在那里。

执行 jstack 进程号 > ~/jstack.txt 命令将进程所运行线程情况打印出来分析,发现大量的WAITING状态的线程,如下

"pool-19-thread-1" #254 prio=5 os_prio=0 tid=0x00007f0b700a6000 nid=0x29a9 waiting on condition [0x00007f0b274df000] java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for <0x00000006ce3d8790> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)at java.lang.Thread.run(Thread.java:748)

根据上述内容可看出线程在等一个条件,并且是在执行LinkedBlockingQueue.take方法的时候,查看该方法的java doc,当队列为空时,该方法将会一直等待直到有元素可用。

/** * Retrieves and removes the head of this queue, waiting if necessary * until an element becomes available. * * @return the head of this queue * @throws InterruptedException if interrupted while waiting */E take() throws InterruptedException;

询问同事在哪里用到了LinkedBlockingQueue,同事回忆起不久前用线程池实现往阿里云OSS服务通过追加的方式上传文件功能,查看代码后发现问题——线程池没有关闭。为了使文件片段保存不存在错乱,每次保存文件时,都new了一个线程池对象,

ThreadPoolExecutor saveImgThreadPool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

但处理完后, 没有关闭这个线程池对象,这样线程池仍会通过take方法去取等待队列中是否还有未完成的线程任务,等待队列为空时将会一直等待,这样就导致大量的线程hung在这里了(基本是只要方法被调一次,就会产生一个hung住的线程),时间一长就达到系统所允许的最大限制(默认32768个),不能处理新任务,从而导致系统服务不可用。

延伸

  1. 线程状态为“waiting for monitor entry”:
  2. 意味着它 在等待进入一个临界区 ,所以它在”Entry Set“队列中等待。此时线程状态一般都是 Blocked:
  3. java.lang.Thread.State: BLOCKED (on object monitor)
  4. 线程状态为“waiting on condition”:
  5. 说明它在等待另一个条件的发生,来把自己唤醒,或者干脆它是调用了 sleep(N)。此时线程状态大致为以下几种:
  6. java.lang.Thread.State: WAITING (parking):一直等那个条件发生(本文案例即为此种场景);java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定时的,那个条件不到来,也将定时唤醒自己。
  7. 如果大量线程在“waiting for monitor entry”:可能是一个全局锁阻塞住了大量线程。如果短时间内打印的thread dump 文件反映,随着时间流逝,waiting for monitor entry 的线程越来越多,没有减少的趋势,可能意味着某些线程在临界区里呆的时间太长了,以至于越来越多新线程迟迟无法进入临界区。
  8. 如果大量线程在“waiting on condition”:可能是它们又跑去获取第三方资源,尤其是第三方网络资源,迟迟获取不到Response,导致大量线程进入等待状态。所以如果你发现有大量的线程都处在 Wait on condition,从线程堆栈看,正等待网络读写,这可能是一个网络瓶颈的征兆,因为网络阻塞导致线程无法执行。也可能是如本文所提到的,由于程序编写不当所致。

参考: https://www.cnblogs.com/rainy-shurun/p/5732341.html

我的个人博客地址:http://blog.jboost.cn

我的头条空间: https://www.toutiao.com/c/user/5833678517/#mid=1636101215791112

我的github地址:https://github.com/ronwxy

我的微信公众号:jboost-ksxy

——————————————————————

欢迎关注我的微信公众号,及时获取最新分享

linux 使用jstack_案例解析:线程池使用不当导致的系统崩溃相关推荐

  1. 从源码角度解析线程池中顶层接口和抽象类

    摘要:我们就来看看线程池中那些非常重要的接口和抽象类,深度分析下线程池中是如何将抽象这一思想运用的淋漓尽致的. 本文分享自华为云社区<[高并发]深度解析线程池中那些重要的顶层接口和抽象类> ...

  2. 一次排查Java线程数异常--线程池使用不当造成线程数升高

    一次排查Java线程数异常--线程池使用不当造成线程数升高 参考文章: (1)一次排查Java线程数异常--线程池使用不当造成线程数升高 (2)https://www.cnblogs.com/etha ...

  3. Linux多线程实践(9) --简单线程池的设计与实现

    线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收.所以 ...

  4. 深入解析线程池的使用

    为什么需要线程池 目前的大多数网络服务器,包括Web服务器.Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短.  传 统多线程方案中我 ...

  5. hibernate 并发获取session失败 空指针_高并发之|通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程...

    核心逻辑概述 ThreadPoolExecutor是Java线程池中最核心的类之一,它能够保证线程池按照正常的业务逻辑执行任务,并通过原子方式更新线程池每个阶段的状态. ThreadPoolExecu ...

  6. linux下c语言版线程池

    1. 线程池原理 我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...

  7. Java多线程案例之线程池

    文章目录 一. 线程池概述 1. 什么是线程池 2. Java标准库提供的线程池 二. 线程池的简单实现 一. 线程池概述 1. 什么是线程池 线程池和和字符串常量池, 数据库连接池一样, 都是为了提 ...

  8. 一次因线程池使用不当造成生产事故的排查记录与思考

    美好的一天从bug结束 某日当我点开熟悉的界面,一个又一个请求失败的提示赫然出现在屏幕上,不会是昨晚上线的代码有问题吧? 吓得我急忙按F12查看了响应--"exception":& ...

  9. 线程池运用不当的一次线上事故

    来自:IT人的职场进阶 在高并发.异步化等场景,线程池的运用可以说无处不在.线程池从本质上来讲,即通过空间换取时间,因为线程的创建和销毁都是要消耗资源和时间的,对于大量使用线程的场景,使用池化管理可以 ...

最新文章

  1. python3字典升序排序_Python(32)常用指引:排序指南
  2. 了解这3点,你也能成为出色的Java工程师!
  3. 【编程练习】正整数分解为几个连续自然数之和
  4. 恢复IE8自带的源代码查看器
  5. Java之开发工具(1) - Eclipse 如何设置注释的模板
  6. 数据结构:列表(双向链表)的了解与示例
  7. ListView与Adapter笔记:ZrcListView
  8. Oracle数据泵的使用
  9. 行如风 Angular初识
  10. 未来教育 ***java二级考试题库第二十五套错题***
  11. Python 思维锻炼
  12. 电脑配置挑选速成攻略
  13. 定时 监控 shell 服务宕机自动重启,并发送短信通知
  14. UWB定位技术原理图解
  15. Linux网卡流量限制
  16. lmp91000偏压配置求助
  17. 在MATLAB中采用M文件实现对Simulink中的S函数程序实现自动调参数
  18. 怎么配置计算机终端网络ip地址,如何配置计算机的IP地址并测试联网
  19. 步骤条的实现原理及AliceUI中步骤条Step的应用
  20. uniapp 下拉列表插件 lable问题

热门文章

  1. 品牌到底要不要做全渠道?且听他们怎么说……
  2. 日志组件logback介绍及配置使用方法
  3. 【ORACLE】 安装需要注意的问题(一)
  4. 如何解决SVN Commit failed (details follow): Access denied
  5. 【ssi】增删改查六操作小框架(八)
  6. 五种最易被老板开除的人
  7. 原生CSS,实现点击按钮出现交互弹窗【新手扫盲】
  8. 【网址收藏】Hadoop3.2.1 【 YARN 】源码分析 : ResourceLocalizationService解析
  9. kafka发送及消费消息示例
  10. 【收藏】Kubernetes(十七) 基于NFS的动态存储申请