如何在不损坏应用程序输出的情况下重定向JVM输出?

18

最近我正在编写一些微基准测试代码,因此我必须将JVM的行为与我的基准信息一起打印出来。 我使用

-XX:+PrintCompilation
-XX:+PrintGCDetails

除了其他获取JVM状态的选项外,我可以简单使用System.out.print()方法来获取基准信息。因为我需要知道打印消息的顺序和JVM输出。

当我只是在控制台中将它们打印出来时,我可以得到很好的结果,虽然JVM输出有时会打乱我的消息,但由于它们在不同的线程中,这是可以理解和接受的。

当我需要进行批量基准测试时,我想通过将输出重定向到文件,并使用管道(在Linux系统中为>)来从文件中获取结果并分析它们。

问题是:

JVM输出总是与我在Java应用程序中打印的消息重叠。 它破坏了消息的完成度。

有什么办法处理这种情况吗?我需要在同一个地方保留JVM输出和应用程序输出,以保留顺序,因为这很重要。同时它们不相互重叠,这样我就不会失去任何东西。

9个回答

9
我建议您稍微绕路,看看使用Java Instrumentation APIs - 使用(编写)一个简单的Java代理程序来完成这项任务。从您的基准测试角度来看,这将为您提供更强大的功能。您可以使用Java代理记录所有内容(因此不会有不同记录器线程之间的争用)。
您可以在http://www.javabeat.net/2012/06/introduction-to-java-agents/http://today.java.net/pub/a/today/2008/04/24/add-logging-at-class-load-time-with-instrumentation.html阅读更多信息。

好主意,我尝试在代理中重定向stdout和stderr,我可以重定向来自我的Java代码的输出,但JVM输出未被重定向。而你提到的文章介绍的方法——在加载类时修改字节码,我必须知道JVM在输出信息时调用了什么,然后也许我可以修改它,但我没有那种信息,你有什么建议吗? - dawnstar

5

使用Log4J或消息驱动的日志框架而不是System.out.println()

Log4J使用消息事件模型,保证消息的顺序。此外,可以使用各种“附加器”将日志记录到数据库或其他输出/文件中,允许按Java包和其他属性进行分离,以避免数据混合。

此外,考虑使用高性能定时器和/或不要尝试测量非常短(毫秒级)的事件。原因是调用System.currentTimeMillis()将反过来调用操作系统时钟。在每个操作系统上都有一些“时钟漂移”和缓存,使得底层系统函数可能返回相同的值,从而导致实际时间的+/- 30毫秒偏移。为了纠正这个问题或增加准确性,请将被测量的函数分组成足够大的样本大小,然后除以迭代次数。

例如,将平均1-2毫秒的10K个操作作为一个测量操作执行。然后除以10K以获得每个操作的时间。

否则,需要使用高性能计时器。


这并没有回答原始问题。Log4J无法帮助处理HotSpot诊断日志,也不能解决将它们与应用程序输出混合在同一文件中,并以反映它们时间关系的方式进行排列的问题。 - Per Mildner
热点诊断在问题中没有提到。问题是如何保留日志序列而不重叠,而解决方案肯定是一种方法。 - Darrell Teague
问题(在我阅读时已被编辑)提到了HotSpot诊断,例如-XX:+PrintCompilation,并且需要在第一段正确地与其他日志记录排序。 - Per Mildner

5

尝试使用System.out.println()而不是System.out.print()System.out.println()在同步块内强制进行流刷新,至少你的输出不会像混合一样。


4
直接通过System.out.print/println进行日志记录被认为是不良实践。
为什么?
1.它不是“线程安全”的。从多个线程记录会导致混乱的文本。
2.它不灵活,因为它是硬编码的而且没有配置。
3.它不灵活,因为您无法指定在日志中想要看到的详细级别(例如详细跟踪/特定调试逻辑/应用程序警告/应用程序错误处理/应用程序致命错误)。您总是得到很多内容,并需要注释许多代码行以避免记录过载。
4.它不灵活,因为您无法指定您感兴趣的包/类或不感兴趣的包/类 - 再次,您总是得到很多内容,并需要注释许多行以获得更简单和更具体的内容。
5.它不灵活,因为您无法将日志重定向到数据库表和列、文件、电子邮件、消息系统、短信提醒等。
6.当您有数千/数百万行被记录到物理磁盘时,速度会变慢。
7.您不能将不同的日志级别/包或类流式传输到不同的日志记录目标。此外,您无法将其配置为记录到与应用程序服务器及其JVM相同的目标或不同的目标。
在2000年,Log4J被引入。它解决了所有这些问题,自那以后一直是标准解决方案。虽然有一些最新和最好的日志记录工具试图超越Log4J,但你仍然可以通过Log4J获得强大、灵活的结果。如果你将所有的System.out.print调用切换到Log4J,那么你引用的问题和许多其他问题都将消失。

http://logging.apache.org/log4j/1.2/manual.html


2
对于-XX:+PrintCompilation,你可以使用-XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation标志来获取单独的"hotspot.log"文件中的“详细”输出。该文件以XML格式呈现,包含了-XX:+PrintCompilation的信息和编译的原因。文件路径可以通过-XX:LogFile=<new_hotspot_log>进行更改。参考文献:https://wiki.openjdk.java.net/display/HotSpot/LogCompilation+overview 对于-XX:+PrintGCDetails,你可以使用-Xloggc:<gc_log>将GC输出重定向到指定的文件。参考文献:java -X

0

首先,我会尝试@barracel提到的使用System.out.println()。

虽然我不太了解Java,但你也可以将所有调试消息写入stderr并将stdout留给JVM。这可能会防止多个线程同时写入同一文件描述符时发生的stdout污染。


0
我建议尝试以下方法。这更像是一种黑客方式,需要一些调整。但是掌握这种方法可能会在长期内产生回报。特别是如果您进行大量基准测试的话。
话虽如此,我相信HS(现在的Oracle)应该有一个选项将编译器输出重定向到文件。只要你足够努力地寻找,就能找到它 :-) HS应该有一个选项打印出他们所有的JVM和编译器选项,其中可能包括将输出重定向到文件的选项。
无论如何,我离题了...
1)$JAVA_HOME或%JAVA_HOME%中应该有src.zip。它包含Java类库的源代码。
2)修改System.out以将所有输出重定向到特定文件或仅使其插入某些特殊符号,以便您可以grep捕获stdout和stderr。不幸的是,我无法对这个特定步骤提供更多具体信息,因为我们公司的政策禁止我们检查src.zip的内容。我只能想象这一步有多难。也许它像用输出流交换“out”那样简单,或者像修改应用程序直接使用的每个print方法那样困难。我甚至不知道System.out使用多少本地语言。
3)将编译后的版本放入一个jar文件中。

4) 在命令行中添加此选项:-Xbootclasspath/p:full_path_to_your_jar。这将告诉JVM首先使用您的类版本。 "P"代表prepend。

希望这可以帮助到您...


0

尝试将JVM和应用程序的输出分开。

  • 将JVM的信息输出到stdout
  • 使用"System.err.println()"将应用程序的信息输出到stderr
  • 使用您喜欢的工具分析输出。

因此,命令行如下:

$java -XX:+PrintCompilation -XX:+PrintGCDetails MainClass 1>stdout.txt 2>stderr.txt

0
为了使输出不重叠,使用System.out.println。然后您可以像这样重定向到同一文件:
java -XX:+PrintCompilation -XX:+PrintGCDetails MainClass 1>stdout.txt 2>&1

这个文件名为stdout.txt,包含了所有的错误和正常控制台输出。

另外,如果日志中有任何形式的线程/时间信息,您可以简单地使用

sort -n -k 1

-k 1代表您拥有线程/数据(时期)信息的列。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接