各种各样的垃圾回收器,总体的回收算法有三种:
标记-清除:首先标记出所有需要回收的对象,在标记完成之后统一回收所有被标记的对象。这个算法是所有回收算法的基础,下面的算法都是在这个基础上的改进。标记-清除有两个缺点:1.标记和清除分两个阶段,但是这两阶段的效率都不是很高。2、容易产生大量的内存‘碎片’。标记-清除的位置是不确定的,而新建对象需要的内存应该是在一块的。假设需要新建一个对象内存为10,而现在却有100段不连续的容量为1的空间。虽然可用空间为100,但是却连10的对象都无法新建,这种情况下不得不再次进行垃圾回收。
复制算法:把内存分为大小相等的两块,垃圾回收将一块上存活的对象复制到另一块。复制算法主要是为了提高效率,标记-清除不是会有内存碎片么?那把所有对象紧紧码到一边,内存碎片不就解决了么。但是复制算法很浪费内存,把内存分为大小相等的两块,意味着每次只能使用一半的容量,当使用的这一半满了之后,就要垃圾回收,将存活对象复制到另一半,程序转而使用另一半内存。使用的内存小了之后,便容易触发垃圾回收,而且是对资源的极大浪费。
标记-整理:标记-清除容易产生内存随便,复制算法比较浪费资源,那么折中一下,就是标记-整理算法了。标记过程和标记-清除算法一样,然后将所有存活的对象都向一端移动,然后直接清理掉边界意外的内存。
分代搜集算法:其实并不是一种新算法,而是将上面的三种组合了一下。根据对象的存活周期不同把内存化为为几快,一般是将Java堆分为新生代和老年代,根据各自的特点选择合适的回收算法。对象在新建的时候首先在新生代分配,然后在新生代存活时间比较久的再迁移到老年代。绝大多数的对象是朝生夕灭的,98%的新生代中对象都会死亡。所以新生代一般都选择复制算法,速度最快效率最高嘛,而进入老年代的对象则不那么容易死了,所以选择标记-清除或者标记-整理算法。
新生代中又将内存区域划分为:Eden区和Survivor区(From + To)。默认的大小是Eden:From:TO = 8:1:1。From和To加起来是Survivor区了,其实也可以理解为是分为了Eden+From+To。每次对象可以分配的空间是Eden+From,当这两块的区域满了之后就会进行新生代的垃圾回收,把Eden+From中存活的对象都放入到To区,随后将Eden+From全部清空。接下来From和To会互换,即From和To代表的内存真正地址并不是固定的,每次都是从From区迁移到To区,迁移完成后原来的To区就变成新的From区,而原来的From区变成新的To区,二者代表的地址发生了互换,这样从程序上永远是Eden+From到To。
分配对象的时候新生代只有To区是无法使用的,这样内存使用率就是90%(Eden+From占90%,而To占10%),比最原始的复制算法的内存使用要好很多。但是在极端情况下,万一Eden+From中的对象全部存活怎么办,To区可放不下这么多东西,这种情况下对象会直接进入老年代。所以老年代需要对新生代进行内存担保,当新生代进行内存回收的时候,老年代必须要空间来放所有新生代的对象。如果此时老年代的内存呢也不多了,就要看JVM是否允许担保失败。
|