基于Mac 和 JDK 1.8

统计 String 对象相关信息

# 导出当前内存快照
jmap -dump:format=b,file=/tmp/java-app-${PID}.hprof ${PID}

然后用 MAT 打开分析

/Applications/mat.app/Contents/MacOS/MemoryAnalyzer -vm /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/bin/java -vmargs -Xmx8g -XX:-UseGCOverheadLimit

然后打开 Histogram 再按如下操作, 选 Group by Value

image-20200602181321328

然后得到类似如下的结果:

image-20200602182010047

想要查看哪些对象持有它, 可以在某个String 对象上, 右键选择

image-20200602182907526

关于 incomming 和 outgoing , 可以这样子理解:

Incomming -> 生成(持有)该对象 --> outgoing

intern 方法

它的方法签名为

public native String intern();

可知, 它是一个 native 方法. 调用 native 方法是很耗时的.

它的作用是, 池化该字符串对象. (即相同的字符串, 在内存中, 只有一份占用空间)

    @Test
    public void testIntern() throws InterruptedException {
        String a = "hello";
        String b = new String("hello");
        System.out.println(a == b);
        String c = b.intern();
        System.out.println(a == c);
    }

上面的代码在 Java 8 中输出为 false true

什么时候才考虑用 intern 呢? 长期存活的对象 并且有 大量重复的字符串 并且不太注重性能, 这样子就可以节省大量的内存空间. 但要注意该方法的性能是比较低的

与 disruptor 相关

线上的 DSP 广告系统, 通过 disruptor 来进行竞价日志队列处理. 由于没添加相关的清除对象的 handler , 导致整个 JVM 周期中持有大量的 String 对象(如上图). 这点要特别注意.

它导致 JVM 的 old gen 在增长, 然后触发 full gc 的频率大.

我的解决方案是, 添加了个 clearHandler , 在该 handler 中清除对象. 可参考另一篇文章 /2019/10/12/disruptor学习/#清除对象-1