• JVM学习八


    OOM常见案例汇总

    OOM产生的原因多种多样,有些程序未必产生OOM,不断FGC(CPU飙高,但内存回收特别少) (上面案例)

    1. 硬件升级系统反而卡顿的问题(见上)【换垃圾回收期】

    2. 线程池不当运用产生OOM问题(见上)
      不断的往List里加对象(实在太LOW)

    3. jira问题
      真正问题在哪儿?不知道,实际系统不断重启
      解决方案: 加内存 + 更换垃圾回收器 G1

    4. tomcat http-header-size过大问题
      在这里插入图片描述
      在这里插入图片描述
      发现每个Http11OutputBuffer都占用很大的内存,当请求过多时,设置堆内存太小,会导致OOM问题。
      在这里插入图片描述

    5. lamda表达式导致方法区溢出问题:
      产生的原因为class逐渐增多了:

    public class LambdaGC {
        public static void main(String[] args) {
            for(;;) {
                I i = C::n;
            }
        }
    
        public static interface I {
            void m();
        }
    
        public static class C {
            static void n() {
                System.out.println("hello");
            }
        }
    }
    
    
    1. 直接内存溢出问题
      使用Unsafe分配直接内存,或者使用NIO的问题

    2. 栈溢出问题(StackOverflowError)
      方法之间调用深度特别深【产生的栈帧大】 -Xss设定太小

    
    public class StackOverFlow {
        public static void main(String[] args) {
            m();
        }
    
        static void m() {
            m();
        }
    }
    

    相较于来说第一种写法更优,第二种写法,如果循环没有结束,对象
    会不断产生,并且循环结束前不会进行垃圾回收。
    在这里插入图片描述
    9.重写finalize引发频繁GC
    小米云,HBase同步系统,系统通过nginx访问超时报警,最后排查,C++程序员重写finalize引发频繁GC问题。
    C++程序员重写finalize的目的是自己释放内存,调用析构函数(new delete【语句】 ),就理所当然认为java也有类似的析构函数,造成频繁GC的原因是因为重写了finalize耗时比较长(200ms),如果一下子要回收很多对象的话则会造成频繁FullGC。

    10.如果有一个系统,内存消耗一直不超过10%,但是观察GC日志,发现FGC总是频繁产生,会是什么引起的?
    有人频繁调用system.gc()

    11.new 大量线程,会产生 native thread OOM,(low)应该用线程池,
    解决方案:减少堆空间,预留更多内存产生native thread,当然采用线程池更好。
    JVM内存占物理内存比例 50% - 80%

    CMS原理

    CMS垃圾回收器在发展历史上是承上启下的。
    其是垃圾回收线程和生产线程一起工作:
    但是会造成内存碎片化,并且还有浮动垃圾不断产生,
    1、初始标记
    2、并发标记
    3、重新标记
    4、并发回收
    在这里插入图片描述
    初始标记:
    初始标记是找到所有GC root以及直接相关联的对象 但是引用链上其他对象不会标记,要等到并发标记,而并发标记产生的垃圾,重新标记其实是并发标记的脏卡重新清理标记
    通过GCRoots找到根对象,这是STW的:
    在这里插入图片描述
    即便是STW的,但是它的根对象相对来说比较少,所以耗时不会太长。
    并发标记:
    在上一步找到根对象之后,并发顺着原来的根对象继续往下找,可以回收的对象。

    重新标记:
    找到标记了要回收的对象,但是在并发标记的过程中,有重新引用的对象,给它在并发清理中回收掉:
    在这里插入图片描述

    从线程角度理解:在这里插入图片描述

    如上在并发清理的时候工作线程运行可能会产生浮动垃圾,并且也是采用标记清除算法,所以也会产生很多内存碎片。

    G1特点、

    参考链接:
    https://www.oracle.com/technicalresources/articles/java/g1gc.html

    在这里插入图片描述
    通过并发或者并行的手段,降低垃圾回收暂停时间并且维持不错的吞吐量。
    G1与PS相比它的吞吐量降低了10%-15%的样子但是G1的响应时间只有差不多200ms,所以看程序不管怎样200ms都有响应用G1,如果你的程序追求吞吐量,那么用PS更好。
    原理:
    在这里插入图片描述
    humongous:是放大对象的内存区域
    特点:
    1、并发收集
    2、压缩空间不会延长GC的暂停时间
    3、更易预测GC暂停时间
    4、适用不需要实现很高的吞吐量的场景
    在这里插入图片描述
    G1的内存区域不是固定的E或者O,非常的灵活。

    基本概念

    分为年轻代和老年代,但是如何去判断哪些对象是老年代呢?如下红色的部分是根对象,这个根对象指向了老年代里面的
    一个对象,这个老年代的对象又指向了年轻代的要给对象,怎么确定年轻代的对象是活的呢?
    一般的思路是遍历所有老年代的对象,找到老年代里面的对象有没有指向年轻代活着的对象。【效率极低】
    所以JVM内部是把内存区域分成多个card,具体的对象存在card里面,在程序的演化
    过程中,如果某个card里面的对象指向了年轻代,那么就会把card标记为dirty,然后会用
    一个位图来代表哪些card是dirty的。这样就在扫描时就只需要扫描Dirty Card就行了。

    在这里插入图片描述

    Cset = Collection Set
    一组可被回收的分区的集合。
    在CSet中存活的数据会在GC过程中被移动到另一个可用的分区,CSet
    中的分区可以来自eden空间、survivor空间、或者老年代,Cset会占用
    不到整个堆空间的1%大小。

    每一个region里面都有一个Hashset记录其他region到当前region的引用,帮助垃圾回收
    使用的。
    在这里插入图片描述
    Rset与赋值的效率
    由于Rset的存在,那么每次给对象赋引用的时候,就得做一些额外的操作,指的是Rset中
    做一些额外的记录(在GC中被称为写屏障)
    这个写屏障 不等于内存屏障,是GC专有的写屏障

    G1新老年代比例【动态调整Y区及Old区大小】:
    如果某个区慢了的话则动态减少那个区的大小,
    在这里插入图片描述
    在这里插入图片描述
    注意:G1在对象分配不下的时候也会产生FGC。
    G1产生FGC应该做什么?

    1、扩内存
    2、提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)
    3、降低MixedGC触发的阈值,让MixedGC提早发生(默认时45%)
    G1中的MixedGC:
    相当于CMS,当Old区超过默认值,整体占用堆内存空间超过45%
    在这里插入图片描述
    在这里插入图片描述
    Java10以前是串行的FullGC,10以后是并行的FullGC

    并发标记算法(CMS G1共有)

    在这里插入图片描述

    在这里插入图片描述
    黑色是,它本身自己被标记了,并且成员变量引用的对象也被标记了。
    在这里插入图片描述
    漏标产生条件:
    黑色对象的引用指向了白色对象,与此同时灰色对象指向白色对象的引用没有了,
    在这里插入图片描述
    解决方案:跟踪A指向D让它重新扫描,或者说当B指向D消失了,则重新扫描,这两种方式的算法:
    第一种方式黑色指向了白色之后将黑色重新标记为灰色(CMS)
    第二种方式则是将引用推入到GC的堆栈,重新扫描(G1)
    在这里插入图片描述
    为什么CMS采用第一种方案,为什么G1采用第二种方案?
    CMS第一种方案,会重新扫描所有成员,但是它的成员已经被扫描了,会影响效率。
    SATB则做一个快照,将改变的引用给重新扫描一遍【扫RSet】看有没有其他引用指向那个白色的块就好了。
    灰色 → 白色 引用消失时,如果没有黑色指向白色引用会被push到堆栈下次扫描时拿到这个引用,由于有RSet的存在,不需要扫描整个堆去查找指向白色的引用,效率比较高SATB 配合 RSet ,浑然天成

    CMS日志分析

    UseConcMarkSweepGC 使用cms垃圾回收器在老年代,年轻代使用的是ParNew
    在这里插入图片描述
    在这里插入图片描述
    可以看到开始为ParNew执行年轻代的垃圾回收,后面再通过CMS执行老年代的垃圾回收。
    年轻代回收日志:
    在这里插入图片描述
    CMS回收日志:
    注意initial Mark 和 final remark 都是STW的
    在这里插入图片描述
    注意CMS产生在老年代使用了8.5M的时候,CMS的产生情况可以根据一个参数指定,
    -XX:CMSinitiatingOccupancyFraction:使用多少比例的老年代后开始CMS收集,默认是68%(近似值)
    如果此参数设置过大容易引起FGC【年轻代对象往老年代移】【这样也会造成Serial Old单线程执行
    ,导致整个系统卡顿】,如果参数设置过小则会频繁CMS回收。

    G1日志分析

    G1有三个过程,YGC, MixedGC,FGC
    G1是分为各个region来回收的,有YGC【是STW的,不推荐指定-Xmn年轻代大小,
    G1会根据YGC的暂停时间动态调整】
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    GC常用参数

    在这里插入图片描述

    在这里插入图片描述

    Parallel垃圾回收器常用参数

    -XX:SurvivorRatio
    它定义了新生代中Eden区域和Survivor区域(From幸存区或To幸存区)的比例,默认为8,也就是说Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10
    参考公式:

    Eden = (R*Y)/(R+1+1)
    From = Y/(R+1+1)
    To   = Y/(R+1+1)
    

    在这里插入图片描述

    CMS垃圾回收器常用参数

    在这里插入图片描述

    G1常用参数

    在这里插入图片描述

    纤程

    如下图程序到进程到线程到纤程是逐步调优的一个过程
    在这里插入图片描述
    一个QQ.exe跑起来分为不同的进程,然后再划分为线程,最后再到纤程。

    线程和纤程的区别一个是通过内核空间【启动线程需要内核调用,消耗资源较多
    ,线程级别的并发就是比较重量级的】一个是不通过内核空间【每个纤程对应一个
    纤程栈并且纤程间的切换在用户空间】,还有一个区别是操作系统不支持启动很多线程的,会花大量时间
    在线程的切换上,但是纤程可以启动很多

    引入【go,python常使用】

        <dependency>
                <groupId>co.paralleluniverse</groupId>
                <artifactId>quasar-core</artifactId>
                <version>0.7.6</version>
            </dependency>
    

    先看线程计算:

    public class HelloFiber {
    
        public static void main(String[] args) throws Exception {
            long start = System.currentTimeMillis();
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    calc();
                }
            };
    
            int size = 10000;
    
            Thread[] threads = new Thread[size];
            for (int i = 0; i < threads.length; i++) {
                threads[i] = new Thread(r);
            }
    
            for (int i = 0; i < threads.length; i++) {
                threads[i].start();
            }
    
            for (int i = 0; i < threads.length; i++) {
                threads[i].join();
            }
    
            long end = System.currentTimeMillis();
            System.out.println(end - start);
    
    
        }
    
        static void calc() {
            int result = 0;
            for (int m = 0; m < 10000; m++) {
                for (int i = 0; i < 200; i++) result += i;
    
            }
        }
    }
    
    

    在这里插入图片描述
    再看纤程:

    public class HelloFiber2 {
        @SuppressWarnings("")
        public static void main(String[] args) throws Exception {
            long start = System.currentTimeMillis();
    
    
            int size = 10000;
    
            Fiber<Void>[] fibers = new Fiber[size];
    
            for (int i = 0; i < fibers.length; i++) {
                fibers[i] = new Fiber<Void>(new SuspendableRunnable() {
                    public void run() throws SuspendExecution, InterruptedException {
                        calc();
                    }
                });
            }
    
            for (int i = 0; i < fibers.length; i++) {
                fibers[i].start();
            }
    
            for (int i = 0; i < fibers.length; i++) {
                fibers[i].join();
            }
    
            long end = System.currentTimeMillis();
            System.out.println(end - start);
    
    
        }
    
        static void calc() {
            int result = 0;
            for (int m = 0; m < 10000; m++) {
                for (int i = 0; i < 200; i++) result += i;
    
            }
        }
    }
    

    在这里插入图片描述
    快了差不多十倍的样子。

  • 相关阅读:
    R语言ggplot2可视化条形图:通过双色渐变配色颜色主题可视化条形图、为每个条形添加标签文本(geom_text函数)
    postgresql-通用表达式
    Prometheus-PushGateway自定义监控项
    CTFHub-Web-密码口令-弱口令
    Linux系统信息收集
    redis雪崩、穿透、击穿
    iphone无线调试(通过wifi无线来调试真机)
    [附源码]计算机毕业设计万佳商城管理系统Springboot程序
    73个产品小白必备知识,项目管理也可看
    Subset Selection
  • 原文地址:https://blog.csdn.net/lsdstone/article/details/127028838