文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

什么是内存泄漏?该如何检测?又该如何解决?

2024-12-03 09:52

关注

前言

这个问题是我之前翻看面经的时候见到的。那位小姐姐把内存泄漏当成了内存溢出问题去解答的,结果当场挂掉了。为此总结一下,之前和一位老哥也讨论过这个问题。可见不管是面试还是工作这都是一个极为重要的点。

我也曾在面阿里的时候也遇到过原题,题目是写出俩内存泄漏案例,然后问如何排查?如何解决?

本篇文章大体结构来自外国大佬baeldung;

一、介绍

1、什么是内存泄漏

java的优势之一就是内置了垃圾回收器GC,它帮助我们实现了自动化内存管理。但是GC再好,也有老马失前蹄的时候,它不能保证提供一个解决内存泄漏的万无一失的解决方案。什么是内存泄漏?可以看看下面这张图,

也就是一部分内存空间我明明已经使用了,却没有引用指向这部分空间。造成这片已经使用的空间无法处理的情况。

正规点的理解:动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。

2、内存泄漏的危害

反正内存泄漏不是好事。

二、内存泄漏原因

内存泄漏原因太多了。说不定就是某一行代码不对就会出现这种情况,因此这里给出最常见的几种。关键的还是如何找出哪个地方出现了内存泄漏,代码好修改,错误不易查。

1、大量使用静态变量

静态变量的生命周期与程序一致。因此常驻内存。

  1. public class StaticTest { 
  2.     public static  List<Integer> list = new ArrayList<>(); 
  3.     public void populateList() { 
  4.         for (int i = 0; i < 10000000; i++) { 
  5.             list.add((int)Math.random()); 
  6.         } 
  7.         System.out.println("running......"); 
  8.     } 
  9.     public static void main(String[] args) { 
  10.       System.out.println("before......"); 
  11.         new StaticTest().populateList(); 
  12.         System.out.println("after......"); 
  13.     } 

现在可以使用jvisualvm运行一边,看看内存效果。

从上图可以看到,堆内存从一开始的135M左右飙升了到了200M。直接占据了65M的内存。

由于全局变量与程序周期不一致,因此不使用时,就会进行回收。此时内存最高150M。

总结:由于静态变量与程序生命周期一致,因此对象常驻内存,造成内存泄漏

2、连接资源未关闭

每当建立一个连接,jvm就会为这么资源分配内存。比如数据库连接、文件输入输出流、网络连接等等。

  1. public class FileTest { 
  2.  public static void main(String[] args) throws IOException { 
  3.   File f=new File("G:\\nginx配套资料\\笔记资料.zip"); 
  4.   System.out.println(f.exists()); 
  5.         System.out.println(f.isDirectory()); 
  6.  } 

依然使用jvisualvm运行一边,看看内存效果。

可以看出,在连接文件资源时,jvm会为本资源分配内存。

3、equals()和hashCode()方法使用不当

定义新类时,如果没有重新equals()和hashCode()方法,也有可能会造成内存泄漏。主要原因是没有这两个方法时,很容易造成重复的数据添加。看例子:

  1. public class User
  2.  public String name
  3.  public int age; 
  4.  public User(String nameint age) { 
  5.   this.name = name
  6.   this.age = age; 
  7.  } 
  8. public class EqualTest { 
  9.  public static void main(String[] args) { 
  10.   Map<UserInteger> map = new HashMap<>(); 
  11.      for(int i=0; i<100; i++) { 
  12.          map.put(new User("", 1), 1); 
  13.      } 
  14.         System.out.println(map.size() == 1);//输出为false 
  15.  } 

然后运行一下,看看内存情况:

内存从150M一下子飙升到225M,可见飙升的厉害。输出为false,说明user对象被重复添加了。我们知道像HashMap在添加新的对象时,会对其hashcode进行比较,如果一样,那就不插入。如果一样那就插入。此时说明这100个User其hashcode不同。

现在重写这俩方法再运行一边:

  1. public class User
  2.  public static String name
  3.  public User(String name) { 
  4.   this.name = name
  5.  } 
  6.     @Override 
  7.     public boolean equals(Object o) { 
  8.         if (o == this) return true
  9.         if (!(o instanceof User)) { 
  10.             return false
  11.         } 
  12.         User user = (User) o; 
  13.         return User.name.equals(name); 
  14.     } 
  15.     @Override 
  16.     public int hashCode() { 
  17.         return name.hashCode(); 
  18.     }  

在EqualTest类再测试一遍,首先看看内存变化:

上图可以看到上升幅度没那么大。而且输出为true,这是肯定的,由于重写了hashcode和equal,所以HashMap添加的肯定是同一个对象。

4、内部类持有外部类

这个场景和上面类似。

5、finalize方法

这个方法之前曾经专门花过文章写过,这个问题很简单。看一张图

这就是整个过程。不过在这里我们主要看的是finalize方法对垃圾回收的影响,其实就是在第三步,也就是这个对象含有finalize,进入了队列但一直没有被调用的这段时间,会一直占用内存。造成内存泄漏。

6、ThreadLocal的错误使用

ThreadLocal主要用于创建本地线程变量,不合理的使用也有可能会造成内存泄漏。

上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。

Thread中有一个map,就是ThreadLocalMap

ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。

ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收

重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

现在介绍了几种常见的内存泄漏情况,上面的知识点比较常见,最主要的是如何检测出来。

三、检测内存泄漏

检测的目的是定位内存泄漏出现的位置,常见的有以下几种方法:

1、工具分析

这个工具比较多,比如说JProfiler、YourKit、Java VisualVM和Netbeans Profiler。他可以帮助我们分析是哪一个对象或者是类内存的飙升。也可以看到内存CPU的等等各种情况。上面多次演示到了。

2、垃圾回收分析

这个其实也可以用工具进行分析。上面的VisualVM中,可以打印堆。也可以从外部导入dump文件进行分析。

如果不用工具的话,我们可以通过IDE看到。JVM配置添加-verbose:gc。然后就会打印出相关信息。下面这张图非原创,来自Baeldung。

3、基准测试

也就是使用科学的方式进行分析java代码的性能。进而判断分析。

四、结论

内存泄漏是个很严重的问题,也比较常见。最主要的原因是动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。因此良好的代码规范,可以有效地避免这些错误。

本文转载自微信公众号「愚公要移山」,可以通过以下二维码关注。转载本文请联系愚公要移山公众号。

 

来源:愚公要移山内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯