【问题标题】:Strange JVM thread hangs - suggestions to troubleshoot?奇怪的 JVM 线程挂起 - 排除故障的建议?
【发布时间】:2012-10-09 13:40:41
【问题描述】:

在解决生产环境中的一个 jvm 挂起问题时,我们遇到了执行以下记录器语句的线程之一

logger.debug("Loaded ids as " + ids + ".");

在此步骤挂起,线程状态为可运行。这里 ids 是一个集合。还有另一个线程通过倒数锁存器等待上述线程以完成其任务。该软件每 15 分钟进行一次线程转储,两个线程的堆栈跟踪如下所示

Stack trace for [THREAD GROUP: Job_Executor] [THREAD NAME:main-Runner Thread][THREAD STATE: WAITING]
    ...sun.misc.Unsafe.park(Native Method)
    ...java.util.concurrent.locks.LockSupport.park(Unknown Source)
    ...java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(Unknown Source)
    ...java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(Unknown Source)
    ...java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(Unknown Source)
    ...java.util.concurrent.CountDownLatch.await(Unknown Source)
    ...com.runner.MainRunner.stopThread(MainRunnerRunner.java:1334)


Stack trace for [THREAD GROUP: Job_Executor] [THREAD NAME:task executor][THREAD STATE: RUNNABLE]    
    ...java.util.AbstractCollection.toString(Unknown Source)           
    ...java.lang.String.valueOf(Unknown Source)      
    ...java.lang.StringBuilder.append(Unknown Source)    
    ...com.runner.CriticalTaskExecutor.loadByIds(CriticalTaskExecutor.java:143)

这个 jvm 挂了将近 24 小时,最后我们不得不杀死它才能继续前进。线程转储表明有 43 个线程处于 RUNNABLE 状态,包括上述线程。

上述线程在执行collection.toString() 24 小时内处于RUNNABLE 状态的原因可能是什么?

对如何进行有什么建议吗?

【问题讨论】:

  • 你能告诉我们toString()方法吗?任何延迟加载休眠字段或其他复杂性?
  • 我会把它作为一个建议。感谢您指出。

标签: java multithreading jvm hung


【解决方案1】:

仅执行 collection.toString() 24 小时以上线程处于 RUNNABLE 状态的原因可能是什么?

您没有提供足够的信息来诊断问题。我只会挑战你不要假设这里发生了 JVM 问题。

如果我们查看AbstractCollection.toString() 方法的源代码,我们会看到它遍历集合并输出大约“[ item0, item1, item2 ]”。调用每个item.toString() 方法来显示项目。

如果应用程序挂起在集合toString() 中,那么我的猜测是集合上的迭代器存在问题。如果您的应用程序正在旋转,您可以知道这一点——使用接近 100% 的 CPU。也许Set 上的hasNext() 方法总是返回true

如果应用程序挂起实际上是在item.toString() 内,那么我会确保您的项目只是显示简单的字段。请注意如果访问会进行 RPC 调用的字段,例如延迟加载的 ORM 包装字段。

如果您提供有关Set 的详细信息并显示id.toString() 代码,我们可以提供更多帮助。

现在听起来这是一组Integer 对象。不知道为什么这会挂起您的应用程序。以下是一些其他想法:

  • 您是否以非同步方式访问此集合?是否有多个线程对集合进行了更改,从而导致其迭代器旋转而损坏?您可以尝试将其包装在 Collections.synchronizedSet(...) 中。
  • Set 是否有可能巨大 并且您正在运行接近内存不足并且程序正在崩溃?但是,这不会挂起您的应用程序,而只会使其缓慢爬行。而且您会开始看到内存不足异常。
  • toString() 是否有可能被一遍又一遍地调用?不过,我假设您会在日志中看到这一点。

【讨论】:

  • 感谢您的意见。底层集合是一个 HashSet。只是为了让我理解,如果代码尝试对延迟加载的集合进行 RPC 调用,堆栈跟踪不会表明这一点吗?我的意思是它不会显示尝试进行数据库调用的休眠代码的痕迹吗?
  • 你会这么认为,是的@Andy。我只是看不到 HashSet 挂任何东西,所以假设还有其他事情发生。
  • 啊哈!! @AndyDufresne 你在这个集合上同步吗?有没有可能多个线程以非同步方式写入此集合并损坏了它以使某些东西在旋转?
  • 您可以尝试将其包装在 Collections.synchronizedSet(...) @AndyDufresne。
  • 感谢您的想法。以下是我对您提到的上述三点的回应 - 1.集合不被多个线程访问。我通过代码确认了这一点。如果是这种情况,线程转储也会表明,但没有任何痕迹。 2. 不,套装的尺寸并不大。它几乎没有任何元素。此外,由于 jvm 挂起超过 24 小时,这将确认内存不是问题。 3. toString() 只是在那个记录器中被调用。我不认为 log4j 被多次迭代地调用它。
【解决方案2】:

这取决于被调用的toString() 方法。我见过AbstractCollection.toString 在构造的String 对堆来说太大时倒下。否则,问题可能出在集合中对象的toString 中。

要弄清楚它是哪一个,需要更多的堆栈转储(大约 10 个)。卡住的线程可能通常在导致问题的toString 中。

作为快速修复,替换

logger.debug("Loaded ids as " + ids + ".");

logger.debug("Loaded ids as {}.", ids);

(假设您使用的是 slf4j,否则请查找在您的框架中进行参数化日志记录的适当方法)。

如果未启用调试,这将跳过 toString。

【讨论】:

  • 这如何解决toString()方法的问题?
  • @Gray 没有。但他们说问题出在生产上。 toString 主要面向开发人员,恕我直言,通常不应在生产环境中调用。
  • 真的吗? toString() 一直被调用以在我们的环境中进行生产日志记录。渲染视图时,Web 应用程序在容器对象上到处调用toString()
  • @artbristol 很高兴看到 MHO 反映在 YHO 中。很多人通过争论 toString 不是对象转储器,而是 GUI 填充器来“赢得”关于疯狂代码的讨论:(
  • 我们使用 log4j,这意味着我们将在记录语句之前添加一个 if 检查以查看日志级别是否为调试。但正如格雷所说,这似乎对这个问题没有任何作用。该软件在该过程的进一步运行中运行良好。我们正在尝试了解上述罕见 jvm 挂起的根本原因。
猜你喜欢
  • 2010-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-07
  • 1970-01-01
  • 2020-07-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多