【问题标题】:Java VisualVM giving bizarre results for CPU profiling - Has anyone else run into this?Java VisualVM 为 CPU 分析提供了奇怪的结果 - 还有其他人遇到过这个吗?
【发布时间】:2011-07-20 03:36:02
【问题描述】:

我编写了这个小(而且效率极低)的类,并希望使用 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)
等等……

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

令人难以置信的是,我的程序实际上将所有时间都花在了这些方法上。他们似乎是连接到我的 jvm 并做它的事情的分析器。

我做错了什么?

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

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

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

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

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

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

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

【问题讨论】:

  • 您使用的是 CPU 采样器还是插桩分析器?使用了哪些设置?您似乎正在使用 CPU 采样器(由于采样粒度等原因可能会得到有偏差的结果),或者您的分析设置未正确设置。
  • 这就是你可以用一些分析器得到的那种疯狂的东西。当您试图弄清楚这一点时,只需尝试this method
  • 看起来您遇到了与 Glunk 相同的问题:stackoverflow.com/questions/3243100/… 这似乎是 jvisualvm 方法检测缺陷,如果您从 main 调用它就无法重新定义。
  • @JB 感谢您指出采样模式。
  • @FrancisStephens 你能告诉我哪些设置适合你吗?

标签: java visualvm profiling


【解决方案1】:

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

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

我更倾向于将分析器用于更重要的应用程序,其中:

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

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

int fib = fib(n);

与:

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

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

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

另一方面,对数字 1,000,000,000 的斐波那契数列使用递归将需要大约 10 亿个级别,而大多数堆栈都很难包含这些级别。

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

【讨论】:

  • 感谢您周到的回答。我对分析递归斐波那契查找器并不感兴趣。我试图用 VisualVM 分析一个真正的软件,得到了同样令人困惑的结果。
【解决方案2】:

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

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

编辑:感谢 JB 的评论。忘记我的类加载废话。我的直觉是 fib 方法与 main 方法耦合太紧,所以它实际上是当前正在执行的字节码。

【讨论】:

  • 不,检测分析器可以重新转换已加载的类。唯一禁止检测代码生效的是当前正在执行的当前字节码(例如当前调用的方法)。
  • JProfiler 在检测 fib 示例时没有任何问题,所以如果有任何东西阻止 VisualVM 做同样的事情,我会感到惊讶。我真的在尝试使用 VisualVM 分析一个更大(非递归)的应用程序,并获得与 Fib 相同的结果。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

    我的猜测是您传递给 fib 的值太小,而程序运行的时间不够长,无法注册。要在分析(或基准测试)时获得几乎所有有意义的数据,您通常需要至少几秒钟的经过时间。

    【讨论】:

      猜你喜欢
      • 2011-02-03
      • 2023-03-22
      • 1970-01-01
      • 2016-12-03
      • 1970-01-01
      • 1970-01-01
      • 2011-04-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多