内存泄漏 订阅
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。 展开全文
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
信息
外文名
Memory Leak
中文名
内存泄漏
内存泄漏简介
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。内存泄漏缺陷具有隐蔽性、积累性的特征,比其他内存非法访问错误更难检测。因为内存泄漏的产生原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷。此外,内存泄漏通常不会直接产生可观察的错误症状,而是逐渐积累,降低系统整体性能,极端的情况下可能使系统崩溃。随着计算机应用需求的日益增加,应用程序的设计与开发也相应的日趋复杂,开发人员在程序实现的过程中处理的变量也大量增加,如何有效进行内存分配和释放,防止内存泄漏的问题变得越来越突出。例如服务器应用软件,需要长时间的运行,不断的处理由客户端发来的请求,如果没有有效的内存管理,每处理一次请求信息就有一定的内存泄漏。这样不仅影响到服务器的性能,还可能造成整个系统的崩溃。因此,内存管理成为软件设计开发人员在设计中考虑的主要方面 [1]  。
收起全文
精华内容
参与话题
问答
  • 内存泄漏

    2016-12-27 15:22:32
    最近做的一个项目中,遇到了内存泄漏的问题,查了很多资料,问题解决后,简单记录下。 一、内存泄漏的概念: 严格意义讲,内存泄露的原因只有一种:没有释放向系统申请的内存,因为不申请内存,就谈不上什么泄露...

    最近做的一个项目中,遇到了内存泄漏的问题,查了很多资料,问题解决后,简单记录下。

    一、内存泄漏的概念:

    严格意义讲,内存泄露的原因只有一种:没有释放向系统申请的内存,因为不申请内存,就谈不上什么泄露。

    二、内存泄漏的原因:

    上述提到,内存泄漏就是内存没有释放,而内存没有释放的原因有很多种:可能写代码的时候,忘记了释放自己代码里申请的内存;也有可能是你使用了一个写的不好的库,库本身有问题等。

    三、内存泄漏的分类:

    1)常发性内存泄漏:发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。

    2)偶发性内存泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。

    3)一次性内存泄漏:发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。

    4)隐式内存泄漏:程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。

    四、JS的垃圾收集机制

     JS具有自动垃圾收集机制。即执行环境会负责管理代码执行过程中使用的内存。而在CC++之类的语言中,开发人员的一项基本任务就是手动跟踪内存的使用情况,这是造成许多问题的一个根源。而在编写JS程序时,开发人员不用再关心内存使用的问题,所需内存的分配以及无用的回收完全实现了自动管理。

     JavaScript中最常用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占的内存,因为只要执行流进入相应的环境,就可能用到它们。而当变量离开环境时,这将其标记为“离开环境”。

    五、JS中常见的内存泄漏的原因:

    (1)全局变量引起的内存泄漏

      function leaks(){

      leak= 'xxxxxx';

      //leak一个全局变量,不会被回收

      }

     定义过多的全局变量,会造成内存泄漏,因为全局变量申请的内存,不会被释放。

     (2)闭包引起的内存泄漏

       var leaks = (function(){

       var leak= 'xxxxxx';//被闭包所引用,不会被回收

       return   function(){

       console.log(leak);

       }

      })()

     变量被闭包函数使用,申请内存不能被释放。

    (3)dom清空或删除时,事件未清除导致的内存泄漏

     事件清除前:

     <div id="container"></div>

     $('#container').bind('click', function(){

      console.log('click');

     }).remove();

     该种方法中,由于事件未被清除,所以remove()无效,这样也会造成内存泄漏。

     事件清除后:

     <div id="container"> </div>

     $('#container').bind('click',function(){

         console.log('click');

     })

     .off('click')

     .remove();

     把事件清除了,即可从内存中移除。

     另外,在项目中,使用多个定时器,尤其是定时器里面套定时器,也造成了内存的泄漏。建议不要过多使用定器

     ,更不要套用,用了也要记着清掉。


      参考:http://www.xuexila.com/yuanyin/734475.html

      参考:http://www.cnblogs.com/libin-1/p/6013490.html



    展开全文
  • JVM 内存泄漏 虚拟机内存泄漏总结

    万次阅读 2018-09-01 10:14:56
    内存泄漏 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。 一 . 以发生的方式来分类,内存泄漏可以分为4类:  ...

    内存泄漏 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

    一 . 以发生的方式来分类,内存泄漏可以分为4类: 

    1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。 

    2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。 

    3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。 

    4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。 

    二 . Java内存泄露引起原因 

    内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。那么,Java内存泄露根本原因是什么呢?长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。具体主要有如下几大类

    1、静态集合类引起内存泄露(static 对象是不会被GC回收的):

    集合类,如HashMap、Set、ArrayList、Vector等它们是内存泄漏的常见位置。如将他们声明为static,那么它将拥有和主程序一样的生命周期,如果他们里面放置了大量对象(引用的关系),当这些对象不在使用时,因为HashMap,集合等是全局的static类型,那么垃圾回收将无法处理这些不在使用的对象,从而造成了内存泄漏。

    public class MemoryLeak{
    	static List<Student> list = new ArrayList<Student>();
    	
    	public static void main(String[] args) 
    			throws InterruptedException{
    		
    		for(int i =0;i<1000000;i++){
    			Student stu = new Student();
    			list.add(stu);    //内存泄漏,无法回收
    		}
    		//便于观察虚拟机运行情况
    		while(true){
    			Thread.sleep(20000);
    		}
    	}
    }
    class Student{	
    }

    2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。

    public class MemoryLeak2 {
    	public static void main(String[] args) {
    
    		Set<Person> set = new HashSet<Person>(); 
    		Person p1 = new Person("唐僧","pwd1",25); 
    		Person p2 = new Person("孙悟空","pwd2",26); 
    		Person p3 = new Person("猪八戒","pwd3",27); 
    		set.add(p1); 
    		set.add(p2); 
    		set.add(p3);//现在set里面有3个对象! 
    		p3.setAge(2); //修改p3的年龄,此时p3的hashcode改变 
    		set.remove(p3); //此时remove不掉,造成内存泄漏
    		set.add(p3); //重新添加,成功,现在set里面有4个对象
    	}
    
    }
    class Person{
        public Person(){}
        public Person(String name,String pwd,Integer age){
            ...
    }

    3、各种连接

    比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。

    4、单例模式

    如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露,考虑下面的例子:

    
    
    class A{
        public A(){
            B.getInstance().setA(this);
        }
        ....
    }
    
    //B类采用单例模式
    class B{
        private A a;
        private static B instance=new B();
        public B(){}
        public static B getInstance(){
            return instance;
        }
        public void setA(A a){
            this.a=a;
        }
        //getter...
    }

    5、读取流没有关闭

    开发中经常忘记关闭流,这样会导致内存泄漏。因为每个流在操作系统层面都对应了打开的文件句柄,流没有关闭,会导致操作系统的文件句柄一直处于打开状态,而jvm会消耗内存来跟踪操作系统打开的文件句柄。

    6、监听器

    在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。

    7、String的intern方法

    在大字符串上调用String.intern() 方法,intern()会将String放在jvm的内存池中(PermGen ),而jvm的内存池是不会被gc的。因此如果大字符串调用intern()方法后,会产生大量的无法gc的内存,导致内存泄漏。如果必须要使用大字符串的intern方法,应该通过-XX:MaxPermSize参数调整PermGen内存的大小(关于设置虚拟机内存大小后续会继续发布相关博客)。

    8、 将没有实现hashCode()和equals()方法的对象加入到HashSet中

    这是一个简单却很常见的场景。正常情况下Set会过滤重复的对象,但是如果没有hashCode() 和 equals()实现,重复对象会不断被加入到Set中,并且再也没有机会去移除。因此给类都加上hashCode() 和 equals()方法的实现是一个好的编程习惯。可以通过Lombok的@EqualsAndHashCode很方便实现这种功能。

    三. 查找内存泄漏的方法

    3.1 记录gc日志

    通过在jvm参数中指定-verbose:gc,可以记录每次gc的详细情况,用于分析内存的使用。

    3.2 进行profiling

    通过Visual VM或jdk自带的Java Mission Control,进行内存分析。

    3.3 代码审查

    通过代码审查和静态代码检查,发现导致内存泄漏问题的错误代码。

    四. 总结

    代码层面的检查可以帮助发现部分内存泄漏的问题,但是生产环境中的内存泄漏往往不容易提前发现,因为很多问题是在大并发场景下才会出现。因此还需要通过压力测试工具进行压力测试,提前发现潜在的内存泄漏问题。

     

     

    展开全文
  • Android内存泄漏

    2019-04-15 10:27:19
    本课程主要讲解在Android端怎么去排查开发的程序是否发生内存泄漏,如果发生了内存泄漏怎么去定位查找,教会如何使用Android Profiler,MAT 工具
  • 堆外内存泄漏排查

    万次阅读 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手段直接申请的内存。这种情况,就没有任何参数能够阻挡它们,要么靠它自己去释放一些内存,要么等待操作系统对它的审判了。
    展开全文
  • Android内存泄漏(线程造成的内存泄漏和资源未关闭造成的内存泄漏



    一.线程造成的内存泄漏

    对于线程造成的内存泄漏,也是平时比较常见的leakCanary官方Demo就是线程成造成的内存泄漏,使用了AsyncTask去执行异步线程,现在我们换个写法,直接使用Thread:

    1. 新建工程,配置好leakCanary环境

    2. 直接在MainActivity添加如下代码:

    红色方框内的代码,可能每个人都这样写过。

    OK ,我们执行一下,然后做如下操作:

    1 MainActivity页面打开后,在20秒内点击手机返回键

    2. 等待10秒

    操作完成,leakCanary检测出内存泄漏。



    分析原因:和上面几个案例的原因类似,不知不觉又搞了一个匿名内部类Runnable,对当前Activity都有一个隐式引用。如果Activity在销毁的时候Runable内部的任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。正确的做法还是使用静态内部类的方式,如下:


    上面代码中,自定义了静态的内部类MyRunable,实现了Runable ,然后在使用的时候实例化它。

    运行代码后做如上相同操作,发现leakCannary没有检测出内存泄漏。

     


    二. 资源未关闭造成的内存泄漏

    对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的代码,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

     

     

    总结

    以上Android开发中常见的内存泄漏问题及解决办法,能对内存泄漏有一定的认识和见解,是同学们面试时的一个极其有利的加分项, 内存泄漏是很多有一定开发经验的程序员都会犯的错误,掌握这些,代表你确确实实做过一些东西,并有一定的总结。



    展开全文
  • JAVA内存泄漏——内存泄漏原因和内存泄漏检测工具(zt) 摘要   虽然Java虚拟机(JVM)及其垃圾收集器(garbage collector,GC)负责管理大多数的内存任务,Java软件程序中还是有可能出现内存泄漏。实际上,这在大型...
  • 内存泄漏:由于疏忽或者错误造成程序未能释放已经不再使用的情况,内存泄漏并不是指内存在物理上的错误消失,而是程序分配某段内存后,由于设计错误,丢失了对这段内存的控制,因而造成了内存浪费。 如何进行内存...
  • 内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏内存泄漏形象...
  • 1.1内存溢出和内存泄漏 2.内存溢出 2.1 栈溢出 2.2堆溢出 2.3永久代溢出 2.4 java常见的几种内存溢出及解决方法 3.内存泄漏 3.1为什么会产生内存泄漏 3.2 解决方式 1.介绍 作为一个Java开发者,想必大家都...
  • Java进程内存泄漏判断及解决方法

    万次阅读 2020-06-12 09:08:34
    内存泄漏种类 Java使用的内存种类包含三种,这三种类型的内存都可能发生内存泄漏。 • 堆内存泄漏,如果JVM 不能在java 堆中获得更多内存来分配更多java 对象,将会抛出java堆内存不足(java OOM) 错误。如果java 堆...
  • 前端内存泄漏

    千次阅读 2019-04-26 10:42:12
    本文内容:关于内存泄漏问题,产生的原因、发生内存泄漏的特征或者表象、如何捕获或者监听是否发生内存泄漏、如何防范和解决内存泄漏问题。 概述 代码发生内存泄漏的问题,一般的应用中很难遇到,因此很多人对内存...
  • *内存泄漏和内存溢出

    千次阅读 2017-06-27 14:03:20
    (1)内存泄漏和内存溢出 内存泄漏:分配出去的内存无法回收(不再使用的对象或者变量仍占内存空间),在Java中内存泄漏就是存在一些被分配的对象(可达的,却是无用的)无法被gc回收。 内存溢出:程序要求的内存...
  • 内存溢出,内存泄漏,内存抖动

    千次阅读 2018-11-19 11:17:57
    内存溢出,内存泄漏,内存抖动你都碰到过吗?怎么解决的?如何区分这几种情况?怎么解决由内存泄漏而导致的内存溢出? 内存优化 . 内存泄露 内存溢出 内存抖动 分析与解决 内存溢出和内存泄漏的区别、产生原因以及...
  • 我在Activity中开一个线程(New Thread),因为这个线程是非静态内部类,...如果算是内存泄漏,是不是这种短时间的内存泄漏属于正常的呢?也就是说这种内存泄漏在我们的接受范围内,无需去管它了? 请老师指教,谢谢!
  • 对Linux c的内存泄漏检测可能还比较熟悉,但是对脚本语言python进行内存泄漏的测试,我一开始表示完全不知情,随着查找资料,才逐渐明朗。所以这个内存泄漏版块,主要为了记录一下当时是如何选择工具,为什么选择这...
  • golang常见内存泄漏

    万次阅读 2020-09-22 22:10:26
    1.有goroutine泄漏,goroutine“飞”了,zombie goroutine没有结束,这个时候在这个goroutine上分配的内存对象将一直被这个僵尸goroutine引用着,进而导致gc无法回收这类对象,内存泄漏。 2.有一些全局(或者生命...
  • Android内存泄漏(使用单例设计模式造成的内存泄漏
  • 内存溢出与内存泄漏

    千次阅读 2016-03-17 15:27:21
    内存泄漏(memory leak)指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况,是应用程序分配某段内存后,由于程序设计错误而导致JVM失去了对该段内存的控制,因而造成了内存的浪费。一般内存泄漏是指堆内存...
  • 内存泄漏和内存溢出

    万次阅读 多人点赞 2018-08-22 15:28:58
    内存泄漏:(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果   内存泄露 memory leak,是指程序在申请内存后,...
  • 关于内存泄漏

    千次阅读 2016-10-13 16:19:52
    内存泄漏产生原因 内存泄漏的出现场景 1单例造成的内存泄漏 非静态内部类创建其静态实例造成内存泄漏 匿名内部类异步线程造成内存泄漏 Handler机制造成内存泄漏 资源未回收导致内存泄漏 使用新版Android Studio检测...
  • 本文将给大家介绍,如何使用一个小的开源组件【memwatch】排查有可能出现【内存泄漏】的代码。 先上一段测试代码,里面是包括一些内存操作错误的代码: //main.c #include &lt;stdio.h&gt; #include &...

空空如也

1 2 3 4 5 ... 20
收藏数 38,231
精华内容 15,292
关键字:

内存泄漏