JVM内存泄漏跟踪

什么是内存泄漏: 无用的对象持续的占用内存或无用对象的内存得不到释放。(也可以说是: 一个对象已不再被任何程序逻辑所引用、需要, 但是还存在着跟对象GCRoots的引用)

内存泄漏的根本原因: 长生命周期的对象持有短生命周期的对象的引用, 尽管短生命周期的对象已经不在需要了, 但因为长生命周期持有它的引用而导致不可回收。

案例分析:

程序运行越来越慢, 或者出现OOM, 初步怀疑是出现了内存泄漏现象。

 

测试代码:

package test;

import java.util.HashMap;
import java.util.Map;


public class demo {
	public static class TestMemory {
		
		public byte[] M_64Array =new byte[64*1024];
	}
	
	
	    //声明缓存对象
	    private static final Map map = new HashMap();
	    public static void main(String args[]){
	        try {
	            Thread.sleep(10000);//给打开visualvm时间
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	        System.out.println("开始");
	        //循环添加对象到缓存
	        for(int i=0; i<100000;i++){
	        	System.out.println(i);
	            TestMemory t = new TestMemory();
	            map.put("key"+i,t);
	        }
	        System.out.println("first");
	        //为dump出堆提供时间
	        try {
	            Thread.sleep(10000);
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	        for(int i=0; i<100000;i++){
	        	System.out.println(i);
	            TestMemory t = new TestMemory();
	            map.put("key"+i,t);
	        }
	        System.out.println("second");
	        try {
	            Thread.sleep(10000);
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	        for(int i=0; i<300000;i++){
	        	System.out.println(i);
	            TestMemory t = new TestMemory();
	            map.put("key"+i,t);
	        }
	        System.out.println("third");
	        try {
	            Thread.sleep(10000);
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	        for(int i=0; i<400000;i++){
	        	System.out.println(i);
	            TestMemory t = new TestMemory();
	            map.put("key"+i,t);
	        }
	        System.out.println("forth");
	        try {
	            Thread.sleep(Integer.MAX_VALUE);
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	        System.out.println("qqqq");
	    }
}

1.使用命令行排查

(1)使用jps指令, 查看虚拟机的当前进程

jps -l

得到进程的ID为 12592

 

(2)使用jstat指令查看该进程gc情况

jstat -gcutil 12592 250 7

发生Full GC的次数较多,共4次

 

(3)使用jmap指令, 查看各个类占用内存的状况

jmap -histo:live 12592

可以看出, 某个类占的内存特别大, 这很不正常, 接下来就是找到这个类

 

(4)生成heap dump文件

方法一: jmap -dump:format=b,file=heap.bin 12592 ( b代表文件格式为二进制, file代表文件名)

方法二: 使用JDK /bin目录下的jvisualvm 可视化工具生成

 

(5)使用MAT 对heap dump文件分析

蓝色代表被占用空间, 发现99%的空间被占用了, 继续往下追踪, 看看到底是哪个类

 

 

最后发现是test.demo下的 HashMap中的类发生内存泄漏

for(int i=0; i<10000;i++){
	        	System.out.println(i);
	            TestMemory t = new TestMemory();
	            map.put("key"+i,t);//此处对象发生内存泄漏
	        }

 

 

不用命令行也可以用其他工具来判断是否发生内存泄漏, 如JDK /bin目录下的 可视化工具 jconsole和jvisualvm

堆内存使用情况:

 

Old区使用情况:

 

 

通过多次观察发现, 一般来说堆内存图像如上图所示(呈上升趋势折线图), 同时出现GC掉的对象越来越少的情况, 则很有可能发生了内存泄漏

 

 

内存泄漏带来的问题:

1.应用可用的内存减小, 增加了对内存的压力。

2.降低了应用的性能, 比如触发更频繁的GC。

3.严重的时候可能会导致内存溢出错误, 即OOM。

对于程序员来说, GC基本是透明的, 不可见的。 不同的JVM的实现可能使用不同的算法管理GC。通常, GC线程的优先级较低, JVM调用的GC策略也有很多种, 有的是内存使用达一定程度而GC, 也有的是定时执行, 有的是平缓执行, 还有中断式GC。

 

内存泄漏发生的场景:

1.静态集合类引发的内存泄漏

static vector c=new vector(10);
for(int i=0;i<100;i++){
    Object o=new Object();
    v.add(o);
    o=null; //即使将对象o置空, 但是对象依然被集合v所引用, 故对象不可回收, 发生内存泄漏
}

解决办法, 将对象从集合v中移除, 或者直接将集合置空 v=null

 

2.当集合里的对象属性被修改(对象的hashcode()发生了改变)后, 在调用remove()方法无效

public static void main(String[] args){
    Set<Person> set=new HashSet<Person>;
    Person p1=new Person("刘昊然",25);
    Person p2=new Person("李钟硕",27);
    Person p3=new Person("陈秋婷",20);
    set.add(p1);
    set.add(p2);
    System.out.println("共:"+set.size()+" 个元素!"); //结果:2
    p3.setAge(24); //修改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.监听器, 开了没关闭

 

4.各种连接, 数据库连接, 网络连接等等, 打开了没关闭, 除非显式的调用了close()方法, 否则是不会被GC掉的

 

5. 单例模式

什么是单例模式?  确保一个类只有一个实例, 自行提供这个实例并向整个系统提供这个实例。

特点: (1) 一个类只能有一个实例

         (2) 自己创建这个实例

         (3) 整个系统都使用这个实例

注意: 不正确的使用单例模式是引起内存泄漏的常见问题, 单例对象在初始化后将在JVM的整个生命周期中存在; 比如单例中引用了外部对象;

以上是Java后台的

 

下面这些是安卓的(ps: 没整过安卓, 查资料看到安卓其实有好多种内存泄漏的方式, 但是我能理解并记住的就下面三种(T T))

1. 集合类泄漏

    集合类仅有添加元素, 而无删除元素; 集合类是全局性变量, 无删除机制;

2. 单例(单例的静态特性, 使其生命周期和应用的生命周期一样长)

    单例中调用了其他对象, 则其他对象的生命周期和应用的一样长;

3. 尽量避免使用static 成员变量

    如果成员变量被声明为static , 那我们都知道其生命周期与整个app 进程的生命周期一样; 如果你的app 进程设计上是长驻内 存,   那即使app切到后台, 这部分内存也不会被释放;

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值