使用jstack调试实时Java生产服务器的指南
jstack就像U2一样-从时间的黎明就一直在我们身边,我们似乎无法摆脱它 。 除了笑话,到目前为止,jstack是您调试军用生产服务器中最方便的工具之一。 即便如此,我仍然认为它在情况恶化时能够将您从火中扑灭的能力仍未得到充分利用,因此我想分享一些方法,您可以在对抗生产错误的战争中将其增压为更强大的武器。
jstack的核心是一个超级简单的工具,它向您显示在目标JVM中运行的所有Java线程的堆栈跟踪。 只需通过一个pid将其指向JVM进程即可获得该时间点所有线程堆栈跟踪的打印输出。 这使您能够回答“这个服务器在做什么?”这个古老的问题,并使您更进一步地了解它为什么真正在做它。 关于jstack的最大优点是它轻巧–它不会给JVM增加任何性能开销,也不会更改其执行状态(与调试器或探查器不同)。
由于没有什么是完美的 ,因此jstack有两个明显的缺点。 第一个是jstack除了调用堆栈外没有为您提供任何变量状态,这意味着尽管您可能正在查看堆栈,但您不知道将其置于何处的状态。 一个很好的例子是查看挂起的JVM,其中jstack会向您显示大量线程正在执行数据库查询或等待获得连接。
这可能意味着某些查询执行时间太长,导致其他线程要么等待连接,要么被拒绝。 在这里,您真的想知道正在执行的查询(或查询的参数是什么)会导致速度下降以及何时开始。 当然,这只是一个例子,在很多情况下,其中某些线程被阻塞并降低了应用程序的吞吐量。 但是不幸的是,使用jstack时,因为您没有获得任何变量状态,所以您无法真正确定应归咎于哪个线程。 还是可以吗?
jstack的第二个缺点是它不是永远在线的工具。 这意味着,当问题发生时,您必须在那里-在生产中这是罕见的事件。 在不断重启VM的弹性环境中,这一点更为真实。
好的部分到了–让我们看一下两种技术,它们可以帮助我们克服这两个缺点,并使一个好的工具真正变得很棒。
创建有状态线程数据
第一个问题是如何向jstack打印输出添加状态? 答案简单而强大-线程名称。 尽管许多人错误地认为线程名称是不可变的,或者是操作系统确定的属性,但实际上,每个线程都具有可变的且非常重要的特征。 这也是进入您的jstack流的关键所在。
实际的应用程序就像日志记录一样,一旦它通过诸如servlet,actor或Scheduler之类的入口点输入代码,就应该控制线程名。 此时,您需要将其名称设置为一个有意义的值,该值可以帮助您了解执行上下文和相关参数,从而可以帮助您隔离事务及其内容。
这很可能包括–
- 线程的用途(例如,处理消息,响应用户请求等)。
- 使用事务ID,您可以识别跨不同机器和应用程序各部分的此特定数据流。
- 参数值,例如servlet参数或要出队的消息的ID。
- 获得线程控制的时间。 对于使用jstack观察它们时,确切地知道代码中的哪些线程被卡住,最后一项至关重要。
Thread.currentThread().setName(Context + TID + Params + current Time,..);
这些数据将意味着查看打印输出(例如下面的打印输出实际上并不能告诉我们任何线程在做什么或为什么)与提供信息的输出之间的区别:
“ pool-1-thread-1”#17 prio = 5 os_prio = 31 tid = 0x00007f9d620c9800 nid = 0x6d03 in Object.wait()[0x000000013ebcc000]
将此与以下线程打印输出进行比较:
“队列处理线程,MessageID:AB5CAD,类型:AnalyzeGraph,队列:ACTIVE_PROD,Transaction_ID:5678956,开始时间:2014/10/8 18:34”
#17 prio = 5 os_prio = 31 tid = 0x00007f9d620c9800 nid = 0x6d03 in Object.wait()[0x000000013ebcc000]
您在这里看到的是对该线程实际作用的更全面的解释。 您可以轻松地从AWS队列中查看其出队消息,它正在分析该消息,其类型,ID和事务ID。 最后,但并非最不重要–线程何时开始对其进行处理。 这可以帮助您非常快速地将注意力集中在那些被卡住的线程上,并查看它们所处的状态。从那以后,在本地进行优化和复制将变得更加容易。
这里的替代方案是要么希望日志文件中有数据,而且能够将日志中的数据关联到该确切线程。 另一个选择是在生产环境中本地或远程连接调试器。 既不是很愉快又很费时间。
在线程名称中写入此信息也有助于传统日志记录。 即使大多数日志记录框架都提供了可以添加到日志中的基于线程的上下文,您也必须确保正确配置它。 使用线程名称还可以确保您将在日志中拥有所需的所有数据。
注意:有些人可能会说不要修改或更改线程名称。 根据我多年的个人经验以及许多同事的经验,我对此非常信奉。
使jstack始终在线
使用jstack时,我们面临的第二个挑战是,就像调试器一样,它是您必须在发生问题时立即手动捕获损坏状态的工具。 但是,当服务器挂起或低于或低于某个特定阈值时,有一种更活跃的方法可以使用jstack自动生成打印输出。 关键是要以编程方式调用jstack,就像在满足特定应用程序条件时从JVM中的任何日志记录功能一样。
这里的两个主要挑战是何时以及如何实现。
如何以编程方式激活jstack?
由于jstack是一个普通的OS进程,因此调用它非常简单。 您要做的就是激活jstack进程并将其指向您自己。 这里的关键是如何从JVM中获取进程的pid。 实际上,没有标准的Java API可以做到这一点( 至少要等到Java 9才可以 )。 这是完成工作的一小段代码(尽管它不是已记录的api的一部分):
String mxName = ManagementFactory.getRuntimeMXBean().getName();int index = mxName.indexOf(PID_SEPERATOR);String result;if (index != -1) {result = mxName.substring(0, index);
} else {throw new IllegalStateException("Could not acquire pid using " + mxName);
}
另一个小挑战是将jstack输出定向到您的日志中。 使用输出流检流器也很容易设置。 在此处查看有关如何将您调用的进程输出的输出数据定向到日志文件或输出流中的示例。
尽管可以使用getAllStackTraces在内部捕获正在运行的线程的堆栈跟踪,但出于多种原因,我更喜欢通过运行jstack来执行此操作。 首先,这是我通常希望在正在运行的应用程序外部进行的操作(即使JVM正在参与提供信息),以确保我不会通过内省调用来影响应用程序的稳定性。 另一个原因是,jstack在功能方面更为强大,例如向您显示本机框架和锁定状态,而这在JVM中是不可用的。
什么时候激活jstack?
您需要做出的第二个决定是在什么条件下您希望JVM记录jstack。 当服务器低于或高于特定处理(即请求或消息处理)阈值时,可能会在预热期之后执行此操作。 您可能还需要确保每次激活之间都花费足够的时间。 只是为了确保您不会在低负载或高负载下泛滥日志。
您将在此处使用的模式是从JVM内加载看门狗线程,该线程可以定期查看应用程序的吞吐量状态(例如,最近两分钟内处理的消息数),并确定是否对屏幕进行“截屏”。线程状态将很有帮助,在这种情况下,它将激活jstack并将其记录到文件中。
设置该线程的名称以包含目标吞吐量和实际吞吐量状态,因此当您执行自动jstack快照时,您可以确切地看到看门狗线程决定这样做的原因。 由于这只会每隔几分钟发生一次,因此该过程没有实际的性能开销,尤其是与所提供数据的质量相比。
下面的代码片段显示了这种模式的实际效果。 startScheduleTask加载看门狗线程以定期检查吞吐量值,该吞吐量值在处理消息时使用Java 8 并发加法器递增。
public void startScheduleTask() {scheduler.scheduleAtFixedRate(new Runnable() {public void run() {checkThroughput();}}, APP_WARMUP, POLLING_CYCLE, TimeUnit.SECONDS);
}private void checkThroughput()
{int throughput = adder.intValue(); //the adder in inc’d when a message is processedif (throughput < MIN_THROUGHPUT) {Thread.currentThread().setName("Throughput jstack thread: " + throughput);System.err.println("Minimal throughput failed: exexuting jstack");executeJstack(); //see the code on github to see how this is done}adder.reset();
}
- 在此处可以找到从代码中抢占调用jstack的完整源代码。
翻译自: https://www.javacodegeeks.com/2014/10/supercharged-jstack-how-to-debug-your-servers-at-100mph.html