今天对java虚拟机的gc和内存分配原理学习了一下,以下是本人的一些总结,不喜勿喷!
首先说一下确定垃圾对象的方法:
(1)标记计数法.(容易出现对象的相互引用的问题,通过这个问题的实例能够说明gc仍然可以对其回收,所以gc并不是通过这个方法确定垃圾对象的)
(2)可达性分析法(看gc-roots能否有路径到达堆中的对象)。
当对象确定为垃圾对象后,并不是立即确定其“死亡”,而是判断是否有必要执行finalize()方法:
(1)当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用了,则直接不需要执行finalize()方法,垃圾对象直接确定死亡。
(2)除此之外,对象确定死亡需要执行finalize()方法,那么对象将会被放入一个F-Queue队列中,有虚拟机按照队列的顺序执行他们的finalize()方法,但不一定会等到此方法执行完才会回收对象(防止finalize()方法执行缓慢或者死循环,导致回收系统崩溃),确定为垃圾对象为第一次标记,执行finalize()方法为第二次标记,之后回收对象,所以在执行finalize()方法时,是使垃圾对象”起死回生“最后一根救命稻草。
确定了垃圾对象”必死“,那么垃圾对象怎么”处死“了,下面是垃圾收集算法:
(1)标记-清除算法
(2)复制算法(将没死的对象复制到survivor区域)
(3)标记-整理算法(将没死的对象整理到一起)
有回收就有分配,下面说一下如何分配对象内存,当然细节还是要取决于使用的是哪种收集器组合以及虚拟机的内存分配的参数设置,下面是几条普遍的分配规则:
首先科普一下:
java的内存区域分为新生代和老年代区域,新生代分为Eden和两个survivor区域,一般的大小比例为8:1:1,一个survivor作为上面的复制算法使用。
新生代gc过程频繁,老年代不频繁,主要是老年代存储的都是生命周期较长的对象。下面是分配规则:
(1)对象优先在新生代分的Eden中分配。
(2)大对象直接存入老年代。当然这个对象的大小要大于虚拟机设置的一个参数。
(3)长期存活的对象将进入老年代,这个是通过年龄计数器来实现的,第一次新生代的gc过后将Eden中能放入survivor区域的对象放入,并将年龄计数器记为一,每次gc后,对象仍然存活就加一,直到15后,就直接将这个对象放入老年代,当然这个15也是虚拟机设置的。
(4)如果在survivor区域中相同年龄所有对象大小的总和大于survivor的一半,年龄大于等于该年龄的对象就可以直接进入老年代。
下面说一下空间分配担保机制,这个很重要是上面的关键。
(一)虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果大于,那么新生代的gc是安全的(按照正常的顺序存,新生代不行,就存老年代,按照上面的分配规则来)。
(二)如果上面不成立,虚拟机就会查看是否允许担保机制,如果允许,那么查看老年代最大可用连续空间是否大于历次放入到老年代中的对象的大小的平均值:
1,大于,进行一次新生代的gc,不过这次gc是有风险的,因为此次的对象可能远远大于以往对象的大小。
2,小于或者不允许有风险,直接对老年代进行gc。
最后按照上面的分配规则分配。