G1垃圾回收器实战

目录

目录.png

参数配置


堆内内存问题

六种OOM

  1. java.lang.StackOverFlowError 虚拟机栈
  2. java.lang.OutOfMemoryError:Java heap space 堆
  3. java.langOutOfMemoryError:Metaspace 元数据空间
  4. java.lang.OutOfMemoryError:Direct buffer memory 堆外
  5. java.lang.OutOfMemoryError:GC overhead limit exceeded GC回收时间长时会抛出OutOfMemoryError
  6. java.lang.OutOfMemoryError:unable to create new native thread linux系统默认允许单个进程可以创建的线程数是1024个

堆内内存,但无oom文件

  • 在grafana上面看到堆内内存上升
  • 拉取一台机器,并且生成dump文件,然后使用MAT分析。
  • MAT简介


    MAT简介.png

    MAT_dump文件分析.png
  • 在thread_override中可以看到push_data_thread有30个,占用了大部分内存


    image.png
  • 从dominator_tree中可以看出是PriceChangePullPushService中push_data_thread中HttpClient的BufferedWritter写入过多String


    image.png
  • 代码定位,这块代码只是用来测试模拟,堆内内存溢出
jvm配置: -Xms350m -Xmx350m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:MaxGCPauseMillis=200m
代码定位.png
参考文章

堆内内存有oom

  • 线上邮件收到报OOM,因为配置OOM时生成dump文件
  • 用MAT分析, 模拟100M内存,这里看占用了绝大多数


    image.png
  • 定位代码
jvm配置: -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ -XX:+PrintGCDetails -Xloggc:./gc.log

public class TestOOM implements Runnable {
    private List<String> list = new ArrayList<>();
    @Override
    public void run() {
        int i = 0;
        ++i;
        list.add(String.valueOf(i));
    }

    public void test(){
        //System.out.println("I am testoom");
    }
}

public class Test {
    private final static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 8,
            30, TimeUnit.SECONDS, new LinkedBlockingDeque<>(), r -> new Thread("test oom thread"));

    public static void main(String[] args) {
        while (true) {
            threadPoolExecutor.execute(new TestOOM());
        }

    }
}

Humous大对象分配测试

  • MAT中能看到超过1M的大对象
public class TestHumous {
    public static void main(String[] args) {
        TestHumous testHumous = new TestHumous();
        testHumous.test();
        try {
            Thread.sleep(10000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void test(){
        int size = 1024 * 1024;

        byte[] myAlloc1 = new byte[ size];
        byte[] myAlloc2 = new byte[ size];
        byte[] myAlloc3 = new byte[ size];
        byte[] myAlloc4 = new byte[ size];

        System.out.println("MyTest.main");
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


堆外内存

代码模拟堆外内存溢出

jvm option: -XX:NativeMemoryTracking=detail -XX:+UseG1GC 

// 开始先给点时间设置基准线
try {
    Thread.sleep(120000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("开始测试");
ByteBuffer buffer = ByteBuffer.allocateDirect(50 * 1024 * 1024);
try {
    Thread.sleep(300000000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

排查思路

  • 先确定下是否堆问题。如果是堆问题,再确定下堆内内存有问题,不是堆内内存大概率就是堆外
  • NMT是Java7U40引入的HotSpot新特性,配合jcmd命令我们就可以看到具体内存组成了。jvm参数需开启-XX:NativeMemoryTracking=summary,会略微耗性能。
  • 排查命令
// 先看下出问题pid,测试的话是可以直接通过jps -l看到的, 线上top看看
jps -l 
// 先设置个基准线
jcmd 96328 VM.native_memory baseline
// 过段时间看下diff
jcmd 96328 VM.native_memory summary.diff
  • native_memory日志
Native Memory Tracking:

Total: reserved=10116110KB +51219KB, committed=927226KB +51219KB

Java Heap (reserved=8388608KB, committed=524288KB)
            (mmap: reserved=8388608KB, committed=524288KB)

    Class (reserved=1068164KB, committed=16260KB)
            (classes #460 +1)
            (malloc=11396KB #236 +5)
            (mmap: reserved=1056768KB, committed=4864KB)
    Thread (reserved=25706KB, committed=25706KB)
            (thread #26)
            (stack: reserved=25600KB, committed=25600KB)
            (malloc=76KB #137)
            (arena=30KB #47)

     Code (reserved=249636KB, committed=2572KB)
            (malloc=36KB #323 +1)
            (mmap: reserved=249600KB, committed=2536KB)

        GC (reserved=319169KB, committed=293573KB)
            (malloc=12689KB #137)
            (mmap: reserved=306480KB, committed=280884KB)

  Compiler (reserved=133KB, committed=133KB)
            (malloc=2KB #25)
            (arena=131KB #7)
 // 可以看到这里加了很多,也就是代码里面的堆外内存分配
 Internal (reserved=62863KB +51201KB, committed=62863KB +51201KB)
            (malloc=62831KB +51201KB #3597 +6)
            (mmap: reserved=32KB, committed=32KB)

    Symbol (reserved=1441KB, committed=1441KB)
            (malloc=953KB #564)
            (arena=488KB #1)

Native Memory Tracking (reserved=215KB +18KB, committed=215KB +18KB)
            (malloc=111KB +14KB #1565 +197)
            (tracking overhead=104KB +3KB)

Arena Chunk (reserved=175KB, committed=175KB)
            (malloc=175KB)
//  原生内存 + 堆保留内存 = jvm 进程使用内存
  • Java Heap部分表示heap内存目前占用了7372MB;Class部分表示已经加载的classes个数为460,占用了16260KB包含metadata;Thread部分表示目前有26个
    线程,占用了25706KB;Code部分表示JIT生成的或者缓存的instructions占用了2572KB;GC部分表示目前已经占用了293573KB的内存空间用于帮助GC;
    Internal部分表示命令行解析、JVMTI(JVM Tool Interface由Java虚拟机提供的native编程接口)等占用了62863KB;Symbol部分表示诸如String table及constant pool等symbol占用了1441KB;Native Memory Tracking部分表示该功能自身占用了215KB
  • DirectByteBuffer分配内存的话,是需要full GC或者手动system.gc来进行回收的(所以最好不要使用-XX:+DisableExplicitGC)。 之前项目是CMS回收器时分析过,-XX:+DisableExplicitGC导致堆外内存很高。这里链接里面是先用上述分析,方法再jstack 抓,才解决: CMS堆外内存泄露案例1

拓展

  • 除堆内内存和方法区,这些地方也要注意
    1.Direct Memory:可以通过-XX:MaxDirectMemorySize调整大小,内存不足时抛出OutOfMemoryError或OutOfMemoryError:Direct buffer memory。注意有时候堆外内存太小也会有问题。
    2.线程堆栈:可通过-Xss调整大小内存不足时抛出StackoverflowErroe(纵向无法分配,即无法分配新的栈帧)或OutOfMemoryError:unable to create new native thread(横向无法分配,即无法建立新的线程)。
    3.Socket缓存区:每个Socket连接都Receive和Send两个缓存区,分别占大约37KB和25KB的内存,连接多的话这块内存占用也比较可观。如果无法分配,则可能会抛出IOException:Too many open files异常。
    4.JNI代码:如果代码中使用JNI调用本地库,那么本地库使用内存也不在堆中
    5.虚拟机和GC:虚拟机和GC的代码执行也要消耗一定的内存。

参考文章


metaspace引发full gc

代码模拟metaspae溢出

jvm option:  -XX:+UseG1GC -Xms1000M -Xmx1000M -XX:+UseG1GC -XX:+PrintGCDetails 
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=./ -XX:+PrintGCDateStamps -XX:MaxGCPauseMillis=200m
// 注意这个,如果改成非0比如7000这种数值,就不会有以下说的大量加载卸载恩恩现象
-XX:SoftRefLRUPolicyMSPerMB=0 
-XX:MaxMetaspaceSize=10m -XX:+TraceClassLoading -XX:+TraceClassUnloading

public class TestMetaSpaceOverFlow {
    static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3000, 3000, 30, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
    public static void main(String[] args) throws Exception {
        Thread.sleep(1000);
        System.out.println("+++++++++");
        // 模拟日常gc
        new Thread(() -> {
            while (true) {
                System.gc();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.gc();
            }
        }).start();
        // 如果反射用到了软引用则因为并发问题会生成及少量的GeneratedMethodAccessor***, 但是如果
        // 不用软引用,则会有可能生成超大量的GeneratedMethodAccessor**对应很多个类加载器DelegatingClassLoader
        // 有可能造成方法区内存溢出
        for(int i = 0; i < 2000000; i++) {
            threadPoolExecutor.execute(() -> {
                try {
                    Class<?> clazz1 = Class.forName("TestOOM");
                    TestOOM testOOM1 = (TestOOM) clazz1.getDeclaredConstructor().newInstance();
                    Method method1 = clazz1.getDeclaredMethod("test");
                    method1.invoke(testOOM1);
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        Thread.sleep(1000000000);
    }
}

高并发场景下重现

用的jdk1.8_201和G1回收器。Metaspace当时调的是256Mb, 邮件收到OOM: Error Metaspace。遇到这个问题后先把Metaspace调整成500Mb,但是还有上升趋势。加了-XX:+TraceClassLoading -XX:+TraceClassUnloading后发现catalina.out文件有很多反射类加载卸载,公司RPC大量用到反射。查看jvm参数发现-XX:SoftRefLRUPolicyMSPerMB=0(不知道谁设置的,得问问) ,这个是罪魁祸首,后面改成-XX:SoftRefLRUPolicyMSPerMB=1000。metaspace就正常了,也没这么多类加载卸载了。

[Loaded sun.reflect.GeneratedMethodAccessor16 from __JVM_DefineClass__]
[UnLoaded sun.reflect.GeneratedMethodAccessor16 from __JVM_DefineClass__]

拓展

反射
  • Metaspace内存空间如何泄漏: Metaspace空间泄露,其实就是ClassLoader内存空间泄露。
    Metaspace由: klass, 是我们熟知的class文件在jvm里的运行时数据结构; NoKlass Metaspace专门来存klass相关的其他的内容,比如method,constantPool等,这块内存是由多块内存组合起来的,所以可以认为是不连续的内存块组成的; Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以类加载器们要分配内存,但是每个类加载器都有一个SpaceManager,来管理属于这个类加载的内存小块;
  • 动态代理类生成和反射调用,这些都会导致ClassLoader空间出现泄露。在反射调用重灾区的项目中可能会存在很多的DelegatingClassLoader对象没有及时回收,对应加载的类也没 有及时卸载,当并发量大的时候可能导致Metaspace空间不足从而引发FullGC。
  • 反射原理: 反射原理
  // 反射用到了软引用则因为并发问题会生成及少量的GeneratedMethodAccessor***, 但是如果
  // 不用软引用,则会有可能生成超大量的GeneratedMethodAccessor**对应很多个类加载器DelegatingClassLoader
 // 有可能造成方法区内存溢出
 Class<?> clazz1 = Class.forName("TestOOM");
 TestOOM testOOM1 = (TestOOM) clazz1.getDeclaredConstructor().newInstance();
  Method method1 = clazz1.getDeclaredMethod("test");
  method1.invoke(testOOM1);
软引用
  • 软引用回收时机会:
clock - timestamp > free_heap*ms_per_mb 则被回收
clock: 最后一次GC时间
timestamp: 使用软引用后最后一次GC时间
free: 空间内存/mb
ms_per_mb: -XX:SoftRefLRUPolicyMSPerMB的值
  • 测试软引用, 如果测试软引用不过期则改变XX:SoftRefLRUPolicyMSPerMB值即可
jvm option: -Xmx20M -XX:+PrintGCDetails -verbose:gc -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+UseG1GC -XX:MaxMetaspaceSize=10m
private static final int _4MB= 30*512*1024;
public static void main(String[] args) throws IOException, InterruptedException {
    soft();
}
public static void soft() throws InterruptedException {
    SoftReference<byte[]> reference = new SoftReference<>(new byte[_4MB]);
    System.out.println("=======" + reference.get());
    // 这次gc更新了timestamp的值
    System.gc();
    Thread.sleep(3000);
    // 这次gc更新了clock的值
    System.gc();
    Thread.sleep(3000);
    // clock - timestamp 的值 > freeSpace * SoftRefLRUPolicyMSPerMB=0, 所以被回收了
    // 改变SoftRefLRUPolicyMSPerMB的值就有可能不变回收左边表达式小于右边就不回收
    System.out.println("+++++++++" + reference.get());
}

参考文献


老年代不足引发fullgc


cpu问题

  • top查看资源占用pid
  • top -H -p pid 定位到线程


    image.png
  • jstack <进程号> | grep <tid 16进制(上图中的pid 比如14293的16进制)>
  • 结合第三点第二点,查到对应堆栈,找到对应代码
    ps arthas也可以用作排查工具

参考文章


磁盘问题


参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354