jvm 内存泄漏和内存溢出

1、内存溢出和内存泄漏是什么

内存溢出(out of memory)

是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

通俗说法就是去蹲坑发现坑位满了。一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出!又比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。分配的内存不足以放下数据项序列,称为内存溢出。

内存泄露(memory leak)

是指程序在申请内存后,无法释放已申请的内存空间。

内存泄漏的定义比较难理解,通俗说法就是有人占着茅坑不拉屎。就是说,你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),而你自己出于某些原因不能再访问到那块内存(也许你把它的地址给弄丢了),这时候系统也不能再次将它分配给需要的程序。

注意:

即一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。memory leak会最终会导致out of memory!

2、内存泄漏的分类

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

  • 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。 
  •  偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
  • 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。 
  •  隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。 

从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。上文也有说到,真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。所以我等程序员们在写代码的时候要养成好的习惯哦(在这一点上,Java的回收机制就做的很好)。

3、内存溢出的原因以及解决方法

内存溢出常见的原因

  • 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
  • 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
  • 代码中存在死循环或循环产生过多重复的对象实体;
  • 使用的第三方软件中的BUG;
  • 启动参数内存值设定的过小

定位到问题,自然也就有解决办法了。

内存溢出的解决方案

第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)

第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。

第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

第四步,使用内存查看工具动态查看内存使用情况。

其中,第三步重点排查以下几点:

  • 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
  • 检查代码中是否有死循环或递归调用。
  • 检查是否有大循环重复产生新对象实体。
  • 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
  • 检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

4、内存泄露分析

4.1 下载mat

下载地址:Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation

jvm 内存泄漏和内存溢出_第1张图片

4.2 内存泄漏demo

package com.ybw.memory.leak.demo.test2;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

/**   
 * @Title: DumpTest.java 
 * @ProjectName docker-test
 * @Description:  
 * @author ybwei   
 * @date 2019年2月11日 下午7:46:28    
 */
@Slf4j
public class DumpTest {
	public void init() {
		List list = new ArrayList<>();
		int i = 0;
		while (true) {
			Person person = new Person();
			list.add(person);
		}
	}
	public static void main(String[] args) {
		DumpTest dumpTest = new DumpTest();
		dumpTest.init();
	}
}
 
class Person{

}

4.3 运行

vm参数配置

jvm 内存泄漏和内存溢出_第2张图片

-Xms4m -Xmx4m -Xmn2m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/1/dump2.hprof
  • -xms:初始内存
  • -Xmx:JVM最大可用内存
  • -xmn年轻代大小
  • -XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集
  • -XX:+HeapDumpOnOutOfMemoryError在堆内存溢出是保存快照
  • -xx:heapDumpPath设置快照的路径

运行后,生成dump2.hprof

4.4 eclipse mat分析

eclipse的file-》open file 找到指定生成的hprof文件位置打开,生成下图报表

jvm 内存泄漏和内存溢出_第3张图片

4.4.1 leak suspects

leak suspects可以查看疑似内存泄漏的地方,点击detail可以看到分析出ArryList 存放对象Person出了问题。

jvm 内存泄漏和内存溢出_第4张图片

jvm 内存泄漏和内存溢出_第5张图片

4.4.2 dominator_tree

在overview中点击dominator_tree查看堆占比,可以看出一个线程占比过高,集合中对象的数量过多

jvm 内存泄漏和内存溢出_第6张图片

4.5 jvisualvm分析

 jvisualvm为jdk自带工具

4.5.1 装入hprof

jvm 内存泄漏和内存溢出_第7张图片

 jvm 内存泄漏和内存溢出_第8张图片

4.5.2 定位错误位置

jvm 内存泄漏和内存溢出_第9张图片

 jvm 内存泄漏和内存溢出_第10张图片

4.5.3 查看类

一共创建71141个实例,消耗了72.1%的运行内存。

jvm 内存泄漏和内存溢出_第11张图片

切换到实例数如下图所示:

jvm 内存泄漏和内存溢出_第12张图片

你可能感兴趣的:(jvm,面试,jvm)