前言

最近看到了这样的一个问题,在 perfma 的论坛上面  

不起眼,但是足以让你有收获的JVM内存分析案例

原问题的帖子是在 

为什么内存中存在很多代码无关的int[]数组?

然后 我也吧这个测试用例复制下来了, 跑了一下, 看了看, 这里整理下一些 相关的收获 

以下的相关代码截图 基于 jdk8

测试代码

测试代码来自于, 问题原帖子, 这里做了一些小的调整 

package com.hx.test11;

/**
 * Test30BigIntArray
 *
 * @author Jerry.X.He 
 * @version 1.0
 * @date 2020/8/12 21:58
 */
public class Test30BigIntArray {

    // identStr
    private String identStr = "identStr";
    // mCache
    final static List<Method> mCache = new Vector<Method>();

    public static void main(String[] args) throws Throwable {
        final File f = new File(".\\target\\classes");
        int i = 0;
        while (true) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        ClassLoader1 classLoader1 = new ClassLoader1(new URL[]{f.toURI().toURL()});
                        ClassLoader2 classLoader2 = new ClassLoader2(new URL[]{f.toURI().toURL()});
                        ClassLoader3 classLoader3 = new ClassLoader3(new URL[]{f.toURI().toURL()});
                        final Class<?> model1Clz = classLoader1.loadClass("com.hx.test11.Test30BigIntArray$TbmkModel1");
                        final Class<?> model2Clz = classLoader2.loadClass("com.hx.test11.Test30BigIntArray$TbmkModel2");
                        final Class<?> model3Clz = classLoader3.loadClass("com.hx.test11.Test30BigIntArray$TbmkModel3");
                        final TbmkModel1 model1 = new TbmkModel1();
                        final TbmkModel2 model2 = new TbmkModel2();
                        final TbmkModel3 model3 = new TbmkModel3();

                        for (int i = 0; i < 1000; ++i) {
                            int methodIdx = i % 10;
                            Method m = model1Clz.getMethod("method" + methodIdx);
                            m.invoke(model1);
                            mCache.add(m);
                            m = model2Clz.getMethod("method" + methodIdx);
                            m.invoke(model2);
                            mCache.add(m);
                            m = model3Clz.getMethod("method" + methodIdx);
                            m.invoke(model3);
                            mCache.add(m);
                        }
                        System.out.println("mCache size: " + mCache.size());
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            ).start();

            Thread.sleep(10);
            i++;
            System.out.println(" i = " + i);
            if (i == 3) {
                Thread.sleep(2000_000);
            }
        }
    }

    /**
     * ClassLoader1
     *
     * @author Jerry.X.He <970655147@qq.com>
     * @version 1.0
     * @date 2020/8/12 22:00
     */
    static class ClassLoader1 extends URLClassLoader {
        public ClassLoader1(URL[] urls) {
            super(urls);
        }
    }

    static class ClassLoader2 extends URLClassLoader {
        public ClassLoader2(URL[] urls) {
            super(urls);
        }
    }

    static class ClassLoader3 extends URLClassLoader {
        public ClassLoader3(URL[] urls) {
            super(urls);
        }
    }

    /**
     * TbmkModel1
     *
     * @author Jerry.X.He <970655147@qq.com>
     * @version 1.0
     * @date 2020/8/12 22:02
     */
    static class TbmkModel1 {
        public void method0() {

        }

        public void method1() {

        }

        public void method2() {

        }

        public void method3() {

        }

        public void method4() {

        }

        public void method5() {

        }

        public void method6() {

        }

        public void method7() {

        }

        public void method8() {

        }

        public void method9() {

        }
    }

    static class TbmkModel2 {
        public void method0() {

        }

        public void method1() {

        }

        public void method2() {

        }

        public void method3() {

        }

        public void method4() {

        }

        public void method5() {

        }

        public void method6() {

        }

        public void method7() {

        }

        public void method8() {

        }

        public void method9() {

        }
    }

    static class TbmkModel3 {
        public void method0() {

        }

        public void method1() {

        }

        public void method2() {

        }

        public void method3() {

        }

        public void method4() {

        }

        public void method5() {

        }

        public void method6() {

        }

        public void method7() {

        }

        public void method8() {

        }

        public void method9() {

        }
    }


}

问题的调试

本文的调试时站在前人的肩膀上面进行的思考, 感谢 你假笨 大佬 

因此本文的主要出发点就是从 CollectedHeap::fill_with_array 开始 

当然 要是问题的原点来找的话, 应该是 vm层面从 Universe::intArrayKlassObj() 开始查找 

39 很多代码无关的int[]来自于哪里_ide

业务代码, 以及依赖的代码这边 应该是从 CollectedHeap::array_allocate 来找吧

呵呵 不过我们这里 暂时只关心问题中提到的这些大的 int[] 

为了更方便的验证这个问题, 我对 collectedHeap.fill_with_array 做了少量的调整, 主要是增加了一些日志输出, 是如下面的代码中的 tty->print_cr 的上下三行 

collectedHeap.cpp 

void
CollectedHeap::fill_with_array(HeapWord* start, size_t words, bool zap)
{
  assert(words >= filler_array_min_size(), "too small for an array");
  assert(words <= filler_array_max_size(), "too big for a single object");

  const size_t payload_size = words - filler_array_hdr_size();
  const size_t len = payload_size * HeapWordSize / sizeof(jint);
  assert((int)len >= 0, err_msg("size too large " SIZE_FORMAT " becomes %d", words, (int)len));

  // Set the length first for concurrent GC.
  ((arrayOop)start)->set_length((int)len);
  if(Thread::current()->is_Java_thread()) {
      tty->print_cr(" thread: %s, start %p, len : %d ", Thread::current()->name(), start, (int) len);
  }
  post_allocation_setup_common(Universe::intArrayKlassObj(), start);
  DEBUG_ONLY(zap_filler_array(start, words, zap);)
}

vm 参数如下 

-Xmx128m

运行时结果如下 

39 很多代码无关的int[]来自于哪里_数组_02

使用 hsdb 查看当前进程的信息  

39 很多代码无关的int[]来自于哪里_d3_03

从上面可以看出, 这部分大的 int[] 的确是来自于 collectedHeap.fill_with_array 

并且有一部分小的得 int[] 也是来自于 collectedHeap.fill_with_array 

collectedHeap.fill_with_array 产生的小的 int[] 

在下图的地方打上断点, 断点 hit 

可以看出的是 当前是正在分配某一个类型的数组, 尝试从 tlab 中分配, 那么为什么又会 创建 int[] 呢? 

39 很多代码无关的int[]来自于哪里_ide_04

切换一下栈帧, 来到 CollectedHeap.allocate_from_tlab_slow 

39 很多代码无关的int[]来自于哪里_数组_05

可以看到 之所以 需要创建int[] 的原因是, 当前线程目前的 tlab 的空间已经不够了, 所以 需要吧当前线程的 tlab 的剩余的空间填充一个 dummy 的 int[], 最终会被 gc 掉 

然后 后面走的是 新建一块 tlab 的空间, 然后再 尝试业务上面的内存分配 

collectedHeap.fill_with_array 产生的大的 int[] 

接下来我们再来看一下 这部分的大的 int[] 

从栈帧信息中可以看出是在 线程退出的时候, 创建的 int[], 那么为什么 需要创建这个 int[] 呢? 

39 很多代码无关的int[]来自于哪里_数组_06

切换一下栈帧来到 JavaThread.exit 

在线程退出的时候, 需要将 当前 tlab 剩余的空间填充一个 int[], 注释这里也将这么做的结果阐述的很清楚 

39 很多代码无关的int[]来自于哪里_数组_07

jmap 的时候产生的 int[] 

上面 你假笨 提到了 jmap 的时候, 也会触发 collectedHeap.fill_with_array 的调用 

接下来我们试试, 如下命令, 触发 jmap 

jmap -dump:format=b,file=/root/Desktop/openJdk8/VM/logs/javapid_59049.hprof 59049

断点信息如下图 

39 很多代码无关的int[]来自于哪里_ide_08

我们吧栈帧切换到 VM_HeapDumper.doit 上面 

在执行业务之前, 需要把 各个线程的 tlab 剩余的部分使用 int[] 来填充, 便于这种遍历整个堆的业务的执行 

39 很多代码无关的int[]来自于哪里_数组_09

其他的填充 int[] 的场景 

1. 从 collectedHeap.fill_with_object 作为出发点来看 

39 很多代码无关的int[]来自于哪里_ide_10

2. 从 Universe::intArrayKlassObj 往外面找 

39 很多代码无关的int[]来自于哪里_ide

3. 从 CollectedHeap::array_allocate 往外面找 

大量的 int[0] 来自于哪里

从上面的问题, 我衍生发现了另外的一个问题, 从 hsdb 上面可以看到 大量的 int[0] 

那么这些对象来自于哪里呢? 

39 很多代码无关的int[]来自于哪里_数组_12

总共有 553 个 int[] 

39 很多代码无关的int[]来自于哪里_ide_13

统计了一下 : 差不多是有 502 个 int[0], 51 个有长度的 int[]

Universe::intArrayKlassObj 的初始化 

39 很多代码无关的int[]来自于哪里_ide_14

类加载的时候创建的 int[0]

类加载的时候也会创建一个 int[0], 作为锁 

39 很多代码无关的int[]来自于哪里_ide_15

为 java.lang.Object.class 创建的 int[0]

39 很多代码无关的int[]来自于哪里_数组_16

类的数量 : 499 个

39 很多代码无关的int[]来自于哪里_数组_17

当前 gc 的次数 : 0次

root@ubuntu:~# jstat -gcutil 59427
  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  32.01   0.00      �      0    0.000     0    0.000    0.000

其他地方创建的 int[0]

业务代码可以通过 new int[0] 来创建 

由于精力有限, 其他的情况就暂时不在深究下去了 

完 

参考

为什么内存中存在很多代码无关的int[]数组?

不起眼,但是足以让你有收获的JVM内存分析案例