jvm垃圾回收主要有以下的几种算法,面试的时候 也常常的被问道这类问题。所以,今天就稍微的整理一下,做一个总结。记得很久前一次面试,当时问我spark的各种调优,我自我感觉回答得都很不错,然后面试官说:你所讲的这些性能调优,资源参数调优,都是运行在JVM上的。你讲一下JVM的垃圾回收算法。所以今天就整理这个了。
首先,问:如何判断对象已经消亡?
答:1 引用计数算法。一个对象如果没有任何引用指向它,就可认为该对象已经”消亡“,这种方法有个缺点就是无法检测到引用环的存在。
算法特点
-
需要单独的字段存储计数器,增加了存储空间的开销;
-
每次赋值都需要更新计数器,增加了时间开销;
-
垃圾对象便于辨识,只要计数器为0,就可作为垃圾回收;
-
及时回收垃圾,没有延迟性;
-
不能解决循环引用的问题;
第二种,2,根搜索算法
Java使用根搜索算法回收垃圾,该算法的基本原理:定义一系列名为GC Roots的对象作为起点,从起点向下搜索,搜索所走过的路径称为引用链。
当一个对象到GC Roots没有任何引用链相连,则说明该对象不可用,这时Java虚拟机可以对这些对象进行回收。
Java虚拟机将以下对象定义为 GC Roots :
1), Java虚拟机栈中引用的对象:比如方法里面定义这种局部变量 User user= new User();
2),静态属性引用的对象:比如 private static User user = new User();
3),常量引用的对象:比如 private static final User user = new User();
4),本地方法栈中引用的对象
二,常用的垃圾回收算法
1,标记-清除算法。
该算法包含标记和清楚两个阶段:
首先是标记处所有需要回收的垃圾对象,标记完成之后统一进行回收处理。
该算法主要不足:
A),一个是效率问题。标记和清楚两个过程的效率都不高。
B),一个是空间的问题。标记清楚之后会导致大量不连续的内存碎片。空间碎片过多会导致在程序运行过程中需要分片大量内存的时候无法找到满足连续内存而不得不提前出发另一次垃圾回收动作。标记-清除算法的执行过程如下图:
2,复制算法
此算法把内存划分为相等大小的两个区域,每一只使用其中一个,回收过程中将存活的对象全部复制到另一个区域中,清空原区域。在年轻代中eden区和两个survivor区就是使用了此种算法。这种算法只复制存活的对象,成本较低,而且不会出现内存碎片问题,缺点是需要2倍的内存空间。
3,标记整理算法
复制收集算法在对象存活率较高的时候就要进行较多的复制操作,效率将会变的很低。更关键是,如果你不想浪费一半内存空间,就需要有额外的内存空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以老年代不采用这种算法。
根据老年代的特点,有人提出了另外一种标记-整理算法,标记过程仍然与标记清除算法一样,然后直接清理掉端边界以外的内存,”标记-整理”算法的示意图如下:
4,分代回收算法
当前商业虚拟机的垃圾回收器都采用分代收集算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存花费为几块。一般是把java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制陈本就可以完成收集。而老年代中因为对象存活率高,没有额外空间对它进行分配担保,就必须使用标记清除或者标记整理算法来进行垃圾回收。
三,内存分配和回收的策略
目前JVM分代主要是分三个年代:
新生代:所有新创建的对象都首先在新生代进行内存分配。新生代具体又分为3个区,一个Eden区、一个From Survivor区和一个To Sruvivor区。大部分对象都被分配在Eden区,当Eden区满时,还存活的对象将被复制到From Survivor区,当From Survivor区满时,此区还存活的对象将被复制到To Survivor区。最后,当To Survivor区也满时,这时从From Survivor区复制过来并且还存活的对象将被复制到老年代。
老年代:在年轻代中经历了N次(一般是15次)GC后依然存活的对象,就会被放到老年代当中。因此,可以认为老年代是存放一些生命周期较长的对象。
持久代:用于存放静态文件,如Java类等。
GC的种类:
新生代GC(Minor GC):
指的是发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以MinorGC非常频繁,一般回收速度也比较快。
老年代GC(MajorGC/FullGC):
指发生在老年代的GC,出现了MajorGC,经常会伴随至少一次的MinorGC(但非绝对,在Parallel Scavenger 收集器的收集策略里就有直接进行MajorGC的策略选择过程)。
MajorGC的速度一般会比MinorGC慢10倍以上。
对象的分配策略:
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次GC。
大对象直接进入老年代。所谓的大对象是指,需要大量连续内存空间的Java对象,最经典的大对象就是那种很长字符串以及数组。
长期存活的对象将进入老年代。虚拟机为了区别对象是应该放在老年代还是新生代,给每个对象定义了一个对象年龄计数器。如果对象在Eden出生并经过第一次MinorGC后任然存活,并且能被Survivor容纳的话,将被移动到Survivor空间,并且将年龄设置为1.对象每在Survivor区中熬过一次MinorGC,年龄就会增加1岁。当他的年龄超过一定程度(默认是15岁),就会被晋升到老年代中。对象晋升到老年代的阈值可以通过参数-XX:MaxTenuringThreshold设置。
动态对象年龄绑定:
为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才晋升老年代,如果在Survivor空间中相同年龄所有对象大小综合大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
空间分配担保:
在发生MinorGC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果这个条件成立,那么MinorGC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于则尝试进行一次MinorGC,尽管这次MinorGC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那么也要改为进行一次FullGC。
风险其实就是指:老年代的空间不一定能容纳青年代所有存活的对象,一旦不能容纳,那就还需要进行一次Full GC。
四,垃圾回收器