`
IXHONG
  • 浏览: 438910 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

聊聊内存泄露

    博客分类:
  • Java
阅读更多

一、Java内存回收机制 

不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址。Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题。在Java语言中,判断一个内存空间是否符合垃圾收集标准有两个:一个是给对象赋予了空值null,以下再没有调用过,另一个是给对象赋予了新值,这样重新分配了内存空间。

二、Java内存泄露引起原因 

首先,什么是内存泄露?经常听人谈起内存泄露,但要问什么是内存泄露,没几个说得清楚。内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。

那么,Java内存泄露根本原因是什么呢?长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。具体主要有如下几大类: 

1、静态集合类引起内存泄露: 

像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。 

例: 

Static Vector v = new Vector(10); 

for (int i = 1; i<100; i++) 

Object o = new Object(); 

v.add(o); 

o = null; 

}// 

在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身(o=null),那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。

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

例: 

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); 

System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素! 

p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变 

set.remove(p3); //此时remove不掉,造成内存泄漏

set.add(p3); //重新添加,居然添加成功 

System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素! 

for (Person person : set) 

System.out.println(person); 

}

3、监听器 

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

4、各种连接 

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

5、内部类和外部模块等的引用 

内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如: 

public void registerMsg(Object b); 

这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。

6、单例模式 

不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在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... 

显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况

 

三、如何确定OOM是由于内存泄露引起的

  在工作中,遇到OOM,你首先要确定他是由于什么原因引起的?是因为堆空间设置太小引起还是因为内存泄露引起。实际上,内存泄露的问题可以通过增大堆空间暂时得到解决,但是他不是长久之计。

  我们可以通过对应用访问峰值时堆空间利用率的分析来确定应用是否存在内存泄露,比如我们可以用JMeter来进行压力测试,我们每次对应用加压1000,一共加压10次,第一次峰值时堆使用了100M,第二次峰值时使用了200M,第三次峰值时使用了300M….那这样我们基本可以确定应用存在内存泄露。因为正常情况下,每次峰值时的堆占用率应该是差不多的,而上面的例子每次峰值时数据出入都比较大,而且是逐步增加,这不是一个正常的现象。

  观察内存的使用情况,你可以使用JConsole或者VisualVM等工具,我比较喜欢从GC的日志中得到我想要的信息,每次峰值时由于堆空间吃紧,肯定会触发一次GC,我通过这几次GC记录可以明了的看到堆内存情况。我们可以通过配置JVM参数来启用一些基本的GC日志,比如-verbose:gc、-XX:+PrintGCTimeStamps、-XX:+PrintGCDetails、-Xloggc:<file>。至于如何读GC日志,我博客里有其他文章讲解,Oracle官网也有比较好的例子。

  总结一下,如何确定应用存在内存泄露问题,我们需要观察峰值时的堆内存变化,比如堆的使用情况像下图一样,那肯定是存在Memory Leak了。

四. 如何定位引起内存泄露的代码

  首先我们可以看发生OOM时的代码,比如上面的例子,我们大概可以知道在执行哪段代码时发生了错误,然后重点看下这部分代码。当然,那部分代码不一定就是导致OOM的代码。

  接下来我们需要分析堆快照,可以为JVM配置发生OOM时出生堆快照文件(+XX:+HeapDumpOnOutOfMemoryError),或者使用jmap命令产生。注意生成堆快照文件时应用会停止运行,所以千万不要在生产环境中这么搞。

  拿到堆快照文件后,我们使用Mat或者VisualVM工具进行分析。借助这些工具,我们可以根据实例数、占用大小对目前堆中的所有实例进行排序,那排在前几位的就是你要重点分析的。

  前面讲的方法很容易就能找出大范围的Memory Leak代码,但是对于一些小的内存溢出问题,我们可能就比较难发现了,我的经验是先定位是哪些功能点引起的内存泄露,然后重点去压这部分功能,放大他们的影响之后再去分析。

 

 

 

4
6
分享到:
评论
4 楼 IXHONG 2016-06-30  
田梦桦 写道
涨姿势了,以前还真的不是特别清楚,看来架构师的命名这些还是有意义的
好的命名是架构师的自我修养
3 楼 IXHONG 2016-06-30  
tjzx 写道
能分别举例说明吗,比如写个可以运行的demo
可以,完全没问题
2 楼 田梦桦 2016-06-30  
涨姿势了,以前还真的不是特别清楚,看来架构师的命名这些还是有意义的
1 楼 tjzx 2016-06-30  
能分别举例说明吗,比如写个可以运行的demo

相关推荐

    今咱们来聊聊JVM 堆外内存泄露的BUG是如何查找的.docx

    。。。

    今咱们来聊聊JVM 堆外内存泄露的BUG是如何查找的.pdf

    。。。

    iOS面试 内存泄漏/基础知识

    2.内存泄漏可能会出现的几种原因,聊聊你的看法? 第一种可能:第三方框架不当使用; 第二种可能:block循环引用; 第三种可能:delegate循环引用; 第四种可能:NSTimer循环引用 第五种可能:非OC对象内存处理 第六...

    iOS内存泄漏监测自动化

    被指派的第一个任务是排查AppiOS版本存在的严重的内存泄漏的问题,原因是iOS10的某些系统bug(参考文章:聊聊苹果的Bug-iOS10nano_freeCrash)导致线上出现了较多的nano_free和nano_realloc的crash问题,而这些crash...

    Android性能优化之利用强大的LeakCanary检测内存泄漏及解决办法

    最近公司C轮融资成功了,移动团队准备扩大一下,需要招聘Android开发工程师,陆陆续续面试了几位Android应聘者,面试过程中聊到性能优化中如何避免内存泄漏问题时,很少有人全面的回答上来。所以决定抽空学习总结...

    腾讯三面以及参考思路

    内存泄漏可能会出现的几种原因,聊聊你的看法? 追问一:非OC对象如何处理? 追问二:若常用框架出现内存泄漏如何处理? 3.容错处理你们一般是注意哪些? 4.项目开始容错处理没做?如何防止拦截潜在的崩溃?

    golang面试题集合.zip

    简单聊聊内存逃逸? 字符串转成byte数组,会发生内存拷贝吗? http包的内存泄漏 sync.Map 的用法 Golang 理论 Go语言的GPM调度器是什么? Goroutine调度策略 goroutine调度器概述 Redis基础 Redis 基础数据结构 ...

    Golang 面试题汇编

    简单聊聊内存逃逸? 字符串转成byte数组,会发生内存拷贝吗? http包的内存泄漏 sync.Map 的用法 Golang 理论 Go语言的GPM调度器是什么? Goroutine调度策略 goroutine调度器概述 Redis基础 Redis 基础数据结构 ...

    一份超级详细的Java面试题【大厂面试真题+Java学习指南+工作总结】

    内存泄漏问题的分析和解决方案 程序员必备基础:加签验签 记一次接口性能优化实践总结:优化接口性能的八个建议 程序员必备基础:如何安全传输存储用户密码? 一次代码优化实践,用了模板方法+策略+工厂方法模式 ...

    Article::light_bulb:个人博客

    内存管理与内存泄漏思考题 深入js之内存管理与内存泄漏 深入js之深究ES6规范前后的执行上下文相关 2018 用 Vue 开发仿旅游站 webapp 项目总结 (下) 用 Vue 开发仿旅游站 webapp 项目总结 (上) 标注图 + 部分举例...

    interview-go:golang面试题集合

    简单聊聊内存逃逸? 字符串转成byte数组,会发生内存拷贝吗? http包的内存泄漏 Golang 理论 Go语言的GPM调度器是什么? Goroutine调度策略 goroutine调度器概述 Redis基础 Redis 基础数据结构 Redis中的底层数据...

    I need AV,Tomcat(翻译一下:我需要安慰,Tomcat)

    细聊才知道,他所负责的一个项目,生产环境上一个Tomcat开启了热加载模式,导致引起内存泄漏,造成生产事故,排查很长时间,具体细节就不讲了。另外说说这个Tomcat,其实是我同事养的一只小猫咪,由于他是做软件这一...

    sdkdemoapp3.0_android

    (2)有效避免生命周期组件内存泄漏。 (3)提高模块可测试性。 (4)提高应用稳定性,有效降低以下异常发生概率。 二、新项目对工具的要求: (1)Android Studio 3.2或更高版本。 (2)SDK targetVersion至少为26...

    AsyncAwaitBestPractices:System.Threading.Tasks.Task和System.Threading.Tasks.ValueTask的扩展

    : SafeFireAndForget 一种安全触发并忘记Task或ValueTask的扩展方法确保Task将重新抛出Exception如果一个Exception被抓IAsyncStateMachine.MoveNext() WeakEventManager 避免在未取消订阅事件时发生内存泄漏由...

Global site tag (gtag.js) - Google Analytics