分析器(Profilers)

下面介绍分析器(profilers, Oracle官方翻译是:抽样器)。相对于前面的工具, 分析器只关心GC中的一部分领域. 本节我们也只关注分析器相关的GC功能。

首先警告 —— 不要认为分析器适用于所有的场景。分析器有时确实作用很大, 比如检测代码中的CPU热点时。但某些情况使用分析器不一定是个好方案。

对GC调优来说也是一样的。要检测是否因为GC而引起延迟或吞吐量问题时, 不需要使用分析器. 前面提到的工具( jstat 或 原生/可视化GC日志)就能更好更快地检测出是否存在GC问题. 特别是从生产环境中收集性能数据时, 最好不要使用分析器, 因为性能开销非常大。

如果确实需要对GC进行优化, 那么分析器就可以派上用场了, 可以对 Object 的创建信息一目了然. 换个角度看, 如果GC暂停的原因不在某个内存池中, 那就只会是因为创建对象太多了。 所有分析器都能够跟踪对象分配(via allocation profiling), 根据内存分配的轨迹, 让你知道 实际驻留在内存中的是哪些对象

分配分析能定位到在哪个地方创建了大量的对象. 使用分析器辅助进行GC调优的好处是, 能确定哪种类型的对象最占用内存, 以及哪些线程创建了最多的对象。

下面我们通过实例介绍3种分配分析器: hprof, JVisualVMAProf。实际上还有很多分析器可供选择, 有商业产品,也有免费工具, 但其功能和应用基本上都是类似的。

hprof

hprof 分析器内置于JDK之中。 在各种环境下都可以使用, 一般优先使用这款工具。

要让 hprof 和程序一起运行, 需要修改启动脚本, 类似这样:

  1. java -agentlib:hprof=heap=sites com.yourcompany.YourApplication

在程序退出时,会将分配信息dump(转储)到工作目录下的 java.hprof.txt 文件中。使用文本编辑器打开, 并搜索 “SITES BEGIN” 关键字, 可以看到:

  1. SITES BEGIN (ordered by live bytes) Tue Dec 8 11:16:15 2015
  2. percent live alloc'ed stack class
  3. rank self accum bytes objs bytes objs trace name
  4. 1 64.43% 4.43% 8370336 20121 27513408 66138 302116 int[]
  5. 2 3.26% 88.49% 482976 20124 1587696 66154 302104 java.util.ArrayList
  6. 3 1.76% 88.74% 241704 20121 1587312 66138 302115 eu.plumbr.demo.largeheap.ClonableClass0006
  7. ... 部分省略 ...
  8. SITES END

从以上片段可以看到, allocations 是根据每次创建的对象数量来排序的。第一行显示所有对象中有 64.43% 的对象是整型数组(int[]), 在标识为 302116 的位置创建。搜索 “TRACE 302116” 可以看到:

  1. TRACE 302116:
  2. eu.plumbr.demo.largeheap.ClonableClass0006.<init>(GeneratorClass.java:11)
  3. sun.reflect.GeneratedConstructorAccessor7.newInstance(<Unknown Source>:Unknown line)
  4. sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  5. java.lang.reflect.Constructor.newInstance(Constructor.java:422)

现在, 知道有 64.43% 的对象是整数数组, 在 ClonableClass0006 类的构造函数中, 第11行的位置, 接下来就可以优化代码, 以减少GC的压力。

Java VisualVM

本章前面的第一部分, 在监控 JVM 的GC行为工具时介绍了 JVisualVM , 本节介绍其在分配分析上的应用。

JVisualVM 通过GUI的方式连接到正在运行的JVM。 连接上目标JVM之后 :

  1. 打开 “工具” —> “选项” 菜单, 点击 性能分析(Profiler) 标签, 新增配置, 选择 Profiler 内存, 确保勾选了 “Record allocations stack traces”(记录分配栈跟踪)。
  2. 勾选 “Settings”(设置) 复选框, 在内存设置标签下,修改预设配置。
  3. 点击 “Memory”(内存) 按钮开始进行内存分析。
  4. 让程序运行一段时间,以收集关于对象分配的足够信息。
  5. 单击下方的 “Snapshot”(快照) 按钮。可以获取收集到的快照信息。

分析器(Profilers) - 图1

完成上面的步骤后, 可以得到类似这样的信息:

分析器(Profilers) - 图2

上图按照每个类被创建的对象数量多少来排序。看第一行可以知道, 创建的最多的对象是 int[] 数组. 鼠标右键单击这行, 就可以看到这些对象都在哪些地方创建的:

分析器(Profilers) - 图3

hprof 相比, JVisualVM 更加容易使用 —— 比如上面的截图中, 在一个地方就可以看到所有int[] 的分配信息, 所以多次在同一处代码进行分配的情况就很容易发现。

AProf

最重要的一款分析器,是由 Devexperts 开发的 AProf。 内存分配分析器 AProf 也被打包为 Java agent 的形式。

用 AProf 分析应用程序, 需要修改 JVM 启动脚本,类似这样:

  1. java -javaagent:/path-to/aprof.jar com.yourcompany.YourApplication

重启应用之后, 工作目录下会生成一个 aprof.txt 文件。此文件每分钟更新一次, 包含这样的信息:

  1. ========================================================================================================================
  2. TOTAL allocation dump for 91,289 ms (0h01m31s)
  3. Allocated 1,769,670,584 bytes in 24,868,088 objects of 425 classes in 2,127 locations
  4. ========================================================================================================================
  5. Top allocation-inducing locations with the data types allocated from them
  6. ------------------------------------------------------------------------------------------------------------------------
  7. eu.plumbr.demo.largeheap.ManyTargetsGarbageProducer.newRandomClassObject: 1,423,675,776 (80.44%) bytes in 17,113,721 (68.81%) objects (avg size 83 bytes)
  8. int[]: 711,322,976 (40.19%) bytes in 1,709,911 (6.87%) objects (avg size 416 bytes)
  9. char[]: 369,550,816 (20.88%) bytes in 5,132,759 (20.63%) objects (avg size 72 bytes)
  10. java.lang.reflect.Constructor: 136,800,000 (7.73%) bytes in 1,710,000 (6.87%) objects (avg size 80 bytes)
  11. java.lang.Object[]: 41,079,872 (2.32%) bytes in 1,710,712 (6.87%) objects (avg size 24 bytes)
  12. java.lang.String: 41,063,496 (2.32%) bytes in 1,710,979 (6.88%) objects (avg size 24 bytes)
  13. java.util.ArrayList: 41,050,680 (2.31%) bytes in 1,710,445 (6.87%) objects (avg size 24 bytes)
  14. ... cut for brevity ...

上面的输出是按照 size 进行排序的。可以看出, 80.44% 的 bytes 和 68.81% 的 objects 是在 ManyTargetsGarbageProducer.newRandomClassObject() 方法中分配的。 其中, int[] 数组占用了 40.19% 的内存, 是最大的一个。

继续往下看, 会发现 allocation traces(分配痕迹)相关的内容, 也是以 allocation size 排序的:

  1. Top allocated data types with reverse location traces
  2. ------------------------------------------------------------------------------------------------------------------------
  3. int[]: 725,306,304 (40.98%) bytes in 1,954,234 (7.85%) objects (avg size 371 bytes)
  4. eu.plumbr.demo.largeheap.ClonableClass0006.: 38,357,696 (2.16%) bytes in 92,206 (0.37%) objects (avg size 416 bytes)
  5. java.lang.reflect.Constructor.newInstance: 38,357,696 (2.16%) bytes in 92,206 (0.37%) objects (avg size 416 bytes)
  6. eu.plumbr.demo.largeheap.ManyTargetsGarbageProducer.newRandomClassObject: 38,357,280 (2.16%) bytes in 92,205 (0.37%) objects (avg size 416 bytes)
  7. java.lang.reflect.Constructor.newInstance: 416 (0.00%) bytes in 1 (0.00%) objects (avg size 416 bytes)
  8. ... cut for brevity ...

可以看到, int[] 数组的分配, 在 ClonableClass0006 构造函数中继续增大。

和其他工具一样, AProf 揭露了 分配的大小以及位置信息(allocation size and locations), 从而能够快速找到最耗内存的部分。在我们看来, AProf 是最有用的分配分析器, 因为它只专注于内存分配, 所以做得最好。 当然, 这款工具是开源免费的, 资源开销也最小。

原文链接: GC Tuning: Tooling