疯狂java笔记:java的内存回收

对象状态

  1. jvm垃圾回收机制是否回收一个对象,取决于是否还有引用变量指向该对象。
  2. 对象引用可以理解成有向图,引用变量指向对象,构成从一点可以到达另一点的可到达状态。
  3. 根据有向图的状态,可以将堆内存中的java对象分为三种状态:
    1).可到达状态,即在有向图中可以导航到该对象。
    2).可恢复状态,指没任何引用变量指向该对象,处于等待jvm调用其finalize方法清理资源。如果在finalize方法调用期间出现了指向该对象的引用变量,则该对象恢复成可到达状态,否则变成不可恢复状态。
    3).不可恢复状态,系统会真正开始回收该对象所占用的资源。

引用类型

  1. 强引用:创建一个对象并把这个对象赋值到一个引用变量,这个引用变量就是强引用。
  2. 软引用:通过java.lang.ref包中的SoftReference类实现,该类引用变量只有在系统内存不足时才回收这些引用类型对象变量的内存空间。
  3. 弱引用:通过WeakReference类实现,其引用级别比弱引用低,启动垃圾回收机制后该类引用的对象所占内存一定被回收。与其功能类似的还有WeakHashMap,在gc启动后回收没有强引用到的键值对,即清空该类型的Map。
  4. 虚引用:通过PhantomReference类实现,完全类似没有引用,需要配合ReferenceQueue使用。使用虚引用的对象只有在垃圾回收机制运行后,才会被加入到与该虚引用关联的引用队列中去。

关于java的内存泄露

  1. java的内存泄露产生根本原因是:程序中有一些java对象,它们处于可到达状态,但程序以后都不会使用到它们,而且它们的所占内存也不会被回收,那么它们所占用的空间就可以算是内存泄露了。
  2. 对应程序中不会在使用的对象,要在程序中赋值成null。
  3. 可用内存检测工具来监控内存的使用,这些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。
  4. IBM上说了:Java也存在内存泄露问题,其原因主要是一些对象虽然不再被使用,但它们仍然被引用。
  5. 产生原因有:静态集合类,物理连接,监听器。

垃圾回收机制

  • 垃圾回收基本算法
    1.串行回收:一个CPU执行垃圾回收;
    2.并行回收:多个CPU执行垃圾回收,缺点是易产生内存碎片。
    3.并发执行:需要解决与应用程序在回收过程中修改对象的冲突问题,系统开销比较高,需要更多堆内存。
    4.应用程序停止:回收过程中会导致应用程序停止执行。Stop-the-world
    5.压缩:把活动的对象迁移到所在内存区域一段连续的范围中去,把剩余的内存区域释放。
    6.不压缩:仅仅把垃圾内存清空,最后导致内存碎片增多。
  • 具体实现方法:
    1.复制:把堆内存分为两个区域,把从跟可到达的对象全部复制到新的内存区域,然后把原先的内存区域回收。
    2.标记清除:把从根可到达的对象全部标记为可到达状态,然后遍历整块内存,把没有标记的内存回收掉。
    3.标记压缩:把从根可到达的对象全部标记为可到达状态,然后把已标记的对象迁移到所在内存区域一段连续的范围中去,再清除原先内存块中不可到达的对象所占用的空间,可避免内存碎片的产生。
  • 堆内存分代回收
    1.分代依据:对象生成时间长短
    2.分为以下三代:
    1).Yong代:由一个Eden,两个Survivor构成。新生对象存在于Eden代内存区域中,Survivor代中保持至少经历一次垃圾回收的对象。由于Yong代对象一般比较少,所以采用复制的算法来回收内存,所以在每次垃圾回收时第二个survivor代区域就是用于保存Eden区和第一个Survivor区里面的可到达的对象,然后清空Eden区和Survivor区1。.
    2).Old代:经过多次垃圾回收依旧坚挺的存在Yong区可到达对象,将迁移到Old区。该区垃圾回收主要采用标记压缩算法。
    3).Permanent代:默认装载Class,方法等信息,默认64M,垃圾回收通常不处理这部分内存。
  • 常见的垃圾回收器:
    1.串行回收器
    2.并行回收器
    3.并行压缩回收器
    4.并发标记-清理回收器

内存管理小技巧:

  1. 尽量使用直接量,可以使用基本类型就不要使用对应的包装类,可以使用字符常量就不要new一个String。
  2. 字符拼接不要使用+,尽量使用StringBuilder和StringBuffer。
  3. 尽早释放无用对象,赋值null最为常见。
  4. 尽量少用静态变量。
  5. 避免在常用方法或循环中创建java对象。
  6. 缓存经常使用到的对象
  7. 尽量不要使用finalize方法,不要企图覆写finalize来实现清理资源,以免加重垃圾回收的开销
  8. 考虑使用软引用。

 

软引用实验代码:

import java.lang.ref.*; import java.util.*; class Fish{ private int id; private String type; public Fish(int id,String type){ this.id = id ; this.type = type; } @Override public String toString(){ return this.id + " - " + this.type; } } public class SoftReferenceTest{ private final static int TEST_COUNT = 10000; public static void main(String[] args) throws Exception{ int count = inputTestCount(); SoftReference<Fish>[] fishs = new SoftReference[count==0?TEST_COUNT:count]; for(int i = 0 ;i <fishs.length; i++){ fishs[i] = new SoftReference<Fish>(new Fish(i,"GoldFish")); } showEndPointVal(fishs); //启动垃圾回收 System.gc(); //调用所有new出来的对象其finalize方法,使其变为不可达状态 System.runFinalization(); //执行命令:java -Xmx2m -Xms2m SoftReferenceTest showNullPos(fishs); showEndPointVal(fishs); } private static int inputTestCount(){ System.out.print("输入数组大小:"); Scanner inputReciver = new Scanner(System.in); String numStr = inputReciver.nextLine(); return Integer.parseInt(numStr); } private static void showNullPos(SoftReference<Fish>[] fishs){ boolean findNullPos = false; for(int i = 0 ;i <fishs.length; i++){ if(!findNullPos && null == fishs[i].get()){ System.out.print("该范围下标的数组元素为null:"+i); findNullPos = true; }else if(findNullPos && null != fishs[i].get()){ System.out.println(" - " + (i-1)); findNullPos = false; } } } private static void showEndPointVal(SoftReference<Fish>[] fishs){ System.out.println("fishs[0] = " + fishs[0].get()); //输入1000000 //System.out.println(fishs[1303].get()); System.out.println("fishs["+(fishs.length-1)+"] = " +fishs[fishs.length-1].get()); /* * 当内存不足时,软引用的变量会释放对象内存,设成null。 * */ } }

 

弱引用实验源码:

import java.lang.ref.*; public class WeakReferenceTest{ public static void main(String[] args){ //不要定义成String str ="那些情歌是我为你唱过"; //这样定义会导致jvm将其缓存在字符串常量池中。 String str = new String("那些情歌是我为你唱过"); WeakReference<String> weakRef = new WeakReference<String>(str); //切断str引用和字符串之间的引用 str = null; //依然可以打印出"那些情歌是我为你唱过" System.out.println(weakRef.get()); //强制启动垃圾回收机制 System.gc(); System.runFinalization(); //只能打印出null了,因为该字符串对象被清理了。 System.out.println(weakRef.get()); } }

 

虚引用实验源码:

import java.lang.ref.*; public class PhantomReferenceTest{ public static void main(String[] args){ String v = new String("那些情歌是我为你唱过"); ReferenceQueue<String> rq = new ReferenceQueue<String>(); PhantomReference<String> pr = new PhantomReference<String> (v,rq); v = null; //虚引用极其类似没有引用,不能通过虚引用访问到被引用的对象,输出null System.out.println(pr.get()); System.gc(); System.runFinalization(); //取出最先进入队列的引用与pr进行比较 System.out.println(rq.poll() == pr); } }

 

内存泄露实验源码:

List<Object> v=new ArrayList<Object>(); for (int i=1;i<100000; i++) { Object o=new Object(); v.add(o); o=null; } //要这样才能释放内存 //v=null;

你可能感兴趣的:(疯狂java笔记:java的内存回收)