哪些内存需要回收
其实,一般是对堆内存而言的。
垃圾回收算法过程
在Java语言中,GC(Garbage Collection)是一个非常重要的概念。它主要是回收程序中不再使用的内存。
对对象而言,如果没有任何变量去引用它,那么该对象将不可能被程序访问,因此可以认为它是垃圾信息,可被回收。只要有一个以上的变量引用该对象,该对象就不会被垃圾回收。
public class Test { public static void main(String a) { Integer i1 = new Integer (1); Integer i2 = new Integer (2); i2 = i1; } }
资源i2所占的内存是不可达的。
(1)引用计数法
原理:为每个对象配一个计数器,如果这个对象被引用,则计数器加1,计数器为0,则表示该对象没有任何引用指向它。
缺点:不可达的对象出现循环引用,它的引用计数器均不为0。
(2)标记清除算法
原理:先通过根节点标记所有可达对象,然后清除所有不可达对象,完成垃圾回收。
缺点:会造成空间碎片。
① 标记出所有需要回收的对象。
② 标记完成后,统一回收所有被标记的对象。
缺点是:
①:标记和收集的两个过程效率都不高。
②:标记清除后会产生大量的不连续的内存空间,空间碎片多了以后就无法分配大块的内存空间给大的对象使用。
(3)复制回收算法
原理:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
把堆内存分成两个大小相同的区域,在任何时刻,只有其中的一个区域被使用,直到这个区域的被消耗完为止,此时垃圾回收器会中断程序的执行,通过遍历的方式把所有活动的对象复制到另外一个区域中,在复制的过程中它们是紧挨着布置的,从而可以消耗内存碎片。当复制过程结束后程序会紧着运行,直到这块区域被使用完,然后采取上面的方法继续进行垃圾回收。
优点:可确保回收后的内存空间是没有空间碎片的。同时,在进行垃圾回收的同时对对象的布置也进行了安排,从而消耗了内存碎片。
缺点:将系统内存折半。对于指定大小的堆来说,需要两倍大小的内存空间,同时由于在内存调整的过程中要中断当前执行的程序,从而降低了程序的执行效率。
复制算法适合用于新生代,因为新生代存活对象少,垃圾对象多。因为在新生代,垃圾对象通常会多于存活对象,复制算法的效果会比较好。
(4)标记压缩回收算法
标记压缩算法是一种老年代的回收算法,老年代大部分都是存活对象。
它对所有可达对象做了一次标记,然后,将堆中所有的存活对象压缩到堆内存的一端,这样就会在堆中另外一端留出很大的一块空闲区域,之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间。
(5)增量算法
如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程,依次反复,直到垃圾收集完成。
优点:由于在垃圾回收的过程中,间断的执行了应用程序代码,所以能减少系统的停顿时间,但是,因为线程切换和上下文切换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。
(6)分代回收算法(非常重要)
根据垃圾回收对象的特性,使用合适的算法回收。比如,新生代使用复制算法,老年代使用标记压缩回收算法。
被标记需要清除对象的自我救赎
标记清除算法是如何解决循环引用的问题呢?
利用可达性分析。枚举根节点,看根节点与对象是否可达,不可达则判断其可被回收,即便有多个对象循环引用,如果不可达则一样要被回收,从此解决循环引用问题。
可达性分析(Reachability Analysis),这个算法的思想是通过一系列的称为“GC Roots”的对象作为起点,从这些起点开始往下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何的引用链,相连接时,则证明这个对象是不可引用的。
可以作为GC ROOT的对象可以包括以下几种:
1、虚拟机栈中引用的对象
2、方法区中类静态属性引用的变量
3、本地方法区中常量引用的变量
4、本地方法栈中JNI(及一般说的Native方法)引用的对象。
要注意的是,即使没有通过可达性分析的对象,也并非是“非死不可”。
第一轮筛选:如果对象在可达性分析中没有于GcROOT链相连,那么就需要进行第二轮筛选
第二轮筛选:对象有没有覆盖(override)过 finalize()函数,对象是否还没运行过finalize()函数。如果这连个条件都满足,
那么对象就会判断为有必要执行finalize()函数,并把这个对象放入到一个叫做F-Queue的队列中,
稍后GC会对这个队列中的对象进行第二次扫描,执行每个对象的finalize()函数,那么如果对象要在这里拯救自己——只要在finalize()函数中让自己重新与GcROOT的任何一个对象建立连接即可。
譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那么在这第二次筛选中就会将这个对象移出出“即将回收”的集合。
对象将根据存活的时间被分为:年轻代、年老代(Old Generation)、永久代
以下是Java堆内存分配:
(1)年轻代
对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快就变得不可达,于是被年轻代的GC机制清理掉,这个GC机制被称为Young GC。
注意:Young GC并不代表年轻代内存不足,事实上只表示在Eden区上的GC
年轻代可以分为3个区:Eden(伊甸园,亚当和夏娃偷吃禁果生娃娃的地方,用来表示内存首次分配的区域,再贴切不过了)和2个survivor区。
(2)年老代
对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代要大,能存放更多的对象,在年老代上发生GC的次数也比年轻代少。当老年代内存不足时,将执行Full GC
如果对象比较大(如大数组),Young空间不足,则大对象会直接分配到年老代上。
(3)永久代
也就是方法区,主要存储Class信息,和存放对象的堆区不同,GC不会再主程序运行期间对永久代进行清理。如果load很多class,就会有可能出现Permanent Generation space错误。
以下是GC机制:
(1)年轻代
收集算法:
1)绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡
2)当Eden区满的时候,执行Young GC,将消亡的对象清理掉,并将剩余的对象复制到一个存货区survivor0中(此时survivor1是空白的,两个survivor区总是有一个是空白的)
3)此后,每次Eden区满了,就执行一次Young GC,并将剩余存活的对象都添加到survivor0中
4)当survivor0也满的时候,会将其中仍然存活的对象放入survivor1中,如果survivor1放不下,会将多出来的放入年老区中。以后Eden区执行Young GC之后,就将剩余的对象添加到survivor1中(此时survivor0是空的)
5)当两个存货区切换了若干次(默认为15次)之后,仍然存活的对象将被复制到年老代。
(2)老年代
老年代存储的对象比年轻代多的多,而且不乏大对象,对老年代进行内存清理时,一般采用标记-整理方法,即标记出仍然存活的对象,将存活对象向一端移动,以保证内存的连续。
在发生Young GC的时候,虚拟机会检查每次进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则触发一次Full GC。
(3)永久代
包括常量池中的常量回收、无用的类信息回收。
注意:
1)Young GC只收集年轻代的对象,Full GC收集所有堆上的对象,包括年轻代、年老代、永久代的对象
2)总而言之,就是分代分配、分代回收
3)System.gc()调用的是Full GC
垃圾回收器的分类(非常重要)
如下
垃圾回收器按分代划分
新生代串行收集器:
1.复制算法 2.单线程垃圾回收 3.独占式的垃圾回收(也就是,在串行收集器运行时,应用程序中的所有线程都停止工作,进行等待。Stop the world现象。)
老年代串行收集器:
1.标记压缩算法 2.单线程垃圾回收 3.独占式的垃圾回收
并行收集器:
并行收集器是工作在新生代的垃圾收集器,它只是简单的将串行回收器多想线程化。同时,它也是独占式的收集器。
新生代并行回收收集器:
1.复制算法 2.多线程 3.独占式
老年代并行回收收集器:
1.标记压缩算法 2.多线程 3.独占式
CMS收集器:它关注系统挺停顿时间。并发标记清除。它可以与用户同时运行,从而降低应用程序的停顿时间。
G1收集器:
G1收集器是目前最新的垃圾回收器,G1收集器的目标是作为一款服务端的垃圾收集器,它在吞吐量和停顿控制上,预期要优于CMS收集器。
与CMS收集器相比,G1收集器是基于标记-压缩算法的,因此,它不会产生空间碎片,也没有必要在收集之后,进行一次独占式的碎片整理工作。G1收集器还可以进行非常精确的停顿控制,它可以在开发人员在指定长度为M的时间段中,垃圾回收时间不超过N。
本文转自大数据躺过的坑博客园博客,原文链接:http://www.cnblogs.com/zlslch/p/7623016.html,如需转载请自行联系原作者