之前说过JVM堆内存的划分与垃圾回收机制有关,现在来填坑,堆内存结构如下。
怎样的对象才需要被回收?java课上老师是这么说的,当某个内存没有引用指向时,该内存就会被释放,那么如何判断一个内存是否有引用指向呢?sun的JVM采用根搜索方法,以一系列名为“GC Root”的对象为起点,从这些节点往下搜索,搜索走过的路径称为引用链,若一个内存没有和任何引用链相连,则被回收,需要GC的只有方法区和堆,这里讲解的是堆的GC。
可作为“GC Root”的对象有:
1、虚拟机栈中的局部变量表中的引用。
2、方法区中的类静态引用。
3、方法区中的常量引用。
4、本地方法栈中的JNI本地方法的引用。
常用的GC算法有:标记-清除算法、标记-整理算法、复制算法
复制算法:复制算法将内存分为两个空间,同一个时刻只有其中一个有实例对象,设这两个空间为A和B,假设内存实例对象都在A中,当A空间满时,JVM将停止运行程序,将A中根据根搜索算法搜索到的实例对象转移到B中,同时更改实例对象引用的指向,实例对象分配到B时,将严格按照内存地址(就如数组的下标从0开始递增一样,使内存的分布连续)依次排列,正因如此,可用内存的分配才不会过于零散,然后释放A中实例对象的内存,这个算法的缺点很明显,空间浪费率达到50%,由于有空间浪费,该算法适用于实例对象的存活时间较短的情况(可以经常释放内存,不会出现所有实例对象均存活且空间已满,而又要添加实例对象的尴尬情况)
标记-清除算法:该算法分为两个阶段,1、标记,2、清除。
1、标记:根据根搜索算法标记实例对象。
2、清除,此时将遍历堆中所有实例对象,清除未被标记的实例对象。
该算法会产生内存碎片(即可用内存的分布非常零散),由于要进行所有对象的遍历,所以该算法的效率低下
标记-整理算法:结合了上述两个算法的优点,该算法分为两个阶段,1、标记,2、整理。
1、标记:首据根搜索算法标记实例对象。
2、整理:将标记的实例对象移动到内存的一端,按内存地址整理,更新实例对象的引用指向,然后清除末端地址以外的内存。
该算法解决了标记-清除算法内存碎片以及复制算法空间浪费的问题,缺点是效率较低。
三个算法效率比较:复制算法>标记-整理算法>标记-清除算法。
复制算法与标记-整理算法的差别主要在于copy算法比移动对象的内存地址效率要高,标记-清除算法最后还要遍历所有对象,标记-整理算法只会对标记对象操作(通过GC Root来获得标记对象),不需要遍历所有对象,故效率更高。
JVM堆内存的划分原因:
在JVM的堆内存中,新生代采用的是复制算法,当Eden区域满后,会将多余的对象放到其中一个survivor区域,当该survivor区满时,会将存活实例对象复制到另一个survivor区域或是老年代,然后释放本survivor区域的内存,两个survivor区域通常有一个为空,Eden与两个survivor区域的内存比例为8:1:1,当一个实例对象经过了两个survivor区域后,在运行复制算法时将被复制到老年代。
在老年代中的对象存活时间较长,使用标记-整理算法。
java虚拟机对老年代的垃圾回收称为MajorGC/Full GC,次数相对比较少,每次回收的时间也比较长。
java虚拟机-Xms参数可以指定最小内存大小,-Xmx参数可以指定最大内存大小,这两个参数分别减去Xmn参数指定的新生代内存大小,可以计算出年老代最小和最大内存容量。