程序员人生 网站导航

单例模式讨论篇:单例模式与垃圾回收

栏目:综合技术时间:2017-02-04 09:30:54

  Jvm的垃圾回收机制到底会不会回收掉长时间不用的单例模式对象,这的确是1个比较有争议性的问题。将这1部份内容单独成篇的目的也是为了与广大博友广泛的讨论1下这个问题。为了能让更多的人看到这篇文章,请各位博友看完文章以后,点1下“顶”,让本篇文章排名尽可能的靠前。笔者在此谢过。

讨论命题:当1个单例的对象久长不用时,会不会被jvm的垃圾搜集机制回收。

        首先说1下为何会产生这1疑问,笔者本人再此之前历来没有斟酌过垃圾回收对单例模式的影响,直到去年读了1本书,《设计模式之禅》秦小波著。在书中提到在j2ee利用中,jvm垃圾回收机制会把久长不用的单例类对象当作垃圾,并在cpu空闲的时候对其进行回收。之前读过的几本设计模式的书,包括《Java与模式》,书中都没有提到jvm垃圾回收机制对单例的影响。并且在工作进程中,也没有过单例对象被回收的经历,加上工作中很多先辈曾告诫过笔者:尽可能不要声明太多的静态属性,由于这些静态属性被加载后不会被释放。因此对jvm垃圾搜集会回收单例对象这1说法持怀疑态度。渐渐地,发现在同事中和网上的技术人员中,对这1问题也基本上是鲜明的对峙两派。那末到底jvm会不会回收久长不用的单例对象呢。

        对这1问题,笔者本人的观点是:不会回收。

下面给出本人的测试代码

[java] view plain copy
  1. class Singleton {  
  2.     private byte[] a = new byte[6*1024*1024];  
  3.     private static Singleton singleton = new Singleton();  
  4.     private Singleton(){}  
  5.       
  6.     public static Singleton getInstance(){  
  7.         return singleton;  
  8.     }  
  9. }  
  10.   
  11. class Obj {  
  12.     private byte[] a = new byte[3*1024*1024];  
  13. }  
  14.   
  15. public class Client{  
  16.     public static void main(String[] args) throws Exception{  
  17.         Singleton.getInstance();  
  18.         while(true){  
  19.             new Obj();  
  20.         }  
  21.     }  
  22. }  

        本段程序的目的是摹拟j2ee容器,首先实例化单例类,这个单例类占6M内存,然后程序进入死循环,不断的创建对象,逼迫jvm进行垃圾回收,然后视察垃圾搜集信息,如果进行垃圾搜集后,内存依然大于6M,则说明垃圾回收不会回收单例对象。

        运行本程序使用的虚拟机是hotspot虚拟机,也就是我们使用的最多的java官方提供的虚拟机,俗称jdk,版本是jdk1.6.0_12

        运行时vm arguments参数为:-verbose:gc -Xms20M -Xmx20M,意思是每次jvm进行垃圾回收时显示内存信息,jvm的内存设为固定20M。

运行结果:

……

[Full GC 18566K->6278K(20352K), 0.0101066 secs]

[GC 18567K->18566K(20352K), 0.0001978 secs]

[Full GC 18566K->6278K(20352K), 0.0088229 secs]

……

        从运行结果中可以看到总有6M空间没有被搜集。因此,笔者认为,最少在hotspot虚拟机中,垃圾回收是不会回收单例对象的。

        后来查阅了1些相干的资料,hotspot虚拟机的垃圾搜集算法使用根搜索算法。这个算法的基本思路是:对任何“活”的对象,1定能终究追溯到其存活在堆栈或静态存储区当中的援用。通过1系列名为根(GC Roots)的援用作为出发点,从这些根开始搜索,经过1系列的路径,如果可以到达java堆中的对象,那末这个对象就是“活”的,是不可回收的。可以作为根的对象有:

  • 虚拟机栈(栈桢中的本地变量表)中的援用的对象。
  • 方法区中的类静态属性援用的对象。
  • 方法区中的常量援用的对象。
  • 本地方法栈中JNI的援用的对象。

        方法区是jvm的1块内存区域,用来寄存类相干的信息。很明显,java中单例模式创建的对象被自己类中的静态属性所援用,符合第2条,因此,单例对象不会被jvm垃圾搜集。

        虽然jvm堆中的单例对象不会被垃圾搜集,但是单例类本身如果长时间不用会不会被搜集呢?由于jvm对方法区也是有垃圾搜集机制的。如果单例类被搜集,那末堆中的对象就会失去到根的路径,必定会被垃圾搜集掉。对此,笔者查阅了hotspot虚拟机对方法区的垃圾搜集方法,jvm卸载类的判定条件以下:

  • 该类所有的实例都已被回收,也就是java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已被回收。
  • 该类对应的java.lang.Class对象没有任何地方被援用,没法在任何地方通过反射访问该类的方法。

        只有3个条件都满足,jvm才会在垃圾搜集的时候卸载类。明显,单例的类不满足条件1,因此单例类也不会被卸载。也就是说,只要单例类中的静态援用指向jvm堆中的单例对象,那末单例类和单例对象都不会被垃圾搜集,根据根搜索算法,对象是不是会被垃圾搜集与未被使用时间长短无关,仅仅在于这个对象是否是“活”的。假设1个对象久长未使用而被回收,那末搜集算法应当是最近最长未使用算法,最近最长未使用算法1般用在操作系统的内外存交换中,如果用在虚拟机垃圾回收中,岂不是太不安全了?以上是笔者的观点。

        因此笔者的观点是:在hotspot虚拟机1.6版本中,除非人为地断开单例中静态援用到单例对象的联接,否则jvm垃圾搜集器是不会回收单例对象的。

        期待各位博友的发言。

 

参考文献

Java虚拟机规范

Java hotspot虚拟机内存管理

Java编程思想

Java与模式

设计模式

设计模式之禅

深入理解java虚拟机

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐