java有垃圾回收机制,内存回收-java中手动回收垃圾
一、GC的特性
GC拥有Java语言特有的一个特性,即对Java内存中的堆(heap)空间的开辟内存和释放内存的操作,均不用程序员自己来操作。
我们将GC的操作与C++中malloc函数(开辟内存),free函数(释放内存)进行一些比较。这些函数在C++中均需要程序员自己进行调用,当一块内存被回收之后,若仍然存在指向这块内存的指针变量,我们对这个指针进行访问的时候,就会出现大家熟知的野指针了(即指针指向了一块已经被回收的内存)。
而在Java引入了GC之后java有垃圾回收机制,内存回收,我们就不需要指针这个东西了,(狂喜),当我们用了堆中的内存之后(即创建对象),当这个对象没有任何一个指向它的引用变量时,JVM会默认这个对象成为了垃圾,最后将他回收。
GC的缺陷:正是因为Java避免了程序员直接操作内存,减少了一些风险,所以Java语言总是比C++语言耗费更多内存,更低效。
二、垃圾检测算法
1、引用计数法
当我们创建一个对象时,给这个对象一个int类型的counter计数器,统计当前指向这个对象的引用变量数目,当一个引用变量失效时,counter–。这个算法十分简单高效。但是有一个致命缺陷。
当我们创建了一个对象A,一个对象B,两个对象中存在互相指向对方的成员变量。这样就成了一个环。而引用计数法的思想无法破解环!
2、根搜索算法(GC)
以根集(root set,这里的根集指的是Java虚拟机栈中的全部引用变量所构成的集合)为遍历起点,遍历堆中的全部对象,若出现无法访问的对象,JVM默认无法访问的对象是内存垃圾,并加以回收,这样像引用计数法中出现了环,JVM可以看作为垃圾对象全部回收。
三、对象的四种引用状态
1、强引用 :创建一个对象并把这个对象直接赋给一个变量,不管系统资源多么紧张java有垃圾回收机制,内存回收,强引用的对象都不会被回收,即使他以后不会再用到。
2、软引用 :通过SoftReference修饰的类,内存非常紧张的时候会被回收,其他时候不会被回收,在使用之前要判断是否为null从而判断他是否已经被回收了。
3、弱引用 :通过WeakReference修饰的类,不管内存是否足够,系统垃圾回收时必定会回收。
4、虚引用 :不能单独使用,主要是用于追踪对象被垃圾回收的状态。通过PhantomReference修饰和引用队列ReferenceQueue类联合使用实现。
四、垃圾回收算法
1、标记清除算法:两次遍历,第一次遍历:从root set开始,对当前能访问到的所有对象置一个访问标志位。第二次遍历:从Java堆中的内存首地址开始遍历,对没有给标志位的对象予以回收。
缺陷:由于垃圾对象是零散的分布在java堆空间中,所以在进行垃圾回收时,会产生许多零散的且无法利用的内存碎片。
2、标记压缩算法:在1、的基础上,第一次遍历之后,将所有遍历可达的对象进行一次压缩,使之到一块内存区域中,在第二次遍历时:照例清除内存中没有标记的垃圾对象,这样会减少很多内存碎片的产生。
3、复制算法:将java堆空间分为两个部分A和B,内存回收时,将A中的所有可用对象依次复制到B的空间中,将A和B的位置互换后,回收A的全部空间。
以“空间”代价换取“时间”代价
4、分代回收(GC):将java中的堆空间分为年轻代,老年代和永久代。根据研究发现,90%以上的对象使用时间都是“朝生夕死”,所以大部分对象都存储在年轻代。
年轻代:分为E(Eden)区和S区,其中S区又分为Form区和To区。在存入对象时,这些对象的空间都分配在了E区,针对于年轻代,我们使用复制算法进行垃圾回收,具体过程如下:1、Eden区和Form区中的对象都会被复制进入To区,2、清空Eden区,将Form区和To区互换,3、将年轻代中年龄大的对象复制到老年代中。
使用复制算法的原因在于:复制算法中的对象如果存活时间长,那么每次在复制时,会造成较大的时间损耗,而复制算法的本质是以空间换时间。所以我们在对象存活时间较短的新生代中使用复制算法。
老年代:因为老年代中都属于存活时间长的对象,数目较少且执行次数较少,因此使用标记压缩算法来回收老年代中的垃圾对象。
使用标记压缩算法的原因在于:老年代中存活的对象数量并没有新生代数量多,而且执行频率也低。因此在压缩过程中对象进行移动所产生的时间损耗就会比较小。
永久代:存放 类对象(.class)和方法等信息,不会被回收!默认大小为64M,像Hibernate,Spring这类喜欢AOP动态生成类的框架,往往会生成大量的动态代理类,因此我们经常遇到java.lang.OutOfMemoryError:PermGen space的错误,这就是Permanent代内存耗尽所导致的错误。
注意:由于在实际开发中,经常会报出永久代的堆空间溢出的异常,所以在jdk1.8之后,就废弃了永久代。但是并不意味着我们以上的结论失效,因为java提供了与永久代类似的叫做“元空间”的技术。元空间的本质和永久代类似。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。也就是不局限与jvm可以使用系统的内存。
实际开发中常用的小技巧:
1、尽量使用直接变量,例如:String javaStr = “XXX”;
2、使用 StringBuilder 和 StringBuffer 进行字符串连接等操作;
3、尽早释放无用对象;
4、尽量少使用静态变量;
5、缓存常用的对象:可以使用开源的开源缓存实现,例如:OSCache,Ehcache;
6、尽量不使用 finalize() 方法;
7、在必要的时候可以考虑使用软引用 SoftReference。