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

时间:2011-03-22 02:12:54

标签: java visualvm profiling

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

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

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

我做错了什么?

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

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

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

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

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

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

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

4 个答案:

答案 0 :(得分:3)

我认为这根本不可能。你有一个应用程序,其中“有效负载”是相当微小的(虽然这当然取决于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序列使用递归将需要大约10亿个级别,并且大多数堆栈将很难包含该级别。

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

答案 1 :(得分:1)

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

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

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

答案 2 :(得分:1)

根据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()
    }
}        

答案 3 :(得分:0)

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