一、在理解垃圾回收前需要了解一个对象在堆内存中的引用状态,分为三种:
1.可达状态:对象创建后,有一个或以上的引用变量引用它。
2.可恢复状态:程序中某个对象不再有任何引用变量引用它,但是还是有可能重新被其他引用变量引用。
3.不可达状态:对象与所用引用变量的引用都被切断,且jvm执行finalize()方法进行资源清理后没有成为可达状态,那么这个对象将永久的失去引用,编程不可达状态。(GC真正回收的正是处于这种状态的对象)。
public void test(){ //此处对象为可达状态 String a=new String("this is a string object"); //此处a引用只想了另一个变量,则上面的string对象成为可恢复状态 a=new String("another string object"); }
二、垃圾回收有如下机制:
1.垃圾回收只负责回收堆内存中的对象,不会回收任何物理资源(如:数据库连接,网络IO等)。
2.程序无法精确控制垃圾回收的运行,垃圾回收只有再合适的时候进行。对象成为不可达状态后,JVM会再合适的时候回收该对象。
3.垃圾回收任何对象前之前总会调用这个对象的finalize方法,进行资源清理。finalize方法可能使该对象重新成为可达状态,从而使使JVM取消对它的回收。
三、强制垃圾回收:
强制垃圾回收其实是建议JVM立即进行GC,但JVM是否进行gc操作是不确定的,垃圾回收机制只是在接收到通知尽快进行垃圾回收。强制垃圾回收有如下两种方式,
System.gc();
Runtime.getRuntime().gc();
四、理解finalize方法:
系统在垃圾回收前总会调用finalize方法进行资源清理,这个操作和垃圾回收操作是一起发生但要先于GC。只有程序任务需要额外的内存时才会进行GC。finalize有如下特点:
1.不要主动调用finalize方法,该方法应交给垃圾回收机制调用。
2.finalize方法何时执行无法确定。
3.JVM执行finalize方法时,有可能将对象从可恢复状态变成可达状态。
4.JVM执行finalize方法时,垃圾回收机制不会报错,程序继续执行。
如下例子说明垃圾回收的不确定性:
public class FinalizeTest { private static FinalizeTest ft=null; public void info(){ System.out.println("调用系统的finalize方法"); } public static void main(String[] args) throws InterruptedException { //创建对象语句也会进行垃圾回收 new FinalizeTest(); //通知系统立即进行垃圾回收 System.gc();//1 // Thread.sleep(2000);//3 ft.info(); } //重写了finalize方法 public void finalize(){ ft=this; } }
上述例子中注释掉3处代码可以发现程序报空指针异常,取消注释后,程序打印“ 调用系统的finalize方法”,说明程序显示执行垃圾回收,但finalize方法不会立即执行,当延迟2s后发现垃圾回收执行了。如果在1和3处代码中间加入下列代码则可强制调用finalize方法。
//强制垃圾回收调用可恢复对象的finalize方法,执行此方法前一定先通知系统进行垃圾回收操作 // Runtime.getRuntime().runFinalization(); System.runFinalization();//2
五、是否应该在程序中显示调用垃圾回收?
首先你需要知道两个概念:
JVM的gc有minor GC和major GC(也就是大家说的Full GC),JVM会很频繁的做minor GC,如果内存块占满的话,JVM会做Full GC,Full GC是对整个JVM内存堆做GC,所示耗时比minor GC要长很多。System.gc()最终执行的是Full GC
1,服务器老是崩溃是因为内存很快被占满,在允许的条件下你可以适当加大内存配置,用mx和ms控制内存堆大小
-Xms
-Xmx
2,如果你的程序需要通过System.gc()来减少JVM崩溃的几率,那么你的程序10有8,9存在问题,需要从你程序本身去进行优化
3,可以把GC配置为CMS回收方式,以提高回收效率
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+UseParNewGC
-XX:CMSFullGCsBeforeCompaction=0
-XX:CMSInitiatingOccupancyFraction=75
-XX:+CMSParallelRemarkEnabled
-XX:+CMSPermGenSweepingEnabled
-XX:+CMSClassUnloadingEnabled
4,-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:+PrintHeapAtGC用这些参数输出GC日志,然后分析,
通过上述描述可知道,System.gc()最终执行的是Full GC,执行时会对整个JVM堆内存做GC,这将给程序带来一个严重的性能损失。一般不建议这么做。
六:为防止内存泄露,对象引用用完后都清空?
清空对象引用应该是一种例外,而不是一种规范行为,这样做会使代码很乱,一般而言只要类是自己管理内存,程序员就应该警惕内存泄露问题。
要防止内存泄露,下面是一些快速上手的实用技巧:
1. 当心集合类,比如 HashMap,ArrayList等,因为这是最容易发生内存泄露的地方.当集合对象被声明为static时,他们的生命周期一般和整个应用程序一样长。
2. 注意事件监听和回调.当注册的监听器不再使用以后,如果没有被注销,那么很可能会发生内存泄露.
3. "当一个类自己管理其内存空间时,程序员应该注意内存泄露." 常常是一个对象的成员变量需要被置为null 时仍然指向其他对象。
下面举一个模拟栈的例子:
public class Stack { private Object[] elements; private int size=0; private static int CAPACITY=16; public Stack(){ elements=new Object[CAPACITY]; } public void push(Object e){ ensureCapacity(); elements[size++]=e; } public Object pop(){ if(size==0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity(){ if(elements.length==size){ elements=Arrays.copyOf(elements, 2*size+1); } } }
此例子会有一个内存泄露的现象,从栈中弹出来的对象不会当做垃圾回收,即使栈不再引用这些对象,这是因为,栈内部还维护者这些弹出对象的过期引用。所谓过期引用是指永远不会被解除的引用。随着时间推移,无法回收对象越来越多,程序性能越来越差最后可能导致内存溢出。此时可以做如下处理:
public Object pop2(){ if(size==0) throw new EmptyStackException(); Object result= elements[--size]; elements[size]=null; return result; }
一旦数组元素变成非活动的一部分,就手动清空这些元素。
七、对象的软、弱和虚引用,
正常程序中的引用变量为强引用。当程序中需要避免在执行期间将对象留在内存中,可以使用如下几种引用:
1.软引用:当系统内存空间不足时,会回收它,但空间足够时,不会回收,程序也可以使用改对象。
2.弱引用:跟软引用类似,比软引用级别耕地,当GC操作时,不管内存是否足够,对象总会回收。
3.虚引用:和没有引用效果大致相同,主要用于跟踪对象垃圾回收状态,不能单独使用,必须和引用队列(ReferenceQueue)联合使用。