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

2022-09-02 04:49:46

我已经编写了这个小的(而且效率极低的)类,并希望使用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(long)
(0%) java.lang.String.equals(Object)
etc...

大概有大约一百种方法被描述,其中没有一个是fib(int)!

不可思议的是,我的程序实际上把所有的时间都花在这些方法上。它们似乎是连接到我的jvm并执行其操作的探查器。

我做错了什么?

为清楚起见,已编辑:如果为 n 传入 45,则此应用程序将运行 20 个分析良好的秒。我最初分析的程序(不是斐波那契计算器)将CPU上的所有四个内核都固定在100%上,并且我进行的性能分析运行持续长达5分钟。这些具有相同的结果,并且我的应用程序中的方法在热点方法列表中没有出现在高位。

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

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

我在玩游戏时发现的一点是,VisualVM 在分析方法调用时会计算方法调用的挂钟时间。

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

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

我希望 sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run() 方法也是如此,它很好地完成了它的工作 - 但是当它终止时,它成为我的应用程序中运行时间最长的方法之一 - 重复


答案 1

我不认为这是不可想象的。您有一个应用程序,其中“有效负载”相当小(尽管这当然取决于 的值),并且您必须接受所需的额外工作(连接探查器并将所有信息转移到它上)将淹没该有效负载。n

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

我更倾向于将探查器用于更实质性的应用程序,其中:

  • 目前尚不清楚优化工作应该去哪里;和
  • 在有效载荷中有一些大量的工作要做。

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

int fib = fib(n);

跟:

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

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

我的意思是,二进制搜索是一个很好的候选者,因为它在每个递归级别中删除了一半的剩余搜索空间(因此十亿个项目的搜索空间只有30个级别)。

另一方面,对数字1,000,000,000的斐波那契序列使用递归将花费大约十亿个水平,大多数堆栈将很难包含它。

尾端递归优化可以避免这个问题,但你需要小心,以防优化没有完成。


答案 2

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

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

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


推荐