JVM内存泄漏跟踪

 https://blog.csdn.net/chenchenQT/article/details/88776208

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

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

案例分析:

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

测试代码:

  1. package test;

  2.  
  3. import java.util.HashMap;

  4. import java.util.Map;

  5.  
  6.  
  7. public class demo {

  8. public static class TestMemory {

  9.  
  10. public byte[] M_64Array =new byte[64*1024];

  11. }

  12.  
  13.  
  14. //声明缓存对象

  15. private static final Map map = new HashMap();

  16. public static void main(String args[]){

  17. try {

  18. Thread.sleep(10000);//给打开visualvm时间

  19. } catch (InterruptedException e) {

  20. e.printStackTrace();

  21. }

  22. System.out.println("开始");

  23. //循环添加对象到缓存

  24. for(int i=0; i<100000;i++){

  25. System.out.println(i);

  26. TestMemory t = new TestMemory();

  27. map.put("key"+i,t);

  28. }

  29. System.out.println("first");

  30. //为dump出堆提供时间

  31. try {

  32. Thread.sleep(10000);

  33. } catch (InterruptedException e) {

  34. e.printStackTrace();

  35. }

  36. for(int i=0; i<100000;i++){

  37. System.out.println(i);

  38. TestMemory t = new TestMemory();

  39. map.put("key"+i,t);

  40. }

  41. System.out.println("second");

  42. try {

  43. Thread.sleep(10000);

  44. } catch (InterruptedException e) {

  45. e.printStackTrace();

  46. }

  47. for(int i=0; i<300000;i++){

  48. System.out.println(i);

  49. TestMemory t = new TestMemory();

  50. map.put("key"+i,t);

  51. }

  52. System.out.println("third");

  53. try {

  54. Thread.sleep(10000);

  55. } catch (InterruptedException e) {

  56. e.printStackTrace();

  57. }

  58. for(int i=0; i<400000;i++){

  59. System.out.println(i);

  60. TestMemory t = new TestMemory();

  61. map.put("key"+i,t);

  62. }

  63. System.out.println("forth");

  64. try {

  65. Thread.sleep(Integer.MAX_VALUE);

  66. } catch (InterruptedException e) {

  67. e.printStackTrace();

  68. }

  69. System.out.println("qqqq");

  70. }

  71. }

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中的类发生内存泄漏

 
  1. for(int i=0; i<10000;i++){

  2. System.out.println(i);

  3. TestMemory t = new TestMemory();

  4. map.put("key"+i,t);//此处对象发生内存泄漏

  5. }

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

堆内存使用情况:

Old区使用情况:

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

内存泄漏带来的问题:

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

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

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

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

内存泄漏发生的场景:

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

 
  1. static vector c=new vector(10);

  2. for(int i=0;i<100;i++){

  3. Object o=new Object();

  4. v.add(o);

  5. o=null; //即使将对象o置空, 但是对象依然被集合v所引用, 故对象不可回收, 发生内存泄漏

  6. }

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

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

 
  1. public static void main(String[] args){

  2. Set<Person> set=new HashSet<Person>;

  3. Person p1=new Person("刘昊然",25);

  4. Person p2=new Person("李钟硕",27);

  5. Person p3=new Person("陈秋婷",20);

  6. set.add(p1);

  7. set.add(p2);

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

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

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

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

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

  15. for (Person person : set)

  16. {

  17. System.out.println(person);

  18. }

  19. }

3.监听器, 开了没关闭

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

5. 单例模式

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

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

         (2) 自己创建这个实例

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

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

以上是Java后台的

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

1. 集合类泄漏

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

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

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

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

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

猜你喜欢

转载自blog.csdn.net/It_BeeCoder/article/details/90743445
今日推荐