精华内容
下载资源
问答
  • VC内存泄露检查 VC内存泄露检查 VC内存泄露检查
  • 本文为大家分享了Android内存泄漏排查利器,供大家参考,具体内容如下在 build.gralde 里加上依赖, 然后sync 一下, 添加内容如下dependencies {....debugCompile '...

    本文为大家分享了Android内存泄漏排查利器,供大家参考,具体内容如下

    在 build.gralde 里加上依赖, 然后sync 一下, 添加内容如下

    dependencies {

    ....

    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'

    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'

    testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'

    }

    省略号代表其他已有内容

    在 Application类里面将 LeakCanary 初始化。。 比如叫MyApplication ,如果没有就创建一个,继承 android.app.Application。 别忘了在AndroidManifest.xml中加上,否则不起作用

    public class MyApplication extends Application {

    @Override

    public void onCreate() {

    super.onCreate();

    if (LeakCanary.isInAnalyzerProcess(this)) {

    // This process is dedicated to LeakCanary for heap analysis.

    // You should not init your app in this process.

    return;

    }

    LeakCanary.install(this);

    // 你的其他代码从下面开始

    }

    }

    官方已经有demo了,可以跑跑看。

    package com.github.pandafang.leakcanarytest;

    import android.app.Activity;

    import android.os.AsyncTask;

    import android.os.Bundle;

    import android.os.SystemClock;

    import android.view.View;

    public class MainActivity extends Activity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    View button = findViewById(R.id.async_task);

    button.setOnClickListener(new View.OnClickListener() {

    @Override public void onClick(View v) {

    startAsyncTask();

    }

    });

    }

    void startAsyncTask() {

    // This async task is an anonymous class and therefore has a hidden reference to the outer

    // class MainActivity. If the activity gets destroyed before the task finishes (e.g. rotation),

    // the activity instance will leak.

    new AsyncTask() {

    @Override protected Void doInBackground(Void... params) {

    // Do some slow work in background

    SystemClock.sleep(20000);

    return null;

    }

    }.execute();

    }

    }

    进入主界面按下按钮, 再按返回键退出主界面, 反复几次,LeakCanary  就能探测到内存泄漏了。注意要多操作几次,1次的话泄漏规模太小,可能不会探测到。LeakCanary  一旦探测到会弹出提示的。

    回到桌面,会看到一个LeakCanary 的图标,如果有多个app 用到就会有多个LeakCanary图标。

    3f2f7a7e1d10e58a60db8b120dff22b3.png

    点进去就会看到内存泄漏记录

    76b0983ffe571d9445c2f787a832a872.png

    再点进去就可以看到调用栈了

    6d642c76c2beb1dc821f9bd62eff5e0d.png

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

    展开全文
  • 内存泄漏排查

    2017-03-17 15:01:15
    内存泄漏的概念? 垃圾回收器无法回收本应该被回收的对象,这个对象就引发了内存泄漏. 对象都有一定的生命周期,gc进行回收.该内存没有回收就会出现out of memory,即内存oom异常. 内存泄漏的危害? 1导致用户手机...
    内存泄漏的概念?
    垃圾回收器无法回收本应该被回收的对象,这个对象就引发了内存泄漏.
    对象都有一定的生命周期,gc进行回收.该内存没有回收就会出现out of memory,即内存oom异常.

    内存泄漏的危害?
    1导致用户手机内存边少.
    2 程序出现卡顿
    3 导致程序被系统杀死,莫名的退出.
    4 应用程序Force Close

    排查内存泄漏的步骤?
    1 通过统计平台统计平台了解OOM情况
    2 重现问题
    3 在发生内存泄漏时Dump内存
    4 在内存分析工具中反复查看,找到原本该被回收的对象
    5计算此对象到GC root是的最短强引用路径
    6 确定并修复问题

    展开全文
  • 检查内存泄露 检查内存泄露 检查内存泄露
  • 堆外内存泄漏排查

    万次阅读 2020-08-21 14:42:39
    堆外内存泄漏排查 直接内存:指的是Java应用程序通过直接方式从操作系统中申请的内存,也叫堆外内存,因为这些对象分配在Java虚拟机的堆(严格来说,应该是JVM的内存外,但是堆是这块内存中最大的)以外。 直接内存...

    堆外内存泄漏排查

    直接内存:指的是Java应用程序通过直接方式从操作系统中申请的内存,也叫堆外内存,因为这些对象分配在Java虚拟机的堆(严格来说,应该是JVM的内存外,但是堆是这块内存中最大的)以外。

    直接内存有哪些?

    • 元空间。
    • BIO中ByteBuffer分配的直接内存。
    • 使用Java的Unsafe类做一些分配本地内存的操作。
    • JNI或者JNA程序,直接操纵了本地内存,比如一些加密库、压缩解压等。

    JNI(Java Native Interface):通过使用Java本地接口(C或者C++)书写程序,可以确保代码在不同的平台上方便移植。

    JNA(Java Native Access):提供一组Java工具类用于在运行期间动态访问系统本地库(native library:如 Window 的 dll)而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。

    JNA是建立在JNI技术基础之上的一个Java类库,它使您可以方便地使用java直接访问动态链接库中的函数。原来使用JNI,你必须手工用C写一个动态链接库,在C语言中映射Java的数据类型。而在JNA中,它提供了一个动态的C语言编写的转发器,可以自动实现Java和C的数据类型映射,你不再需要编写C动态链接库。也许这也意味着,使用JNA技术比使用JNI技术调用动态链接库会有些微的性能损失。但总体影响不大,因为JNA也避免了JNI的一些平台配置的开销。

    直接内存的优缺点

    直接内存,其实就是不受JVM控制的内存。相比于堆内存有几个优势:

    • 减少了垃圾回收的工作,因为垃圾回收会暂停其他的工作,能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。
    • 加快了复制的速度。因为堆内在flush到远程时,会先复制到直接内存(非堆内存),然后再发送,而堆外内存相当于省略掉了这个工作。
    • 可以在进程间共享,减少JVM间的对象复制,使得JVM的分割部署更容易实现。
    • 可以扩展至更大的内存空间,比如超过1TB甚至比主存还大的空间。

    直接内存有很多好处,我们还是应该要了解它的缺点:

    • 堆外内存难以控制,如果内存泄漏,那么很难排查。
    • 堆外内存相对来说,不适合存储很复杂的对象,一般简单的对象比较适合。

    直接内存的泄漏

    ByteBuffer

    public class DirectMemoryOOM {
    	
    	public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
    		ByteBuffer.allocateDirect(128 * 1024 * 1024);
    	}
    }
    

    运行时带上JVM参数-XX:MaxDirectMemorySize=100m会抛出如下异常:

    Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
    	at java.nio.Bits.reserveMemory(Bits.java:694)
    	at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
    	at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
    	at com.morris.jvm.heapout.DirectMemoryOOM.main(DirectMemoryOOM.java:14)
    

    如果我们没有通过-XX:MaxDirectMemorySize来指定最大的直接内存,那么默认的最大堆外内存是多少呢?一般来说,如果没有显示的设置-XX:MaxDirectMemorySize参数,通过ByteBuffer能够分配的直接内存空间大小就是堆的最大大小,也就是对应对应参数-Xmx,真的是这么样吗?

    1. VM参数配置:-XX:MaxDirectMemorySize=128m,程序会正常退出。
    2. VM参数配置:-Xmx128m,运行结果为OutOfMemoryError,这就说明了ByteBuffer能够分配的直接内存空间大小并不是-Xmx指定的大小。
    3. VM参数配置:-Xmx135m -Xmn100m -XX:SurvivorRatio=8,运行结果还是OutOfMemoryError。
    4. VM参数配置:-Xmx138m -Xmn100m -XX:SurvivorRatio=8,程序会正常退出。

    总结:没有显示的设置-XX:MaxDirectMemorySize参数,通过ByteBuffer能够分配的直接内存空间大小就是堆的最大可使用的大小。堆的最大可使用的大小=堆的最大值(-Xmx)- 一个Survivor的大小(浪费的空间),所以案例3会OOM,堆的最大的可使用的大小=135m-10m=125m,不能分配128M的对象,而在案例4中,堆的最大可使用的大小=138m-10m=128m ,刚好可以分配128M的对象,所有不会OOM。

    UnSafe

    public class UnSafeDemo {
        public static final int _1MB = 1024 * 1024;
    
        public static void main(String[] args) throws Exception {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            Unsafe unsafe = (Unsafe) field.get(null);
            unsafe.allocateMemory(100 * 1024 * 1024);
        }
    }
    

    运行时加上-XX:MaxDirectMemorySize=10m参数,发现程序并没有OOM,也就是说明使用UnSafe API分配的内存不受-XX:MaxDirectMemorySize参数的控制。

    JNI

    package com.morris.jvm.heapout;
    
    import com.sun.management.OperatingSystemMXBean;
    import com.sun.net.httpserver.HttpContext;
    import com.sun.net.httpserver.HttpServer;
    
    import java.io.*;
    import java.lang.management.ManagementFactory;
    import java.net.InetSocketAddress;
    import java.util.Random;
    import java.util.concurrent.ThreadLocalRandom;
    import java.util.zip.GZIPInputStream;
    import java.util.zip.GZIPOutputStream;
    
    public class LeakDemo {
    
        /**
         * 构造随机的字符串
         */
        public static String randomString(int strLength) {
            Random rnd = ThreadLocalRandom.current();
            StringBuilder ret = new StringBuilder();
            for (int i = 0; i < strLength; i++) {
                boolean isChar = (rnd.nextInt(2) % 2 == 0);
                if (isChar) {
                    int choice = rnd.nextInt(2) % 2 == 0 ? 65 : 97;
                    ret.append((char) (choice + rnd.nextInt(26)));
                } else {
                    ret.append(rnd.nextInt(10));
                }
            }
            return ret.toString();
        }
    
        //复制方法
        public static int copy(InputStream input, OutputStream output) throws IOException {
            long count = copyLarge(input, output);
            return count > 2147483647L ? -1 : (int) count;
        }
    
        //复制方法
        public static long copyLarge(InputStream input, OutputStream output) throws IOException {
            byte[] buffer = new byte[4096];
            long count = 0L;
    
            int n;
            for (; -1 != (n = input.read(buffer)); count += (long) n) {
                output.write(buffer, 0, n);
            }
    
            return count;
        }
    
        //解压
        public static String decompress(byte[] input) throws Exception {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            copy(new GZIPInputStream(new ByteArrayInputStream(input)), out);
            return new String(out.toByteArray());
        }
    
        //压缩
        public static byte[] compress(String str) throws Exception {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            GZIPOutputStream gzip = new GZIPOutputStream(bos);
            try {
                gzip.write(str.getBytes());
                gzip.finish();
                byte[] b = bos.toByteArray();
                return b;
            } finally {
                try {
                    gzip.close();
                } catch (Exception ex) {
                }
                try {
                    bos.close();
                } catch (Exception ex) {
                }
            }
        }
    
        private static OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
    
        //通过MXbean来判断获取内存使用率(系统)
        public static int memoryLoad() {
            double totalvirtualMemory = osmxb.getTotalPhysicalMemorySize();
            double freePhysicalMemorySize = osmxb.getFreePhysicalMemorySize();
    
            double value = freePhysicalMemorySize / totalvirtualMemory;
            int percentMemoryLoad = (int) ((1 - value) * 100);
            return percentMemoryLoad;
        }
    
    
        private static volatile int RADIO = 60;
    
        public static void main(String[] args) throws Exception {
    
            //构造1kb的随机字符串
            int BLOCK_SIZE = 1024;
            String str = randomString(BLOCK_SIZE / Byte.SIZE);
            //字符串进行压缩
            byte[] bytes = compress(str);
            for (; ; ) {
                int percent = memoryLoad();
                if (percent > RADIO) { // 如果操作系统内存使用率达到阈值,则等待1s
                    System.out.println("memory used >" + RADIO + "  hold 1s");
                    Thread.sleep(1000);
                } else {
                    //不断对字符串进行解压
                    decompress(bytes);
                    Thread.sleep(1);
                }
            }
        }
    }
    

    演示程序的简单说明:程序将会申请1kb的随机字符串,然后持续解压。为了避免让操作系统陷入假死状态,我们每次都会判断操作系统内存使用率,在达到60%的时候,我们将挂起程序,这样方便用工具来分析。

    启动参数:java -XX:+PrintGC -Xmx1G -Xmn1G -XX:+AlwaysPreTouch -XX:MaxMetaspaceSize=10M -XX:MaxDirectMemorySize=10M LeakDemo

    参数说明:

    • -XX:+PrintGC:打印GC日志。
    • -Xmx1G:限制堆的最大值为1G。
    • -Xmn1G:限制堆的最小值为1G。
    • -XX:MaxMetaspaceSize=10M:限制元空间的最大值为10M。
    • -XX:MaxDirectMemorySize=10M:限制直接内存的最大值为10M。
    • -XX:+AlwaysPreTouch:如果不设置这个参数,JVM的内存只有真正在使用的时候,才会分配给它。如果设置这个参数,在JVM启动的时候,就把它所有的内存在操作系统分配了。在堆比较大的时候,会加大启动时间,但在这个场景中,我们为了减少内存动态分配的影响,把这个参数添加上。

    这个程序很快就打印了以下的显示,这个证明操作系统内存使用率,达到了60%。

    Java HotSpot(TM) 64-Bit Server VM warning: MaxNewSize (1048576k) is equal to or greater than the entire heap (1048576k).  A new max generation size of 1048064k will be used.
    memory used >80  hold 1s
    memory used >80  hold 1s
    memory used >80  hold 1s
    ... ...
    

    下面通过一些命令和工具分析内存泄漏的原因。

    top

    先使用top命令查看哪个进程的内存占用过高:

    # top
    top - 11:14:34 up 277 days, 50 min,  2 users,  load average: 0.00, 0.00, 0.00
    Tasks: 143 total,   1 running, 141 sleeping,   1 stopped,   0 zombie
    Cpu(s):  0.3%us,  0.3%sy,  0.0%ni, 99.3%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
    Mem:   3921488k total,  2458504k used,  1462984k free,   327152k buffers
    Swap:  4063228k total,    58672k used,  4004556k free,   434392k cached
    
      PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                                                                                                         
    17238 root      20   0 4287m 1.3g  11m S  7.9 34.9   0:02.69 java                                                                                                                                                                                                             
        1 root      20   0 19360 1032  792 S  0.0  0.0   0:05.59 init   
    

    发现一个进程号为17238的java进程占用内存较高。

    top命令中几个指标说明:

    • VIRT(virtual memory usage):虚拟内存,进程申请的虚拟内存大小,包括进程使用的库、代码、数据等,假如进程申请100m的内存,但实际只使用了10m,那么它会增长100m,而不是实际的使用量。
    • RES(resident memory usage):常驻内存(物理内存),如果申请100m的内存,实际使用10m,它只增长10m。
    • %MEM:进程使用的物理内存占操作系统物理内存的百分比。

    如果进程较多,可以使用top -p 17238命令,只观察这一个进程。

    # top -p 17238
    top - 11:19:04 up 277 days, 54 min,  2 users,  load average: 0.00, 0.00, 0.00
    Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
    Cpu(s):  0.2%us,  0.0%sy,  0.0%ni, 99.8%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
    Mem:   3921488k total,  3176908k used,   744580k free,   327152k buffers
    Swap:  4063228k total,    58672k used,  4004556k free,   434392k cached
    
      PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                                                                                                         
    17238 root      20   0 6975m 2.0g  11m S  0.3 53.1   0:08.24 java       
    

    当控制台打印memory used >80 hold 1s时,此时进程17238占用的物理内存达到最大值2G。

    jmap

    既然java进程占用的内存较大,在jvm中占用内存最大的区域为堆,存在内存泄漏可能性最大的也是堆,所以使用jvm提供的jmap指令来查看堆的情况。

    # jmap -heap 17238
    Attaching to process ID 17238, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 25.241-b07
    
    using thread-local object allocation.
    Parallel GC with 2 thread(s)
    
    Heap Configuration:
       MinHeapFreeRatio         = 0
       MaxHeapFreeRatio         = 100
       MaxHeapSize              = 1073741824 (1024.0MB)
       NewSize                  = 1073217536 (1023.5MB)
       MaxNewSize               = 1073217536 (1023.5MB)
       OldSize                  = 524288 (0.5MB)
       NewRatio                 = 2
       SurvivorRatio            = 8
       MetaspaceSize            = 10485760 (10.0MB)
       CompressedClassSpaceSize = 2097152 (2.0MB)
       MaxMetaspaceSize         = 10485760 (10.0MB)
       G1HeapRegionSize         = 0 (0.0MB)
    
    Heap Usage:
    PS Young Generation
    Eden Space:
       capacity = 805830656 (768.5MB)
       used     = 274035832 (261.34093475341797MB)
       free     = 531794824 (507.15906524658203MB)
       34.00662781436799% used
    From Space:
       capacity = 133693440 (127.5MB)
       used     = 0 (0.0MB)
       free     = 133693440 (127.5MB)
       0.0% used
    To Space:
       capacity = 133693440 (127.5MB)
       used     = 0 (0.0MB)
       free     = 133693440 (127.5MB)
       0.0% used
    PS Old Generation
       capacity = 524288 (0.5MB)
       used     = 0 (0.0MB)
       free     = 524288 (0.5MB)
       0.0% used
    
    820 interned Strings occupying 55872 bytes.
    

    从堆的日志中可以发现,JVM的堆和元空间总共使用内存1.1G左右,说明不是堆内存泄漏。

    jstack

    既然不是堆内存泄露,那么是不是栈的内存泄漏了呢?

    # jstack 17238
    2020-08-21 11:46:21
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.241-b07 mixed mode):
    
    "Attach Listener" #8 daemon prio=9 os_prio=0 tid=0x00007f0544001000 nid=0x449d waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f057c0ba000 nid=0x444f runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f057c0b7000 nid=0x444e waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f057c0b4800 nid=0x444d waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f057c0af800 nid=0x444c runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f057c081800 nid=0x444b in Object.wait() [0x00007f055fefd000]
       java.lang.Thread.State: WAITING (on object monitor)
    	at java.lang.Object.wait(Native Method)
    	- waiting on <0x00000000c0088ee0> (a java.lang.ref.ReferenceQueue$Lock)
    	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
    	- locked <0x00000000c0088ee0> (a java.lang.ref.ReferenceQueue$Lock)
    	at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
    	at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
    
    "Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f057c07d000 nid=0x444a in Object.wait() [0x00007f055fffe000]
       java.lang.Thread.State: WAITING (on object monitor)
    	at java.lang.Object.wait(Native Method)
    	- waiting on <0x00000000c0086c00> (a java.lang.ref.Reference$Lock)
    	at java.lang.Object.wait(Object.java:502)
    	at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    	- locked <0x00000000c0086c00> (a java.lang.ref.Reference$Lock)
    	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
    
    "main" #1 prio=5 os_prio=0 tid=0x00007f057c009000 nid=0x4446 waiting on condition [0x00007f0583977000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
    	at java.lang.Thread.sleep(Native Method)
    	at LeakDemo.main(LeakDemo.java:130)
    
    "VM Thread" os_prio=0 tid=0x00007f057c073800 nid=0x4449 runnable 
    
    "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f057c01e800 nid=0x4447 runnable 
    
    "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f057c020800 nid=0x4448 runnable 
    
    "VM Periodic Task Thread" os_prio=0 tid=0x00007f057c0bd000 nid=0x4450 waiting on condition 
    
    JNI global references: 5
    

    从栈的日志中可以发现,这个进程总共开启了10多个线程,在jvm中每个线程分配的栈的大小(-Xss)默认为1M,说明不是栈内存泄漏。

    到这里基本上可以确定应该是直接内存发生了泄漏。

    NMT

    NMT(NativeMemoryTracking)是JVM提供的用来追踪Native内存的使用情况。通过在启动参数上加入-XX:NativeMemoryTracking=detail就可以启用,然后使用jcmd命令,就可查看内存分配。

    先把程序关闭,加上-XX:NativeMemoryTracking=detail参数重新启动:

    # jcmd 17845 VM.native_memory summary
    17845:
    
    Native Memory Tracking:
    
    Total: reserved=2416216KB, committed=1117248KB
    -                 Java Heap (reserved=1048576KB, committed=1048576KB)
                                (mmap: reserved=1048576KB, committed=1048576KB) 
     
    -                     Class (reserved=1059970KB, committed=8066KB)
                                (classes #414)
                                (malloc=3202KB #280) 
                                (mmap: reserved=1056768KB, committed=4864KB) 
     
    -                    Thread (reserved=12388KB, committed=12388KB)
                                (thread #13)
                                (stack: reserved=12336KB, committed=12336KB)
                                (malloc=38KB #64) 
                                (arena=14KB #22)
     
    -                      Code (reserved=249713KB, committed=2649KB)
                                (malloc=113KB #447) 
                                (mmap: reserved=249600KB, committed=2536KB) 
     
    -                        GC (reserved=40417KB, committed=40417KB)
                                (malloc=3465KB #112) 
                                (mmap: reserved=36952KB, committed=36952KB) 
     
    -                  Compiler (reserved=137KB, committed=137KB)
                                (malloc=6KB #31) 
                                (arena=131KB #5)
     
    -                  Internal (reserved=3290KB, committed=3290KB)
                                (malloc=3258KB #1328) 
                                (mmap: reserved=32KB, committed=32KB) 
     
    -                    Symbol (reserved=1373KB, committed=1373KB)
                                (malloc=917KB #92) 
                                (arena=456KB #1)
     
    -    Native Memory Tracking (reserved=177KB, committed=177KB)
                                (malloc=114KB #1618) 
                                (tracking overhead=63KB)
     
    -               Arena Chunk (reserved=174KB, committed=174KB)
                                (malloc=174KB) 
    
    

    可惜的是,这个名字让人振奋的工具并不能如它描述的一样,看到我们这种泄漏的场景。上面日志中这点小小的空间,是不能和2GB的内存占用相比的。

    perf

    下面介绍一个神器perf,它除了能够进行一些性能分析,它还能帮助我们找到相应的native调用,这么突出的堆外内存使用问题,肯定能找到相应的调用函数。

    安装:yum install -y perf

    当java进程启动时,我们使用命令perf record -g -p 2747开启监控栈函数调用,当程序内存使用的阈值增加到80%,使用Ctrl+C结束perf命令,这时会在当前目录下生成一个文件perf.data。

    然后执行perf report -i perf.data查看报告:

    Samples: 64K of event 'cpu-clock', Event count (approx.): 16226750000                                                                                                                                                                                                         
      Children      Self  Command  Shared Object       Symbol                                                                                                                                                                                                                     
    +   69.64%     0.05%  java     [kernel.kallsyms]   [k] system_call_fastpath
    +   42.45%     0.08%  java     libc-2.17.so        [.] __GI___libc_read
    +   42.22%     0.03%  java     [kernel.kallsyms]   [k] sys_read
    +   42.14%     0.13%  java     [kernel.kallsyms]   [k] vfs_read
    +   41.66%     0.08%  java     [kernel.kallsyms]   [k] proc_reg_read
    +   36.66%     0.20%  java     [kernel.kallsyms]   [k] seq_read
    +   36.22%     0.33%  java     [kernel.kallsyms]   [k] meminfo_proc_show
    +   28.52%    28.51%  java     [kernel.kallsyms]   [k] get_vmalloc_info
    +   13.59%     0.00%  java     [unknown]           [k] 0x702f006f666e696d
    +   13.35%     0.07%  java     libc-2.17.so        [.] __fopen_internal
    +   13.04%     0.07%  java     libc-2.17.so        [.] __GI___libc_open
    +   12.88%     0.04%  java     [kernel.kallsyms]   [k] sys_open
    +   12.78%     0.17%  java     [kernel.kallsyms]   [k] do_sys_open
    +   11.80%     0.04%  java     [kernel.kallsyms]   [k] do_filp_open
    +   11.70%     0.09%  java     [kernel.kallsyms]   [k] path_openat
    +    8.40%     0.48%  java     [kernel.kallsyms]   [k] do_last
    +    7.95%     0.00%  java     [kernel.kallsyms]   [k] page_fault
    +    7.95%     0.01%  java     [kernel.kallsyms]   [k] do_page_fault
    +    7.81%     1.41%  java     [kernel.kallsyms]   [k] __do_page_fault
    +    6.60%     0.32%  java     libjvm.so           [.] JavaCalls::call_helper
    +    6.33%     0.12%  java     [kernel.kallsyms]   [k] seq_printf
    +    6.30%     0.00%  java     libpthread-2.17.so  [.] start_thread
    +    6.21%     0.05%  java     [kernel.kallsyms]   [k] seq_vprintf
    +    6.08%     0.00%  java     perf-2747.map       [.] 0x00007fb799150574
    +    6.02%     0.00%  java     perf-2747.map       [.] 0x00007fb799121c46
    +    5.91%     0.35%  java     [kernel.kallsyms]   [k] handle_mm_fault
    +    5.78%     1.05%  java     [kernel.kallsyms]   [k] vsnprintf
    +    5.74%     0.07%  java     libc-2.17.so        [.] __GI___munmap
    +    5.71%     0.08%  java     libzip.so           [.] Java_java_util_zip_Inflater_inflateBytes
    +    5.68%     0.00%  java     libjli.so           [.] JavaMain
    +    5.68%     0.00%  java     libjvm.so           [.] jni_CallStaticVoidMethod
    +    5.68%     0.00%  java     libjvm.so           [.] jni_invoke_static
    +    5.58%     0.03%  java     [kernel.kallsyms]   [k] sys_munmap
    +    5.39%     0.03%  java     [kernel.kallsyms]   [k] vm_munmap
    +    5.21%     0.25%  java     [kernel.kallsyms]   [k] do_munmap
    +    5.14%     0.07%  java     [kernel.kallsyms]   [k] proc_root_lookup
    +    5.12%     0.01%  java     [kernel.kallsyms]   [k] lookup_real
    +    5.09%     0.03%  java     [kernel.kallsyms]   [k] proc_lookup
    +    4.99%     0.46%  java     [kernel.kallsyms]   [k] proc_lookup_de
    +    4.95%     0.24%  java     libc-2.17.so        [.] __GI___libc_close
    +    4.74%     0.00%  java     perf-2747.map       [.] 0x00007fb799146471
    +    4.67%     1.35%  java     libzip.so           [.] inflate
    +    4.47%     0.15%  java     libjvm.so           [.] JVM_Sleep
    +    4.45%     0.31%  java     [kernel.kallsyms]   [k] __alloc_pages_nodemask
    +    4.19%     0.00%  java     perf-2747.map       [.] 0x00007fb7990007a7
    +    4.15%     0.09%  java     [kernel.kallsyms]   [k] alloc_pages_vma
    +    4.06%     0.06%  java     [kernel.kallsyms]   [k] do_notify_resume
    +    4.06%     0.00%  java     [kernel.kallsyms]   [k] int_signal
    +    3.99%     0.09%  java     [kernel.kallsyms]   [k] task_work_run
    +    3.94%     0.26%  java     libjvm.so           [.] os::sleep
    +    3.89%     0.02%  java     [kernel.kallsyms]   [k] ____fput
    

    一些JNI程序或者JDK内的模块,都会调用相应的本地函数,在Linux上,这些函数库的后缀都是so,windows下函数库的后缀是dll。

    我们依次浏览所有可疑的资源,发现了“libzip.so”,还发现了不少相关的调用。搜索zip(输入 / 进入搜索模式),结果如下:

    Samples: 64K of event 'cpu-clock', Event count (approx.): 16226750000                                                                                                                                                                                                         
      Children      Self  Comm  Shared Ob Symbol                                                                                                                                                                                                                                 
    +    5.71%     0.08%  java  libzip.so  [.] Java_java_util_zip_Inflater_inflateBytes                                                                                                                                                                                          
         0.23%     0.04%  java  libzip.so  [.] Java_java_util_zip_Inflater_init                                                                                                                                                                                                  
    +   42.22%     0.03%  java  [kernel.kallsyms]   [k] sys_read    
    

    我们发现这些本地调用都是由java.util.zip.Inflater#inflateBytes()方法产生的,然后在代码中追踪哪些地方调用了这个方法或者类,一步一步分析。

    内存泄漏的原因

    GZIPInputStream使用Inflater申请堆外内存、我们没有调用close()方法来主动释放。如果忘记关闭,Inflater对象的生命会延续到下一次GC,在此过程中,堆外内存会一直增长,而GC迟迟没有发生。

        public static String decompress(byte[] input) throws Exception {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            copy(new GZIPInputStream(new ByteArrayInputStream(input)), out);
            return new String(out.toByteArray());
        }
    

    只需要将上面的代码改成如下即可:

        public static String decompress(byte[] input) throws Exception {
    
            try (
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(input))
            ) {
                copy(gzip, out);
                return new String(out.toByteArray());
            }
        }
    

    总结

    对堆外内存划分为3块:

    • 元空间:主要是方法区和常量池的存储之地,可以使用“MaxMetaspaceSize”参数来限制它的大小。
    • 直接内存:主要是通过DirectByteBuffer申请的内存,可以使用“MaxDirectMemorySize”参数来限制它的大小。
    • 其他堆外内存:主要是指使用了Unsafe或者其他JNI手段直接申请的内存。这种情况,就没有任何参数能够阻挡它们,要么靠它自己去释放一些内存,要么等待操作系统对它的审判了。
    展开全文
  • 内存泄露排查

    2020-07-01 13:50:50
    内存泄露(Memory Leak):意思就是你用资源的时候为他开辟了一段空间,当你用完时忘记释放资源了,这时内存还被占用着,一次没关系,但是内存泄漏次数多了就会导致内存溢出 内存溢出(Out Of Memory---OOM):系统已经...

    概念

    内存泄露(Memory Leak):意思就是你用资源的时候为他开辟了一段空间,当你用完时忘记释放资源了,这时内存还被占用着,一次没关系,但是内存泄漏次数多了就会导致内存溢出

    内存溢出(Out Of Memory---OOM):系统已经不能再分配出你所需要的空间,比如你需要100M的空间,系统只剩90M了,这就叫内存溢出

    常见问题现象

    后台系统,经过压力测试之后,或者生产程序运行一段时间后,进程占用的操作系统内存比例一直居高不下。怀疑系统可能存在内存泄漏。

    常用工具及命令:visualVM, MAT, jmap, top

    排查思路:

    使用top命令查看问题进程,查看cpu,内存消耗情况   top -p 12121212

    查看该进程的启动参数等相关情况,判断参数设置是否合理  ps -aux |grep -v grep|grep 28990

    查看内存使用情况    jmap -heap 12121212

    查看内存实时使用情况,用visualVM远程连接程序(visualVM远程连接进程),查看使用趋势……

    经过以上几步如果已经确定是内存泄露,则需要导出heap dump进一步分析,导出方式有visualVM,jmap

    jmap -dump:file=/data/server/test_ops/a.hprof 22678

    jmap -dump:live,format=b,file=/data/server/test_ops/a.hprof 5889

    jmap -histo:live 5889

    heap dump分析,用MAT, visualVM结合起来分析

    不健壮代码的特征及解决办法 

    1、尽早释放无用对象的引用。好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为null,暗示垃圾收集器来收集该 对象,防止发生内存泄露。对于仍然有指针指向的实例,jvm就不会回收该资源,因为垃圾回收会将值为null的对象作为垃圾,提高GC回收机制效率; 

    2、我们的程序里不可避免大量使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域;例如 

    3、尽量少用静态变量,因为静态变量是全局的,GC不会回收的; 

    4、避免集中创建对象尤其是大对象,JVM会突然需要大量内存,这时必然会触发GC优化系统内存环境;显示的声明数组空间,而且申请数量还极大。 

    5、尽量运用对象池技术以提高系统性能;生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。 

    6、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用hashtable,vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次new之后又丢弃 

    7、一般都是发生在开启大型文件或跟数据库一次拿了太多的数据,造成 Out Of Memory Error 的状况,这时就大概要计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值


    展开全文
  • C++内存泄漏排查

    2021-01-13 20:43:31
    linux c++内存泄漏排查方案汇总 参考:https://www.cnblogs.com/kex1n/p/11572313.html 使用mtrace分析内存泄漏 参考:https://zhuanlan.zhihu.com/p/83547768
  • 主要为大家详细介绍了Android内存泄漏排查利器LeakCanary的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 内存泄漏检查

    2015-11-23 14:24:35
    什么是内存泄漏(memory leak)?  一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存 。由于程序员 疏忽或...
  • iOS 内存泄漏排查方法及原因分析

    千次阅读 2018-11-22 09:03:18
    标签:「iOS」「内存泄漏排查」「Leaks工具」 作者: MrLiuQ 审校: QiShare团队 本文将从以下两个层面解决iOS内存泄漏问题: 内存泄漏排查方法(工具) 内存泄漏原因分析(解决方案) 在正式开始前,我们先...
  • Java内存泄漏排查

    2019-01-03 16:34:58
    Java内存泄漏排查 1.内存溢出和内存泄露 通俗点说: 内存溢出:申请了10个字节的空间,但是确在这个空间写入11或以上字节的数据,出现溢出。 内存泄漏:new申请了一块内存,后来很长时间都不再使用了(按理应该释放...
  • java内存泄露排查

    2021-02-23 09:37:54
    java内存泄露排查 https://blog.csdn.net/fishinhouse/article/details/80781673
  • Android内存泄露排查

    2016-05-15 17:57:44
    Android内存泄露排查:使用conv-hprof转化生成的mat,然后在histogrm页面中过滤,选择incomming reference,然后去掉非强引用项目。
  • 服务内存泄漏排查

    2020-11-09 11:12:24
    个人理解算是内存泄漏的问题。 缘起: 在测试环境,产品说后台服务不能访问了,查看了下,发现进程突然消失了。用户用dmesg查看了下,确实被操作系统给杀了。 由于这个服务是把多个微服务又聚合到一个进程里面,...
  • 内存泄露检查

    2013-03-17 11:10:35
    内存泄漏的根源在于申请了空间而没有释放,因此,查找内存泄漏的简单有效的方法就是检查内存分配与内存释放的配对情况,以及代码分析.   辅助的方法有: 1,在vc6中,通过build->start debug->go命令,可以直接...
  • 内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,需要自主观察,比较...
  • Android 内存泄漏排查过程最近项目内存占用越来越大,在图片管理优化了一波之后,内存还是有点过大。大概率是出现内存泄漏了,不查不知道,一查吓一跳。很多地方出现了内存泄漏内存泄漏 内存泄漏(Memory Leak)...
  • JS内存泄漏排查

    千次阅读 2016-10-13 21:27:03
    可以利用chrome f12审查工具中的profile功能进行内存泄漏排查,点击take snapshot拍下堆内存的快照,对比相邻的两次快照就能得出哪些内存没有回收1.在控制台中新添加一个对象,观察profile中的变化:function Foo...
  • java内存泄露排查总结

    2021-01-18 11:14:24
    1.内存溢出和内存泄露 一种通俗的说法: 内存溢出:你申请了10个字节的空间,但是你在这个空间写入了11个或者以上字节的数据,则出现溢出 内存泄露:你用new申请了一块内存,后来很长时间都不使用了,但是因为...
  • kaldi内存泄漏排查

    2019-10-12 15:07:16
    经过一番搜索,在Linux下常用的内存泄露检查工具箱是valgrind,这是一个非常强大的工具,光说明使用手册就有400多页。 比较傻瓜的使用方式是直接使用valgrind下面的memcheck工具,当然这个工具并不是万能的,在Kaldi...
  • 作为一个合格的BSP开发人员,在工作中会碰到各种内存泄漏的问题,遇到该问题的时候要如何确认问题原因和排查方向 这是大家一直比较关心的问题,接下来我主要介绍几款BSP工程师排查内核内存泄漏的手段和方式 内存泄漏...
  • 内存泄漏检查

    2015-12-02 22:26:04
    C++内存泄漏检查源码,通过重载全局new/delete new[]/delete[]实现,多线程安全。
  • JVM内存泄漏排查记录

    2021-07-29 10:31:20
    JVM堆外内存泄漏故障排查记录
  • 线上内存泄露排查

    2021-03-25 21:09:17
    背景: 上周网关上线后,隔了几小时突然服务不可用,重启后恢复,隔几小时后依然如此;由于此次网关改动的代码较多,一时不好定位。...从系统监控图可以看出,自从服务上线后已用内存就一直在申请、上升,没有释放,那
  • 文章目录step1、判断是否存在内存泄漏或者内存一直申请没有释放的情况step2、内核层的内存泄漏还是应用程序的内存泄漏step3、内核里有内存泄漏排查分析方法step4、应用层程序内存泄漏排查分析方法 step1、判断是否...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 126,727
精华内容 50,690
关键字:

内存泄漏如何排查