万字详文:Java内存泄漏、性能优化、宕机死锁的N种姿势( 五 )

万字详文:Java内存泄漏、性能优化、宕机死锁的N种姿势
文章插图
 
接着分析为何有两万多个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的原因找到 。

万字详文:Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 

万字详文:Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 
死锁log4j导致的死锁jstack打出的死锁信息如下所示 。grpc-default-executor-14765线程拿到了log4j的锁,在等RaftServerImpl的锁;grpc-default-executor-14776线程拿到了RaftServerImpl的锁,在等log4j的锁,导致这两个线程都拿到了对方等待的锁,所以造成两个线程死锁 。可以看出,仅仅打日志的log4j,不释放锁是最值得怀疑的地方 。最后发现log4j存在死锁的缺陷[4] 。该缺陷在log4j2得到解决,升级log4j即可 。
万字详文:Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 

万字详文:Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 
封装不严谨导致的死锁jstack打出的死锁信息如下所示 。grpc-default-executor-3449线程拿到了RaftLog的锁,在等DataBlockingQueue的锁;SegmentedRaftLogWorker拿到了DataBlockingQueue的锁,在等RaftLog的锁 。
万字详文:Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 

万字详文:Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 
这里最值得怀疑的是SegmentedRaftLogWorker拿到了DataBlockingQueue的锁却不释放,因为queue的操作只是在队列里增、删、查元素 。如下图所示DataBlockingQueue的方法poll,使用的锁是自己封装的锁AutoCloseableLock implement AutoCloseable,锁的释放依赖于AutoCloseableLock重载的close方法 。
万字详文:Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 
再看acquire方法,先用lock.lock()拿到锁,再创建新的AutoCloseableLock对象,如果拿到锁后,在创建新对象AutoCloseableLock时发生OOM等异常,锁就无法释放 。
万字详文:Java内存泄漏、性能优化、宕机死锁的N种姿势

文章插图
 
参考
[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/




推荐阅读