前言
最近看到了这样的一个问题,在 perfma 的论坛上面
原问题的帖子是在
然后 我也吧这个测试用例复制下来了, 跑了一下, 看了看, 这里整理下一些 相关的收获
以下的相关代码截图 基于 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() 开始查找
业务代码, 以及依赖的代码这边 应该是从 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
运行时结果如下
使用 hsdb 查看当前进程的信息
从上面可以看出, 这部分大的 int[] 的确是来自于 collectedHeap.fill_with_array
并且有一部分小的得 int[] 也是来自于 collectedHeap.fill_with_array
collectedHeap.fill_with_array 产生的小的 int[]
在下图的地方打上断点, 断点 hit
可以看出的是 当前是正在分配某一个类型的数组, 尝试从 tlab 中分配, 那么为什么又会 创建 int[] 呢?
切换一下栈帧, 来到 CollectedHeap.allocate_from_tlab_slow
可以看到 之所以 需要创建int[] 的原因是, 当前线程目前的 tlab 的空间已经不够了, 所以 需要吧当前线程的 tlab 的剩余的空间填充一个 dummy 的 int[], 最终会被 gc 掉
然后 后面走的是 新建一块 tlab 的空间, 然后再 尝试业务上面的内存分配
collectedHeap.fill_with_array 产生的大的 int[]
接下来我们再来看一下 这部分的大的 int[]
从栈帧信息中可以看出是在 线程退出的时候, 创建的 int[], 那么为什么 需要创建这个 int[] 呢?
切换一下栈帧来到 JavaThread.exit
在线程退出的时候, 需要将 当前 tlab 剩余的空间填充一个 int[], 注释这里也将这么做的结果阐述的很清楚
jmap 的时候产生的 int[]
上面 你假笨 提到了 jmap 的时候, 也会触发 collectedHeap.fill_with_array 的调用
接下来我们试试, 如下命令, 触发 jmap
jmap -dump:format=b,file=/root/Desktop/openJdk8/VM/logs/javapid_59049.hprof 59049
断点信息如下图
我们吧栈帧切换到 VM_HeapDumper.doit 上面
在执行业务之前, 需要把 各个线程的 tlab 剩余的部分使用 int[] 来填充, 便于这种遍历整个堆的业务的执行
其他的填充 int[] 的场景
1. 从 collectedHeap.fill_with_object 作为出发点来看
2. 从 Universe::intArrayKlassObj 往外面找
3. 从 CollectedHeap::array_allocate 往外面找
大量的 int[0] 来自于哪里
从上面的问题, 我衍生发现了另外的一个问题, 从 hsdb 上面可以看到 大量的 int[0]
那么这些对象来自于哪里呢?
总共有 553 个 int[]
统计了一下 : 差不多是有 502 个 int[0], 51 个有长度的 int[]
Universe::intArrayKlassObj 的初始化
类加载的时候创建的 int[0]
类加载的时候也会创建一个 int[0], 作为锁
为 java.lang.Object.class 创建的 int[0]
类的数量 : 499 个
当前 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] 来创建
由于精力有限, 其他的情况就暂时不在深究下去了
完