Java VisualVM为CPU分析提供了奇怪的结果 – 还有其他人遇到过这种情况吗?

我已经编写了这个小型(并且非常低效)的类,并希望使用Java VisualVM对其进行概要分析。

public class Test { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); br.readLine(); int n = Integer.parseInt(args[0]); int fib = fib(n); System.out.println(fib); } private static int fib(int n) { if (n < 2) { return n; } return fib(n-1)+fib(n-2); } } 

结果很奇怪。 调用ConnectionHandler.run()完全控制了结果。

(98.2%)sun.rmi.transport.tcp.TCPTransport $ ConnectionHandler.run()
(1.7%)java.lang.Thread.join(长)
(0%)java.lang.String.equals(Object)
等等…

可能有大约一百个方法分析,其中没有一个是fib(int)!

我的计划实际上是把所有时间花在这些方法上,这是不可思议的。 他们似乎是连接到我的jvm并做其事情的探查器。

我究竟做错了什么?

为清晰起见进行了编辑:如果您传入了45个n,则此应用程序将运行20个简单的秒数。 我最初分析的程序(不是斐波那契计算器)将我的cpu上的所有四个核心固定为100%并且我正在进行持续长达5分钟的分析运行。 它们具有相同的结果,我的应用程序中的方法并没有出现在热点方法列表中。

它因运行而异,但ConnectionHandler.run()始终位于顶部,通常占配置文件时间的约99%。

第二次编辑:我尝试过使用采样器,现在我得到的结果与JProfiler正在生成的结果一致。 这样做的缺点是我没有得到分析带来的堆栈跟踪信息。 但对于我的迫切需求,这是非常好的。

我在玩游戏时发现的东西是VisualVM在分析它们时计算方法调用的挂钟时间。

在我的特定情况下,我的应用程序有一个主线程,它启动工作线程并立即阻止等待队列中的消息。

这意味着阻塞方法似乎几乎占用了探查器的所有时间,尽管事实上这种方法并不占用我的CPU。

我希望sun.rmi.transport.tcp.TCPTransport $ ConnectionHandler.run()方法能够很好地完成它的工作 – 但是当它终止时它会成为我应用程序中运行时间最长的方法之一 – 反复出现

我认为这根本不可能。 你有一个应用程序,其中“有效载荷”是相当微小的(虽然这当然取决于n的值),你必须接受所需的额外努力(连接探查器并将所有信息转移到它)将沼泽有效载荷。

这不是我首先要分析的那种应用程序,因为非常明显的是无论如何都会在fib花费大量时间(对于n非平凡值),将其标记为优化的明显目标。

我更倾向于使用分析器来进行更实质的应用,其中:

  • 优化工作应该去哪里并不明显; 和
  • 有效载荷中有大量的工作要做。

如果你真的想测试那段代码,你可能需要通过(例如)替换来提高它的效果:

 int fib = fib(n); 

有:

 for (int i = 0; i < 100000; i++) { int fib = fib(n); ) 

不过,我会告诉你件事要注意。 我不知道任何特定JVM的内部,但使用递归方法,其中参数的减少是缓慢的通常是一个坏主意,导致堆栈空间很快耗尽。

通过这种方式,我的意思是二进制搜索是一个很好的候选者,因为它用每个递归级别移除剩余搜索空间的一半(因此十亿个项目的搜索空间只有30个级别)。

另一方面,对数量为1,000,000,000的Fibonacci序列使用递归将花费大约十亿个级别,并且大多数堆栈将很难包含该级别。

尾部递归优化可以避免这个问题,但是如果没有进行优化,则需要小心。

jvisualvm profiling可能会在加载时将字节码编织到类中。 由于你的程序只有一个类,并且在jvisualvm到达现场时已经初始化了,我认为它无法进行检测。

将fib方法移动到另一个类并再次尝试分析。 在jvisualvm中启用cpu profiling之前,您可以添加一个jvm选项“-verbose:class”来仔细检查是否未加载该类。

编辑:感谢JB的评论。 忘记我的课堂加载hogwash。 我的直觉是fib方法与main方法紧密耦合,因此它实际上是当前正在执行的字节码。

根据Ron的回答,能够通过在启动后立即停止JVM来改善结果,然后激活探查器,最后继续执行除外(通过按Enter键)。 这很粗糙。

 class Foobar { /* First line in Class */ static { try { System.in.read(); } catch (IOException e) { throw new RuntimeException(e); } } /* .. */ public static void main(..) { doMagic() } } 

我的猜测是你将一个小值传递给了fib,而且程序的运行时间不够长。 为了在分析(或基准测试)时获得几乎任何有意义的数据,通常需要至少几秒的经过时间。