在Object。<init>(hprof)</init>中花费的时间

时间:2013-08-15 02:03:36

标签: java memory profiling hprof

我正在分析我的应用程序 - 它是一个基于Java构建的宏系统。我正在使用hprof来分析一些基本示例,以下是花费时间的前20个函数:

rank   self  accum   count trace  method
   1 14.73% 14.73%     453 303755 java.lang.Object.<init>
   2  9.75% 24.48%     300 303754 java.lang.Object.<init>
   3  7.18% 31.66%     221 303641 java.lang.Object.<init>
   4  6.83% 38.49%     210 303621 java.util.ArrayList.<init>
   5  6.76% 45.25%     208 303620 java.util.ArrayList.<init>
   6  5.95% 51.20%     183 303833 java.lang.Character.toChars
   7  4.55% 55.75%     140 303809 java.lang.Object.<init>
   8  4.42% 60.18%     136 303791 java.lang.Object.<init>
   9  3.77% 63.95%     116 303756 java.lang.Object.<init>
  10  3.64% 67.59%     112 300509 java.lang.Object.<init>
  11  2.67% 70.25%      82 303789 java.lang.Object.<init>
  12  2.54% 72.79%      78 303790 java.lang.Object.<init>
  13  1.69% 74.48%      52 303688 java.lang.Object.<init>
  14  1.66% 76.14%      51 303644 java.lang.Object.<init>
  15  1.46% 77.60%      45 305935 java.lang.Object.<init>
  16  1.40% 79.00%      43 303758 java.lang.Object.<init>
  17  1.20% 80.20%      37 305324 java.lang.Object.<init>
  18  0.98% 81.18%      30 302559 java.lang.Object.<init>
  19  0.62% 81.79%      19 300006 java.util.Arrays.copyOf
  20  0.52% 82.31%      16 305214 java.lang.Object.<init>

如您所见,大部分时间花费在Object.<init>。这对我来说有些模糊。

我的直觉是时间是由内存分配完成的。在C中工作让我强烈感觉到动态内存分配(即malloc())是低效的。然而,在Java中,福音似乎是JVM有效地处理短期对象;因此,从对象池等模式中无法获得任何东西。

我应该补充说,应用程序中性能最高的部分是解析器,它确实创建了大量短期对象作为其操作的一部分。

您认为Object.<init>所花费的时间是多少?它确实与内存分配有关吗?我可以使用对象池或其他技巧来减少内存分配吗?

编辑:

在回答Mike Dunlavey的回答时,这里是JPerfAnal对hprof输出的解释,给出了包容性时间。

Method Times by Caller (times inclusive): 3076 ticks
  1: java.lang.Object.<init>: 71,26% (2192 inclusive / 2191 exclusive)
    2: com.sun.demo.jvmti.hprof.Tracker.ObjectInit: 0,03% (1 inclusive / 0 exclusive)
      3: java.lang.Thread.currentThread: 0,03% (1 inclusive / 1 exclusive)
  1: parser.ParseData.<init>: 34,33% (1056 inclusive / 0 exclusive)
    2: parser.ParseErrors.<init>: 13,98% (430 inclusive / 1 exclusive)
      3: java.lang.Object.<init>: 7,18% (221 inclusive / 221 exclusive)
      3: java.util.ArrayList.<init>: 6,76% (208 inclusive / 208 exclusive)
    2: java.lang.Object.<init>: 13,52% (416 inclusive / 416 exclusive)
    2: java.util.ArrayList.<init>: 6,83% (210 inclusive / 0 exclusive)
      3: java.util.ArrayList.<init>: 6,83% (210 inclusive / 210 exclusive)
  1: parser.Matcher.parse: 34,33% (1056 inclusive / 0 exclusive)
    2: parser.ParseData.<init>: 34,33% (1056 inclusive / 0 exclusive)
      3: parser.ParseErrors.<init>: 13,98% (430 inclusive / 1 exclusive)
        4: java.lang.Object.<init>: 7,18% (221 inclusive / 221 exclusive)
        4: java.util.ArrayList.<init>: 6,76% (208 inclusive / 208 exclusive)
      3: java.lang.Object.<init>: 13,52% (416 inclusive / 416 exclusive)
      3: java.util.ArrayList.<init>: 6,83% (210 inclusive / 0 exclusive)
        4: java.util.ArrayList.<init>: 6,83% (210 inclusive / 210 exclusive)
  1: java.util.ArrayList.<init>: 28,38% (873 inclusive / 419 exclusive)
    2: java.util.AbstractList.<init>: 14,76% (454 inclusive / 0 exclusive)
      3: java.util.AbstractCollection.<init>: 14,76% (454 inclusive / 0 exclusive)
        4: java.lang.Object.<init>: 14,76% (454 inclusive / 454 exclusive)

(JPerfAnal还会生成一个倒置的树,其中孩子是父母的来电者。为简洁起见,我不会重现它,但足以说大约有40%的Object.<init>次呼叫来自初始化ArrayListParseDataParseErrors。)

现在,这并没有真正改变我对这个问题或我的问题的看法。我可以改变算法,以便实例化更少的对象;但目前,我正在寻找正交解决方案。那么:对象池可以帮助我吗?

2 个答案:

答案 0 :(得分:1)

1)“自我”时间几乎总是无用的,因为真正的问题在于您的代码中调用那些低级方法 - 获得包容性时间(自我+被调用者)

2)“累积”栏目更加无用,因为它所做的就是加上“自我”时间。

其他专栏也告诉你没什么用处。

hprof获取堆栈跟踪。 你需要的是那些尽可能深的人 你不需要大量的(与流行的统计错误信息相反) - 十个可能绰绰有余 但是,您需要查看它们并理解它们。

Here's some general info about profiling.

编辑以响应显示JPerfAnal输出的编辑:

a)看起来堆栈深度仅为4.您需要尽可能深地查看堆栈深度。 我不关心他们呈现输出的方式。 我主要只是看看堆栈样本本身。 时间不是真正重要的事情。重要的是发生了什么以及为什么。

b)堆栈样本实际上包含行级信息。因此,他们会指出您的日常工作中的精确行,例如parser.ParseData.<init>parser.Matcher.parseparrser.ParseErrors.<init>,其中发生有问题的调用。 这将告诉你究竟哪些分配花费了所有时间,然后你可以决定是否有更好的方法来做它们。

我得到了解析器运行数据结构表的印象。 我知道你不想重新设计它,但如果你重新设计它,你将获得更好的性能。 基本上,如果你可以把它表达为一个递归下降的解析器,并且如果语言不经常改变,你可以直接在代码中编写解析器 - 没有表 - 它会快10到100倍。

答案 1 :(得分:1)

简短回答:

Object.<init>中花费的时间可能是由于使用堆配置文件在中生成的CPU配置文件;后者用于检测所有内存分配,使分配变慢并使它们浮现在CPU配置文件的顶部。

答案很长:

我怀疑问题中显示的配置文件是通过同时询问CPU配置文件和堆配置文件生成的。换句话说,它是通过执行以下操作生成的:

java -agentlib:hprof=heap=sites,cpu=samples,depth=20 MyProg

不幸的是,至少在JVM的内置hprof分析器中,CPU采样在堆分析的情况下是无用的。堆分析仪器记录所有内存分配,使程序运行顺序更慢。在用于跟踪内存分配的程序中,分配确实是瓶颈,这就是Object.<init>出现在CPU配置文件顶部的原因。但在实际程序中,分配/垃圾收集可能是,也可能不是瓶颈。

您通常希望一次只生成一个配置文件。首先通过运行类似java -agentlib:hprof=cpu=samples,depth=20的内容生成CPU配置文件,然后分析结果。如果CPU配置文件指向内存分配/垃圾收集作为瓶颈,那么,只有这样,才能生成并分析堆配置文件,以查看大部分分配来自哪里。

另一个答案提出了很多关于分析CPU配置文件的重点。特别是,通过查看原始hperf CPU配置文件,可以更容易地查看使用JPerfAnal的内容。此外,为了使包容性百分比合理准确,您可能需要增加堆栈采样深度以匹配您的应用。