精华内容
下载资源
问答
  • 内存抖动和内存泄漏 内存大户,Bitmap内存优化 Profile内存检测工具 Mat大对象与泄漏检测 【内存抖动和内存泄漏】 Out Of Memory(内存溢出) 翻译中文就是内存用完了,当JVM因为没有足够的内存来为对象分配空间...

    大纲

    • 内存抖动和内存泄漏
    • 内存大户,Bitmap内存优化
    • Profile内存检测工具
    • Mat大对象与泄漏检测

    【内存抖动和内存泄漏】

    Out Of Memory(内存溢出)

    翻译中文就是内存用完了,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error,此时称之为溢出。(注:非exception,因为这个问题已经严重到不足以被应用处理)。

    为什么会OOM?

    1. 分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。
    2. 应用用的太多:并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。

    在Java语言中,由于存在了垃圾自动回收机制,所以,我们一般不用去主动释放不用的对象所占的内存,也就是理论上来说,是不会存在“内存泄露”的。但是,如果编码不当,比如,将某个对象的引用放到了全局的Map中,虽然方法结束了,但是由于垃圾回收器会根据对象的引用情况来回收内存,导致该对象不能被及时的回收。如果该种情况出现次数多了,就会导致内存溢出,比如系统中经常使用的缓存机制。Java中的内存泄露,不同于C++中的忘了delete,往往是逻辑上的原因泄露。

    OOM的类型?

    JVM内存模型?

    按照JVM规范,JAVA虚拟机在运行时会管理以下的内存区域:

    • 程序计数器:当前线程执行的字节码的行号指示器,线程私有
    • JAVA虚拟机栈:Java方法执行的内存模型,每个Java方法的执行对应着一个栈帧的进栈和出栈的操作。
    • 本地方法栈:类似“ JAVA虚拟机栈 ”,但是为native方法的运行提供内存环境。
    • JAVA堆:对象内存分配的地方,内存垃圾回收的主要区域,所有线程共享。可分为新生代,老生代。
    • 方法区:用于存储已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Hotspot中的“永久代”。
    • 运行时常量池:方法区的一部分,存储常量信息,如各种字面量、符号引用等。
    • 直接内存:并不是JVM运行时数据区的一部分, 可直接访问的内存, 比如NIO会用到这部分。
      按照JVM规范,除了程序计数器不会抛出OOM外,其他各个内存区域都可能会抛出OOM。

    最常见的OOM情况有以下三种:

    • java.lang.OutOfMemoryError: Java heap space
      java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
    • java.lang.OutOfMemoryError: PermGen space
      java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。
    • java.lang.StackOverflowError
      不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。

    内存抖动

    短时间内大量的对象被创建,导致可用内存不足,从而引起频繁gc回收对象,这种已用内存忽高忽低的现象就叫内存抖动。由于gc的过程会 “stop the world” 停止其他的一切工作,gc太频繁无疑会造成界面卡顿,而且gc回收后可能会产生内存碎片,如果这时其他线程需要申请大块内存还有可能发生OOM,所以内存抖动的情况必须要避免。

    什么情况会出现内存抖动呢?

    img

    for循环内使用了+进行字符串拼接

    这是很常见的字符串拼接操作,通过查看字节码文件可以看出+的拼接字符串实际上是创建StringBuilder对象进行拼接,这样就会出现大量创建对象频繁GC,导致内存抖动。

    内存抖动一定是锯齿状吗?

    看下面一个实例,实现IOS小菊花的功能

    public class IOSStyleLoadingView extends View {
    
        private Context mContext;
        private float mStrokeWidth;
        private float northwestXStart = 264.57f;
        private float northwestYStart = 264.71f;
        private float northwestXEnd = 193.72f;
        private float northwestYEnd = 194.14f;
    
        private float northXStart = 300;
        private float northYStart = 250;
        private float northXEnd = 300;
        private float northYEnd = 150;
    
        private float notheastXStart = 335.25f;
        private float notheastYStart = 264.54f;
        private float notheastXEnd = 405.76f;
        private float notheastYEnd = 193.63f;
    
        private float eastXStart = 350;
        private float eastYStart = 300f;
        private float eastXEnd = 450;
        private float eastYEnd = 300;
    
        private float southeastXStart = 335.36f;
        private float southeastYStart = 335.34f;
        private float southeastXEnd = 406.10f;
        private float southeastYEnd = 406.02f;
    
        private float southXStart = 300.03f;
        private float southYStart = 345f;
        private float southXEnd = 300;
        private float southYEnd = 450;
    
        private float southwestXStart = 264.68f;
        private float southwestYStart = 335.39f;
        private float southwestXEnd = 194.06f;
        private float southwestYEnd = 406.19f;
    
        private float westXStart = 250;
        private float westYStart = 300;
        private float westXEnd = 150;
        private float westYEnd = 300;
    
        String colorStr[] = new String[]{
                "#ffff00",
                "#ff3300",
                "#ccff00",
                "#ff00cc",
                "#ccffff",
                "#cc99ff",
                "#99ff66",
                "#993300"
        };
        
        private ValueAnimator valueAnimator;
        private int currentColor = 0;
    
        public IOSStyleLoadingView(Context context, AttributeSet attrs) {
            super(context, attrs);
            this.mContext = context;
            this.mStrokeWidth = UIUtils.dp2px(this.mContext, 5);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            Sample test1=new Sample("测试1");
            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setStrokeWidth(this.mStrokeWidth);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeCap(Paint.Cap.ROUND);
    
            Path p0 = new Path();
            paint.setColor(Color.parseColor(colorStr[0]));
            p0.moveTo(northwestXStart, northwestYStart);
            p0.lineTo(northwestXEnd, northwestYEnd);
            canvas.drawPath(p0, paint);
    
            Path p1 = new Path();
            paint.setColor(Color.parseColor(colorStr[1]));
            p1.moveTo(northXStart, northYStart);
            p1.lineTo(northXEnd, northYEnd);
            canvas.drawPath(p1, paint);
    
            Path p2 = new Path();
            paint.setColor(Color.parseColor(colorStr[2]));
            p2.moveTo(notheastXStart, notheastYStart);
            p2.lineTo(notheastXEnd, notheastYEnd);
            canvas.drawPath(p2, paint);
    
            Path p3 = new Path();
            paint.setColor(Color.parseColor(colorStr[3]));
            p3.moveTo(eastXStart, eastYStart);
            p3.lineTo(eastXEnd, eastYEnd);
            canvas.drawPath(p3, paint);
    
            Path p4 = new Path();
            paint.setColor(Color.parseColor(colorStr[4]));
            p4.moveTo(southeastXStart, southeastYStart);
            p4.lineTo(southeastXEnd, southeastYEnd);
            canvas.drawPath(p4, paint);
    
            Path p5 = new Path();
            paint.setColor(Color.parseColor(colorStr[5]));
            p5.moveTo(southXStart, southYStart);
            p5.lineTo(southXEnd, southYEnd);
            canvas.drawPath(p5, paint);
    
            Path p6 = new Path();
            paint.setColor(Color.parseColor(colorStr[6]));
            p6.moveTo(southwestXStart, southwestYStart);
            p6.lineTo(southwestXEnd, southwestYEnd);
            canvas.drawPath(p6, paint);
    
            Path p7 = new Path();
            paint.setColor(Color.parseColor(colorStr[7]));
            p7.moveTo(westXStart, westYStart);
            p7.lineTo(westXEnd, westYEnd);
            canvas.drawPath(p7, paint);
        }
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            startAnimation();
        }
    
        public void startAnimation() {
            valueAnimator = ValueAnimator.ofInt(7, 0);
            valueAnimator.setDuration(400);
            valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
            valueAnimator.setInterpolator(new LinearInterpolator());
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    if ((int) animation.getAnimatedValue() != currentColor){
                        String b[] = new String[colorStr.length];
                        for (int c = 0, size = colorStr.length - 1; c < size; c++) {
                            b[c + 1] = colorStr[c];
                        }
                        b[0] = colorStr[colorStr.length - 1];
                        colorStr = b;
                        invalidate();
                        currentColor = (int) animation.getAnimatedValue();
                    }
                }
            });
            valueAnimator.start();
        }
    }
    

    img

    内存分配

    这是一个看起来相对平滑的内存走势图,但是也能看出内存一直在增加,不知道为什么我的内存分配Allocations一直为0,希望看到的小伙伴留言解答一下。
    上面的代码有三个问题:

    1、不断创建String对象,如下代码

    paint.setColor(Color.parseColor(color[0]));
    点击进去看源码:

    public static int parseColor(@Size(min=1) String colorString) {
            if (colorString.charAt(0) == '#') {
                // Use a long to avoid rollovers on #ffXXXXXX
                // 这里调用了String类的substring方法
                long color = Long.parseLong(colorString.substring(1), 16);
                if (colorString.length() == 7) {
                    // Set the alpha value
                    color |= 0x00000000ff000000;
                } else if (colorString.length() != 9) {
                    throw new IllegalArgumentException("Unknown color");
                }
                return (int)color;
            } else {
                Integer color = sColorNameMap.get(colorString.toLowerCase(Locale.ROOT));
                if (color != null) {
                    return color;
                }
            }
            throw new IllegalArgumentException("Unknown color");
        }
    

    原来这里调用了String类的substring方法,我们都知道String类是不可变类,每一个字符串对应一个String对象,每次调用substring方法截取字符串就要新建一个String对象来接收截取后的值,所以我们选中的String对象就来自这里。
    代码修改如下:

       int[] colorInt = new int[colorStr.length];
        // 构造方法中初始化
       for (int i = 0; i < colorStr.length; i++) {
                colorInt[i] = Color.parseColor(colorStr[i]);
        }
    

    2、不能再onDraw中创建对象

        Path mPath = new Path();
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            // 把Paint、Path的创建移出去
            mPath.reset();
            paint.setColor(colorInt[(currentColor++)%8]);
            mPath.moveTo(northwestXStart, northwestYStart);
            mPath.lineTo(northwestXEnd, northwestYEnd);
            canvas.drawPath(mPath, paint);
    }
    

    3、属性动画的addUpdateListener监听中不断创建数组

    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    if ((int) animation.getAnimatedValue() != currentColor){
                         // 记录每次回调的int值,去改变颜色的角标
                        currentColor = (int) animation.getAnimatedValue();
                        invalidate();
                    }
                }
            });
    

    通过修改,内存非常稳定,没有了大量对象的创建。那我们来看一下是否有内存泄漏的问题呢?
    使用Profiler工具dump一下,查看内存分配的具体信息,在使用MAT工具查看明细。

    img

    查看Activity是否有强引用

    可以看到我们刚刚的自定义菊花Activity退出后一直存在强引用,造成内存泄漏。Activity的Context一直被自定义控件持有,再往上看IOSStyleLoadingViewIOSStyleLoadingView$1内部类持有。

    img

    引用关系

    注意:
    动画一直要记得停止

    protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            if (valueAnimator != null){
                valueAnimator.removeAllUpdateListeners();
                valueAnimator.cancel();
            }
        }
    

    总结:

    软引用与弱引用区别?

     /**
             * 软引用
             */
            Object softObject = new Object();
            SoftReference<Object> objectSoftReference = new SoftReference<>(softObject);
            softObject = null;
    
            System.gc();
            System.out.println("sort:"+objectSoftReference.get());
    
            /**
             * 弱引用(结果为null,只要被GC扫到就会被回收)
             */
    
            Object weakObject = new Object();
            WeakReference<Object> objectWeakReference = new WeakReference<>(weakObject);
            weakObject = null;
            System.gc();
            System.out.println("weak:"+objectWeakReference.get());
    
    展开全文
  • python之内存调试

    千次阅读 2018-07-05 15:21:41
    python内存管理知识基础 Python有两种共存的内存管理机制: 引用计数和垃圾回收. 引用计数是一种非常高效的内存管理手段, 当一个Python对象被引 用时其引用计数增加1, 当其不再被一个变量引用时则计数减1. 当引用...

    python内存管理知识基础

    python内存管理机制具有四层结构:

    1. layer 0:最底层(0层)是C运行的malloc和free接口,往上的三层才是由Python实现并且维护的。
    2. layer 1:第1层则是在第0层的基础之上对其提供的接口进行了统一的封装。
    3. layer 2:Python为了避免频繁的申请和删除内存所造成系统切换于用户态和核心态的开销,在第2层引入了内存池机制,专门用来管理小内存的申请和释放。在Linux上运行过Python服务器的程序都知道,python不会立即将释放的内存归还给操作系统,这就是内存缓冲池的原因。
    4. layer 3:第3层主要是对象缓冲池机制,它基于在第二层的内存池。对于可能被经常使用、而且是immutable的对象,比如较小的整数、长度较短的字符串,python会缓存在layer3,避免频繁创建和销毁。

    Python有两种共存的内存管理机制: 引用计数垃圾回收。python的内存回收以引用计数机制为主,引用计数是一种非常高效的内存管理手段, 当一个Python对象被引用时其引用计数增加1, 当其不再被一个变量引用时则计数减1,当引用计数等于0时对象被删除。引用计数的优点在于原理通俗易懂,且将对象的回收分布在代码运行时,一旦对象不再被引用,就会被释放掉(be freed),不会造成卡顿。主要缺点是无法自动处理循环引用。

    垃圾回收机制用来弥补引用计数的不足,可回收循环引用的对象。垃圾回收机制提供了一些接口:

    gc.disable()  # 暂停自动垃圾回收.
    gc.collect()  # 执行一次完整的垃圾回收, 返回垃圾回收所找到无法到达的对象的数量.
    gc.set_threshold()  # 设置Python垃圾回收的阈值.
    gc.set_debug()  # 设置垃圾回收的调试标记. 调试信息会被写入std.err.

    垃圾回收机制

    Python中, 所有能够引用其他对象的对象都被称为容器(container), 因此只有容器之间才可能形成循环引用。Python的垃圾回收机制利用了这个特点来寻找需要被释放的对象,为了记录下所有的容器对象,Python将每一个 容器都链到了一个双向链表中, 之所以使用双向链表是为了方便快速的在容器集合中插入和删除对象。有了这个维护了所有容器对象的双向链表以后, Python在垃圾回收时使用如下步骤来寻找需要释放的对象:

    1. 对于每一个容器对象, 设置一个gc_refs值, 并将其初始化为该对象的引用计数值.
    2. 对于每一个容器对象, 找到所有其引用的对象, 将被引用对象的gc_refs值减1.
    3. 执行完步骤2以后所有gc_refs值还大于0的对象都被非容器对象引用着, 至少存在一个非循环引用。 因此 不能释放这些对象,将他们放入另一个集合。
    4. 在步骤3中不能被释放的对象, 如果他们引用着某个对象,被引用的对象也是不能被释放的, 因此将这些对象也放入另一个集合中。
    5. 此时还剩下的对象都是无法到达的对象. 现在可以释放这些对象了.

    python中应用了分代回收机制 。简单来说就是,将存在时间短的对象容易死掉,而老年的对象不太容易死,这叫做弱代假说(weak generation hypothesis),这也很好理解,一般生命周期长的对象往往是全局变量,而短的多为局部变量或者临时定义的变量。那么,我们把当前的对象作为第0代,我们每当allocation比deallocation多到某个阈值时,就对这些对象做一次检查和清理,没有被清理的那些就存活下来,进入第1代,第一代检查做若干次后,对1代清理,存活下来的进入第2代,第二代也是如此。这样就实现了分代回收的操作。

    通过sys.getrefcount(obj)对象可以获得一个对象的引用数目,返回值是真实引用数目加1(加1的原因是obj被当做参数传入了getrefcount函数)。

    问题分析背景

    最近使用django项目分析一个50M的数据时(并不是一次全部读取到内存),内存在某一时刻突然飙升到2G,并保持到程序执行结束。以下是对内存溢出调试,进行的一些记录。

    查阅资料后,发现可用于调试python内存的工具有resource、memory_profiler、objgraph、heap。

    1. resource

    • 使用:print(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss))
    • 效果:显示当前进程使用了多少内存,resource.RUSAGE_SELF表示只将自己计算在内,不包括当前进程的子进程,RUSAGE_BOTH相当于当前进程和子进程自己的总和(与平台相关)。不同平台上ru_maxrss的值的单位是不一样的,在OS X上单位是Byte,但是在Linux上单位是KB。

    resource模块统计的内存是RSS。

    使用该模块在某些函数执行后,打印进程占用的内存,发现了函数A执行后,内存从100M增加到2131880kb,为了更详细地定位到问题,我尝试使用memory_profiler。

    2. memory_profiler

    2.1 简单使用

    memory_profiler统计的内存是RSS。

    • 安装: pip3 install -U memory_profiler,为了提高效率,另外还应该安装pip3 install psutil
    • 使用:用@profile修饰需要查看内存的函数
    • 执行:python main.py
    • 效果:显示被@profile的函数中每一行代码对应的内存增减

    以下是代码示例

    from memory_profiler import profile
    
    @profile(precision=4)
    def my_func():
        a = [1] * (10 ** 6)
        b = [2] * (2 * 10 ** 7)
        del b
        return a
    
    if __name__ == '__main__':
        my_func()

    2.2 生成与时间相对应的内存变化图

    memory_profiler可以生成内存图,需要先安装pip3 install matplotlib:

     mprof run __main__.py 
     mprof plot

    第一个命令将进程的内存使用情况写入mprofile**.dat文件中,第二个命令将生成的文件展示为图。

    这里写图片描述

    我们可以看到,上面的图中,进程使用的最大内存是50M,然而resource模块在函数A中打印的该进程使用的内存为2G,数值差距很大。这是因为,如果存在子进程或者多进程情况,memory_profile只计算主进程的内存占用。需要分别使用以下方式执行:

    mprof run --include-children <script>
    或
    mprof run --multiprocess <script>

    也可以同时使用–include-children、–multiprocess,最后再执行

    mprof plot

    相同代码的执行结果变为:

    这里写图片描述

    奇怪的是,内存占用图显示了内存飙升,但是代码行对应的内存增减却是正常的。

    2.3 memory_profiler支持api调用

    >>> from memory_profiler import memory_usage
    >>> mem_usage = memory_usage(-1, interval=.2, timeout=1)
    >>> print(mem_usage)
        [7.296875, 7.296875, 7.296875, 7.296875, 7.296875]

    memory_usage(proc=-1, interval=.2, timeout=None)返回一段时间的内存值,其中proc=-1表示此进程,这里可以指定特定的进程号;interval=.2表示监控的时间间隔是0.2秒;timeout=1表示总共的时间段为1秒。那结果就返回5个值。

    如果要返回一个函数的内存消耗,示例

    def f(a, n=100):
         import time
         time.sleep(2)
         b = [a] * n
         time.sleep(1)
         return b
    
    from memory_profiler import memory_usage
    print memory_usage((f, (2,), {'n' : int(1e6)}))

    2.4 调试

    memory_profile支持调试,当你设置了内存阈值时,程序会在内存达到阈值时停下,阈值单位为MB。调试前,你需要为函数添加@profile修饰符

    python -m memory_profiler --pdb-mmem=100 my_script.py

    这功能暂时还未验证是否好用。如果不是使用不当,那么使用memory_profiler与resources模块的感受是,虽然memory_profiler可以显示每一行代码对应的内存增减,但是不是那么准确,函数A中调用了函数a1,我为函数a1添加了@profile(precision=4),显示的内存增减是正常的,但是我使用resources模块在函数a1前后打印内存,发现a1函数开始与结束时,内存占用相差了1G。memory_profiler是准确的,所以,memory_profiler我将会用其内存走势图评估应用的内存走势是否存在问题,resources用来定位出现问题的地方。

    3. heap

    guppy可用于查看python对象占用的堆内存大小,但是该模块的最新版本只支持python2.7,我使用的是python3,暂时用不了。
    结论来自于: http://guppy-pe.sourceforge.net/

    4. objgraph

    安装

    sudo apt-get install xdot
    sudo pip3 install objgraph

    objgraph能够通过图的形式展示对象之间的引用情况:

    import objgraph
    
    a = [1]
    b = [2]
    a.append(b)
    b.append(a)
    objgraph.show_refs([a], filename='ref_topo.png')

    5. 问题定位

    我最终通过memory_profile的cpu走势图与resources模块定位到了问题,出问题的具体函数为:

    def get_a(content, pid, time, process):
        match = re.search(
            '(^----- pid ' + pid + ' at ' + time + ' -----\nCmd line: '
            + process + '\n('
                        '.|\n)*?----- end ' + pid + ' '
                                                    '-----)',
            content, re.M)
        if not match:
            return None
        return match.group(1)

    这个函数的目的是从整个文件内容content中根据pid、time、包名获取匹配的内容。将以上函数的正则修改为:

    def get_a(content, pid, time, process):
        match = re.search(
            '(^----- pid ' + pid + ' at ' + time + ' -----\nCmd line: '
            + process + '\n' +
            '(.*\n)*?----- end ' + pid + ' '
                                                    '-----)',
            content, re.M)
        if not match:
            return None
        return match.group(1)

    修改部分仅仅是将不需要指定关键字的多行从“(.|\n)?”修改为“(.\n)*?”,执行代码后,内存占用的走势变为:
    这里写图片描述

    最高的内存占用从2G变为250M,这与正则的匹配逻辑有关。同时,在正则前后,content对象的引用计数增加了7。更为具体的原因后续添加。由此案例我知,在正则匹配时不会慎重使用或(|)。

    6. 收获

    这里写图片描述

    以上demon执行结果也说明使用”(.|\n)?”表示任意行的性能比”[\S\s]?”差很多。

    参考资料:
    1. 《Python内存问题:提示和技巧》
    2. 《利用PyCharm的Profile工具进行Python性能分析》 –用处不大
    3. 《官方文档 memory_profiler 0.52.0》
    4. Python的7种性能测试工具:timeit、profile、cProfile、line_profiler、memory_profiler、PyCharm图形化性能测试工具、objgraph
    5. Python内存管理机制及优化简析

    展开全文
  • 1、JVM内存管理常识 LMK (LowMemoryKill)机制 android底层会在系统内存告急的时候,按照一定规则杀死一些进程来满足其他进程的内存需要。其中 消耗内存的高低就是其中一项指标,所以,优化app的内存占用,能够...

    1、JVM内存管理常识

    LMK (LowMemoryKill)机制

    android底层会在系统内存告急的时候,按照一定规则杀死一些进程来满足其他进程的内存需要。其中 消耗内存的高低就是其中一项指标,所以,优化app的内存占用,能够有效降低app被系统杀死的概率。

    GC STW机制

    GC,垃圾回收进程,在GC线程执行任务的时候,会存在一个 STW (stop the world) 机制,他就会把其他所有线程都挂起。如果GC非常频繁地调用,那就会导致主线程不流畅,给用户的感觉就是卡顿

    内存抖动频繁引起OOM

    内存抖动太频繁,导致大量对象频繁创建和销毁,会产生大量不连续的内存空间,如果此时有一个大对象需要申请内存,就有可能申请失败,导致OOM内存溢出

    一句话解释 内存泄漏

    生命周期的对象持有生命周期对象的强引用,在生命周期对象需要回收的时候发现不能被回收,视为泄漏。

    GC回收 可达性分析

    GC线程判定 一个对象是不是可以回收,是根据可达性分析算法,计算GcRoot,从GcRoot向下搜索,把GcRoot没有直接关联的对象全部作为垃圾来回收。

    强软弱虚四大引用

    强和虚自不必说。强 最常见,没有特殊处理的都是强引用(包

    展开全文
  • 本文将介绍明源研发协同平台团队针对一次内存泄露问题的排查过程和技术探索。 一、背景 内存泄漏,一个说大不大说下不小的瑕疵。作为开发者,我们都很清楚内存泄漏是我们代码问题导致的。但是话说回来,泄漏后果...

    源宝导读:随着系统越来越庞大,越来越复杂,疑难杂症问题也越来越多。本文将介绍明源研发协同平台团队针对一次内存泄露问题的排查过程和技术探索。

    一、背景

        内存泄漏,一个说大不大说下不小的瑕疵。作为开发者,我们都很清楚内存泄漏是我们代码问题导致的。但是话说回来,泄漏后果会很严重嘛?这不好说,如果对服务器内存的影响只有几个百分点,又或者对应用没有什么致命影响,那么修补内存泄漏就像鸡肋一样,“食之无味,弃之可惜”。反之内存泄漏也可能导致服务不可用,严重影响用户体验。

    二、问题

        19年年初,协同平台后台服务开始间歇性非正常重启,每次重启耗时2-5分钟,重启期间服务不可用;并且事发前用户触发的部分任务没有重试机制,一些中间状态的数据需要人工修复。

        初步排查是程序池内存溢出引起的IIS自动回收,监控看板显示服务器内存从50%瞬间涨到100%,十几分钟之后触发回收,内存才彻底降下来。

        此后重启事件陆续不断,间隔从数小时到数天不等,每次重启前毫无征兆,没有规律可循,下图是服务的内存走势图。

    三、事件分析

        通常内存泄漏最常规的做法是抓取Dump包,然后分析Dump包中的堆栈数据。但这次内存泄漏并非缓慢上升,而是瞬间将内存吃满,没有多余的硬件资源供抓包工具使用。更重要的是这个后台服务职责过重,也非常重要,属于一个在线系统,随便都有用户访问,抓包工具在进行抓包过程中会中断进程,影响服务使用。所以抓取Dump包的方案被暂时搁置。

        在翻遍所有日志没有找到蛛丝马迹之后,决定采用分治策略排除法,先将事故影响的范围慢慢缩小。

    四、垂直拆分

        前文提到后台服务职责过重,需要进行拆分,这个是年初团队达成的共识,方案是先将后台作业任务独立出来,此次事件是个契机。新增一个调度服务,将现有定时作业和主动长时作业两块逻辑包含进去。拆分之后,主服务恢复正常,重启问题转移至新增的调度服务,风险随之降级,虽然问题没有解决,但是两个服务的事故级别是不一样的,主服务恢复正常后,至少不会对用户产生直接影响。

        虽然是一次投鼠忌器的尝试,好在结果符合预期,满足分治的需求,现在已经可以确定,元凶肯定就在调度服务的代码中。

    五、监控埋点

        现在范围缩小了许多,定时作业和主动长时作业两大类,其中的子任务加起来不超过20个,将所有任务的开始和结束各记一次埋点。随后观察重启发作时间点与两大类后台作业的开始时间进行匹配,所有的任务都可以正常的开始,并正常的结束,没有一类任务的执行时间与重启时间点完全匹配,后台任务的嫌疑全部排出。

    六、动态抓包

        排查陷入了僵局,再次观察监控看板,分析现有数据:调度服务的进程内存,正常值在400m-700m之间波动,出现异常时内存在2分钟内,由正常值上升至最高值,最高值接近系统总内存,这个与最初的症状有所不同,起初内存是90度直线瞬间吃满,现在是在2分钟内45度吃满内存。有了2分钟的缓冲期,可能与内存扩容有关,也可能与垂直拆分有关,原因不得而知,但这一点变化使得Dump抓包成为可能。

        这里推荐使用ProcDump,ProcDump是一个轻量级的Sysinternal团队开发的命令行工具,它的主要目的是监控应用程序的CPU内存异常动向,并在此异常时生成crash dump文件,供研发人员和管理员确定问题发生的原因。你还可以把它作为生成dump的工具使用在其他的脚本中。

        根据调度服务内存正常值的区间范围,内存阈值设置为1.5G,超过1.5g认为内存异常。按照ProcDump官网的介绍,ProcDump可以在阈值条件(内存、CPU、异常)到达时,自动触发Dump抓取,但是我部署在服务器ProcDump在一定时间后会出现假死现象,不能后台作业,也不知道下一次重启什么时候来临。无奈临时编写一个小工具,需求很简单,实时监控进程内存,10s轮询一次,超过阈值执行“procdump.exe -ma dotnet.exe“命令抓取Dump,下面是关键代码:

    七、内存分析

        在小工具完成的第二天便成功捕捉到了传说的“黑匣子”,如空难事故中一样,它记录了程序在宕机前的性能指标。

        堆中数据显示,两个字符串数组占据了大部分内存空间,进一步跟踪到是一个zip包解压函数,在读取文件流时,while循环的退出条件判断有误,引起了死循环,无论系统内存有多大,都可以在短时间将服务器内存吃满。代码并非每次都会进来,具有一定的随机性,取决于用户的操作,所以重启没有规律。

        这段业务属于长时作业任务,在上面第五步的监控埋点中,记录了定时作业和主动长时作业两大类的执行结果,唯独这个被动长时作业任务成了漏网之鱼,没有纳入监控体系。

    八、总结

        虽然修复这个Bug的工作量很小,但Bug持续时间长,影响较大。事后团队做了两点改进措施,一是研发流程中加入代码扫描工具,将这类“低级错误”拦截在开发阶段,避免提交到测试环境和生产环境;二是继续完善了现有监控数据,将被动长时作业任务的执行情况,展示在监控看板上,如果某个任务执行失败,或者只有开始没有结束,监控看板会及时发送提醒,为排查类似问题提供线索,快速定位类似问题。

    展开全文
  • 如果你从图形中观察到,内存走势平稳,并没有出现上满模拟抖动的图中那么夸张,是不是就不存在内存抖动呢?并不是。因为我们的gc,是在内存不可用的情况下才会去回收内存,如果app占用内存一直比较少,没有触及gc的...
  • JavaScript,会在创建变量(对象,字符串等)时分配内存,并且在不再使用它们时“自动”释放内存,这个自动释放内存的过程称为垃圾回收。 因为自动垃圾回收机制的存在,让大多Javascript开发者感觉他们可以不关心...
  • 内存泄露引发的卡顿

    2021-04-10 17:27:54
    内存泄露前言一、提出背景二、什么是内存泄露内存泄露的定义js的数据存储js垃圾回收机制Chrome devTools查看内存情况有哪些场景会发生内存泄露及对应的解决方案意外的全局变量引起的内存泄漏被遗忘的定时器 循环引用...
  • 2019年常见Elasticsearch 面试题答案详细解析(下)

    千次阅读 多人点赞 2019-12-26 15:51:03
    在这种情况下,你可以抓取供应商的价格,将它们推入到Elasticsearch中,并使用其反向搜索(Percolator)功能来匹配价格走势与客户查询,并最终在找到匹配后将警报推送给客户。 (4)你有分析/业务智能需求,并希望...
  • App内存优化整理

    千次阅读 2017-03-11 12:10:49
    1.Android内存的管理方式 Android系统内存分配和回收方式 一个app通常就是一个进程对应一个虚拟机,使用adb shell --> ps可以查看所有进程信息,具体内存相关信息可以使用dumpsys meminfo packagename查看,如下: ...
  • 很多开发者可能平时并不关心自己维护的页面是否存在内存泄漏,原因可能是刚开始简单的页面内存泄漏的速度很缓慢,在造成严重卡顿之前可能就被用户刷新了,问题也就被隐藏了,但是随着页面越来越复杂,尤...
  • Android 编程所使用的 Java 是一门使用垃圾收集器(GC, garbage collection)来自动管理内存的语言,它使得我们不再需要手动调用代码来进行内存回收。那么它是如何判断的呢?简单说,如果一个对象,从它的根节点开始...
  • 实战Go内存泄露

    2020-04-30 16:52:50
    实战Go内存泄露 golangmemory内存泄露pprof 最近解决了我们项目中的一个内存泄露问题,事实再次证明pprof是一个好工具,但掌握好工具的正确用法,才能发挥好工具的威力,不然就算你手里有屠龙刀,也成不了天下第...
  • 如果你从图形中观察到,内存走势平稳,并没有出现上满模拟抖动的图中那么夸张,是不是就不存在内存抖动呢?并不是。因为我们的gc,是在内存不可用的情况下才会去回收内存,如果app占用内存一直比较少,没有触及gc的...
  • js内存管理机制

    千次阅读 2019-06-20 09:49:04
    像C语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()用于分配内存和释放内存。而对于JavaScript来说,会在创建变量(对象,字符串等)时分配内存,并且在不再使用它们时“自动”释放内存,这个...
  • JavaScript-内存泄漏

    2021-05-19 22:17:09
    死循环等: 内存泄漏排查: 确定是否是内存泄漏问题 打开谷歌开发者工具,切换至 Performance 选项,勾选 Memory 选项,在页面上点击运行按钮,然后在开发者工具上面点击左上角的录制按钮,通过内存走势图判断当前...
  • 内存泄露是Android开发中比较常见的问题,一旦发生会导致大量内存空间得不到释放,可用内存急剧减少,导致运行卡顿,部分功能不可用甚至引发应用crash。对于复杂度比较高、多人协同开发的项目来讲,如何快速排查并...
  • 当一个应用同时运行越来越多的任务以及复杂的业务,Android系统的内存管理机制已经无法满足内存的释放与回收,为了应用的稳定性与性能,去控制内存的创建和回收就成为了一个重要的命题。 本篇文章主要涉及内容如下...
  • Android 编程所使用的 Java 是一门使用垃圾收集器(GC, garbage collection)来自动管理内存的语言,它使得我们不再需要手动调用代码来进行内存回收。那么它是如何判断的呢?简单说,如果一个对象,从它的根节点开始...
  • 通过点击Android Studio右下角的Memory Monitor标签,打开工具可以看见较浅蓝色代表free的内存,而深色的部分代表使用的内存内存变换的走势图变换,可以判断关于内存的使用状态,例如当内存持续增高时,可能发生...
  • 很多开发者可能平时并不关心自己维护的页面是否存在内存泄漏,原因可能是刚开始简单的页面内存泄漏的速度很缓慢,在造成严重卡顿之前可能就被用户刷新了,问题也就被隐藏了,但是随着页面越来越复杂,尤其当你的页面...
  • 从上图中我们可以看到,在页面从零到加载完成这个过程中JS Heap(js堆内存)、documents(文档)、Nodes(DOM节点)、Listeners(监听器)、GPU memory(GPU内存)的最低值、最高值以及随时间的走势曲线,...
  • 对于前端攻城师来说,JS的内存机制不容忽视。如果想成为行业专家,或者打造高性能前端应用,那就必须要弄清楚JavaScript的内存机制了 先看栗子 function foo (){ let a = 1 let b = a a = 2 console.log(a) // ...
  • Android 编程所使用的 Java 是一门使用垃圾收集器(GC, garbage collection)来自动管理内存的语言,它使得我们不再需要手动调用代码来进行内存回收。那么它是如何判断的呢?简单说,如果一个对象,从它的根节点开始...
  • 简单来讲就是假设某个变量占用100M的内存,而你又用不到这个变量,但是这个变量没有被手动的回收或自动回收,即仍然占用100M的内存空间,这就是一种内存的浪费,即内存泄漏 ???? JS的数据存储 JavaScript的内存...
  • 在开发项目的过程中 或多或少都有些地方不注意造成内存泄漏导致App卡顿的情况发生 Android Studio也为我们提供了一些查找内存泄漏的组件 今天就先学习下 如何使用Android Monitor来分析和查找项目中内存泄漏的地方 ...
  • 关于Unity内存优化,你可能遇到这些问题汇总。
  • 内存优化和内存分析

    2018-01-09 18:08:59
    应用App内存的使用,也是评价一个应用性能高低的一个重要指标。虽然现在只能手机的内存越来越大,但是一个好的应用应当效率发挥到极致,精益求精。而现在有很多应用为了自己的利益,使用一些非常影响系统效率的方法...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,521
精华内容 1,408
关键字:

内存走势