Java内存管理第三篇 - 内存可能产生的问题

来源:互联网 发布:重庆时时彩杀号软件 编辑:程序博客网 时间:2024/05/22 17:04

Java内存在分配和回收的过程中会产品很多的问题,下面来说一说可能会产生的问题。


1、垃圾处理

      
        从程序运行的根节点出发,遍历整个对象引用,查找存活的对象。那么在这种方式的实现中,垃圾回收从哪儿开始的呢?即,从哪儿开始查找哪些对象是正在被当前系统使用的。上面分析的堆和栈的区别,其中栈是真正进行程序执行地方,所以要获取哪些对象正在被使用,则需要从Java栈开始。同时,一个栈是与一个线程对应的,因此,如果有多个线程的话,则必须对这些线程对应的所有的栈进行检查。

       同时,除了栈外,还有系统运行时的寄存器等,也是存储程序运行数据的。这样,以栈或寄存器中的引用为起点,我们可以找到堆中的对象,又从这些对象找到对堆中其他对象的引用,这种引用逐步扩展,最终以null引用或者基本类型结束,这样就形成了一颗以Java栈中引用所对应的对象为根节点的一颗对象树,如果栈中有多个引用,则最终会形成多颗对象树。在这些对象树上的对象,都是当前系统运行所需要的对象,不能被垃圾回收。而其他剩余对象,则可以视为无法被引用到的对象,可以被当做垃圾进行回收。
因此,垃圾回收的起点是一些根对象(java栈, 静态变量, 寄存器...)。而最简单的Java栈就是Java程序执行的main函数。这种回收方式,也是“标记-清除”的回收方式。

2、碎片处理
       
       由于不同Java对象存活时间是不一定的,因此,在程序运行一段时间以后,如果不进行内存整理,就会出现零散的内存碎片。碎片最直接的问题就是会导致无法分配大块的内存空间,以及程序运行效率降低。所以,“标记-整理”算法可以解决碎片的问题。

3、内存泄漏
    
   所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。java中有垃圾回收机制,它可以保证一对象不再被引用的时候,即对象编程了孤儿的时候,对象将自动被垃圾回收器从内存中清除掉。由于Java 使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的。
(1)为什么内存会泄露
       在下面的情境中,对象A引用了对象B. A的生命周期(t1 - t4) 比 B的(t2 - t3)要长得多. 当对象B在应用程序逻辑中不会再被使用以后, 对象 A 仍然持有着 B的引用. (根据虚拟机规范)在这种情况下垃圾收集器不能将 B 从内存中释放. 这种情况很可能会引起内存问题,假若A 还持有着其他对象的引用,那么这些被引用的(无用)对象都不会被回收,并占用着内存空间.
甚至有可能 B 也持有一大堆其他对象的引用。 这些对象由于被 B 所引用,也不会被垃圾收集器所回收. 所有这些无用的对象将消耗大量宝贵的内存空间。


典型举例:如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
又如:缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。
(2)避免内存泄漏
    检查java中的内存泄露,一定要让程序将各种分支情况都完整执行到程序结束,然后看某个对象是否被使用过,如果没有,则才能判定这个对象属于内存泄露。
1. 当心集合类,比如 HashMap,ArrayList等,因为这是最容易发生内存泄露的地方.当集合对象被声明为static时,他们的生命周期一般和整个应用程序一样长。
2. 注意事件监听和回调.当注册的监听器不再使用以后,如果没有被注销,那么很可能会发生内存泄露.
3. "当一个类自己管理其内存空间时,程序员应该注意内存泄露." 常常是一个对象的成员变量需要被置为null 时仍然指向其他对象,
(3)内存泄漏举例
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. import java.util.EmptyStackException;  
  2.   
  3. public class test {  
  4.     private Object[] elements = new Object[10];  
  5.     private int size = 0;  
  6.     public void push(Object e) {  
  7.         ensureCapacity();  
  8.         elements[size++] = e;  
  9.     }  
  10.     public Object pop() {  
  11.         if (size == 0)  
  12.             throw new EmptyStackException();  
  13.         return elements[--size];  
  14.     }  
  15.     private void ensureCapacity() {  
  16.         if (elements.length == size) {  
  17.             Object[] oldElements = elements;  
  18.             elements = new Object[2 * elements.length + 1];  
  19.             System.arraycopy(oldElements, 0, elements, 0, size);  
  20.         }  
  21.     }  
  22. }  

    上面的原理应该很简单,假如堆栈加了10个元素,然后全部弹出来,虽然堆栈是空的,没有我们要的东西,但是这是个对象是无法回收的,这个才符合了内存泄露的两个条件:无用,无法回收。

    但是就是存在这样的东西也不一定会导致什么样的后果,如果这个堆栈用的比较少,也就浪费了几个K内存而已,反正我们的内存都上G了,哪里会有什么影响,再说这个东西很快就会被回收的,有什么关系。下面看两个例子。

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. import java.util.Stack;  
  2. public class test3 {  
  3.     public static Stack<Object> s = new Stack<>();  
  4.     static {  
  5.         s.push(new Object());  
  6.         s.pop(); // 这里有一个对象发生内存泄露  
  7.         s.push(new Object()); // 上面的对象可以被回收了,等于是自愈了 } }  
  8.     }  
  9. }  

  因为是static,就一直存在到程序退出,但是我们也可以看到它有自愈功能,就是说如果你的Stack最多有100个对象,那么最多也就只有100个对象无法被回收其实这个应该很容易理解,Stack内部持有100个引用,最坏的情况就是他们都是无用的,因为我们一旦放新的进去,以前的引用自然消失!

内存泄露的另外一种情况:当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. import java.util.HashSet;  
  2. import java.util.Set;  
  3.   
  4. public class HashSetTest {  
  5.     public static void main(String[] args) {  
  6.         Set<Person> set = new HashSet<Person>();  
  7.         Person p1 = new Person("唐僧""pwd1"25);  
  8.         Person p2 = new Person("孙悟空""pwd2"26);  
  9.         Person p3 = new Person("猪八戒""pwd3"27);  
  10.         set.add(p1);  
  11.         set.add(p2);  
  12.         set.add(p3);  
  13.         System.out.println("总共有:" + set.size() + " 个元素!"); // 结果:总共有:3 个元素!  
  14.         p3.setAge(2); // 修改p3的年龄,此时p3元素对应的hashcode值发生改变  
  15.         set.remove(p3); // 此时remove不掉,造成内存泄漏  
  16.         set.add(p3); // 重新添加,居然添加成功  
  17.         System.out.println("总共有:" + set.size() + " 个元素!"); // 结果:总共有:4 个元素!  
  18.         for (Person person : set) {  
  19.             System.out.println(person);  
  20.         }  
  21.   
  22.     }  
  23. }  


[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. package test6;  
  2. public class Person {    
  3.     private String username;    
  4.     private String password;    
  5.     private int age;    
  6.     public Person(String username, String password, int age){    
  7.         this.username = username;    
  8.         this.password = password;    
  9.         this.age = age;    
  10.     }    
  11.     public String getUsername(){    
  12.         return username;    
  13.     }    
  14.     public void setUsername(String username)  {    
  15.         this.username = username;    
  16.     }    
  17.     public String getPassword() {    
  18.         return password;    
  19.     }    
  20.     public void setPassword(String password)  {    
  21.         this.password = password;    
  22.     }    
  23.     public int getAge()  {    
  24.         return age;    
  25.     }    
  26.     public void setAge(int age)   {    
  27.         this.age = age;    
  28.     }    
  29.     public int hashCode() {    
  30.         final int prime = 31;    
  31.         int result = 1;    
  32.         result = prime * result + age;    
  33.         result = prime * result + ((password == null) ? 0 : password.hashCode());    
  34.         result = prime * result + ((username == null) ? 0 : username.hashCode());    
  35.         return result;    
  36.     }    
  37.     public boolean equals(Object obj)  {    
  38.         if (this == obj)    
  39.             return true;    
  40.         if (obj == null)    
  41.             return false;    
  42.         if (getClass() != obj.getClass())    
  43.             return false;    
  44.         Person other = (Person) obj;    
  45.         if (age != other.age)    
  46.             return false;    
  47.         if (password == null)    {    
  48.             if (other.password != null)    
  49.                 return false;    
  50.         }    
  51.         else if (!password.equals(other.password))    
  52.             return false;    
  53.         if (username == null)  {    
  54.             if (other.username != null)    
  55.                 return false;    
  56.         }    
  57.         else if (!username.equals(other.username))    
  58.             return false;    
  59.         return true;    
  60.     }    
  61.     public String toString()  {    
  62.         return this.username+"-->"+this.password+"-->"+this.age;    
  63.     }    
  64.  }    
输出结果如下:
总共有:3 个元素!
总共有:4 个元素!
猪八戒-->pwd3-->2
孙悟空-->pwd2-->26
唐僧-->pwd1-->25
猪八戒-->pwd3-->2
0 0
原创粉丝点击