前言
前面我们已经知道了Java虚拟机所做的事情就是回收那些不用的垃圾,那些不用的对象。那么问题来了,我们如何知道一个对象我们不需要使用了呢?程序在使用的过程中会不断的创建对象,这些所创建的对象指不定在哪里我们就需要用到了呢?GC怎么知道我们不用了呢?回收就是简单的删除吗?这些问题将会在这里做出解释。
怎么判断一个对象将会被回收
说白了就是判断一个对象已经死亡,不会再被用到了。
首先我们需要知道java中有四种不一样的引用。
强引用:A a = new A()
软引用:还有用,但不是必须的对象,软引用就是连着这些对象,如果马上要内存溢出了就会尝试回收这些对象
弱引用:被弱引用关联的对象只能活到下一次GC之前
虚引用:最没用的一个,只有一个目的,在回收之前收到一个系统通知
引用计数(这个不是java虚拟机使用的)
看书的时候我就惊讶了,因为我确实也面试到很多人这个问题,回答告诉我的都是引用计数,即:用一个计数器,当有引用指向它,则加1,当失去一个引用,则减1,当引用为0时则回收。
我学过AS3,在flash里面确实是这样的,但是在java虚拟机里面不是,因为没有办法回收两个互相引用的对象。
比如A引用B,B引用A这样大家引用都不是0,就无法回收了,显然java中是可以回收这样对象的。
可达性算法
引入了GC Root的概念,从这个根必须能引用到这个对象。
(GC Root)->(A)->(B)
(C)
当C这样的对象到GC Root没有任何引用链项链的时候,也就是不可达的时候,算这个对象已经死亡。
java和C#都是使用这样算法。
上面说的都是堆中对象的死亡条件,或者说堆的的对象什么时候会被回收。
我们要知道的是,方法区其实也是可以垃圾收集的。比如当一个字符串常量没有地方引用到了这个量,如果现在方法区不够了那就会进行回收。
垃圾收集的算法
这里用最简单的话概括一下,有必要的详细了解
1、标记-清除
看名字就知道,就先标记一下要回收的对象,标记完成之后统一清除。
缺点:效率不高,清除之后碎片内存太多。
2、复制
将内存一分为A、B;用的时候就用A这块,回收的时候,将A中需要用的东西复制到B中,然后一次性清除A的区域。
优点:对象存活率不高的时候效率高,不会有内存碎片,都是一整块的。
缺点:代价高,原本挺大的一块内存硬生生是被切成了一半。(不是说所有的虚拟机都是对半分来实现复制的)
3、标记-整理
和标记清除类似,只是在清除的时候不一样,这里是整理,将存活的对象移到一起。
效率和标记清除是差不多的,但是不会有碎片内存
4、分代收集
因为不是每个收集的算法都是完美的,所以java虚拟机采用分代收集的思想,对于新生代因为有大批的对象会死亡,所以使用复制,但是对于老年代,对象的存活率高,所以采用标记整理或者标记清除。
分配
我们知道了对象是如何回收的,同时我们也需要知道对象是如何分配的。
1、对象优先分配在新生代Eden区,当没有位置的时候发起一次MinorGC
2、大的对象,如连续分配的数组直接进入老年代
3、长期存活的对象会进入老年代,没熬过一次MinorGC就增加一岁
4、当相同年龄的对象大小综合大于Survivor的一半就会将年龄大于等于这个年龄的对象直接进入老年代
5、老年代可以用作新生代的空间担保,新生代用的是复制算法,万一对象存活太多,那么需要额外的空间去弥补
针对每个收集器的具体行为和优势劣势将会在后面提出,这里暂时就到这里。
上面就是GC策略,我们所要知道的就是如何判断一个对象存活,对象怎么被回收,对象怎么被分配。