文章插图
接着分析为何有两万多个Datanode State Machine Thread,代码里可以看到该线程用线程池newCacheThreadPool创建 。该newCacheThreadPool在没有线程可用,例如线程都在等锁的情况下,会创建新的线程,因此创建了两万多个线程 。接着分析Datanode State Machine Thread等的什么锁 。在进程的线程数超过5000时,用jstack -l pid > jstack.txt打出所有线程的状态 。
可以看到几乎所有Datanode State Machine Thread在等锁,而只有一个Datanode State Machine Thread – 5500 拿到了锁,但是卡在提交RPC请求submitRequest 。至此Java调用C++发生Crash 和Java内Crash的原因找到 。
文章插图
文章插图
死锁log4j导致的死锁jstack打出的死锁信息如下所示 。grpc-default-executor-14765线程拿到了log4j的锁,在等RaftServerImpl的锁;grpc-default-executor-14776线程拿到了RaftServerImpl的锁,在等log4j的锁,导致这两个线程都拿到了对方等待的锁,所以造成两个线程死锁 。可以看出,仅仅打日志的log4j,不释放锁是最值得怀疑的地方 。最后发现log4j存在死锁的缺陷[4] 。该缺陷在log4j2得到解决,升级log4j即可 。
文章插图
文章插图
封装不严谨导致的死锁jstack打出的死锁信息如下所示 。grpc-default-executor-3449线程拿到了RaftLog的锁,在等DataBlockingQueue的锁;SegmentedRaftLogWorker拿到了DataBlockingQueue的锁,在等RaftLog的锁 。
文章插图
文章插图
这里最值得怀疑的是SegmentedRaftLogWorker拿到了DataBlockingQueue的锁却不释放,因为queue的操作只是在队列里增、删、查元素 。如下图所示DataBlockingQueue的方法poll,使用的锁是自己封装的锁AutoCloseableLock implement AutoCloseable,锁的释放依赖于AutoCloseableLock重载的close方法 。
文章插图
再看acquire方法,先用lock.lock()拿到锁,再创建新的AutoCloseableLock对象,如果拿到锁后,在创建新对象AutoCloseableLock时发生OOM等异常,锁就无法释放 。
文章插图
参考
[1]https://www.waitingforcode.com/Apache-spark/apache-spark-off-heap-memory/read
[2]https://github.com/alibaba/arthas/releases/tag/arthas-all-3.3.6
[3]https://www.jaegertracing.io/docs/1.18/getting-started/
[4]https://stackoverflow.com/questions/3537870/production-settings-file-for-log4j/
推荐阅读
- javascript 知识普及之 FormData
- 技术转载 || 使用java API进行zip递归压缩文件夹以及解压
- 两万字长文读懂 Java 集合
- 腾讯算法:判断一个数是否在40亿个整数中?最后附java代码
- Java最全面试题之Spring篇
- 什么是Java可变参数列表?怎么和重载机制配合使用?
- Java安全编码之sql注入
- 异步文件通道Java NIO你需要了解多少,来看看这篇文章
- Java开源框架之SpringMVC原理及源码解析
- 破解 Java Agent 探针黑科技