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切到后台, 这部分内存也不会被释放;