1.概念
当前的商业虚拟机的垃圾收集算法都采用“分代收集 (Generational Collection)” 算法。这种算法并不是什么新的思想,只是根据对象存活周期的不同将 java 堆内存划分为几块(新生代、老年代),根据各个年龄代的特点采用最适当的收集算法。在新生代中,每次垃圾收集都会有大批对象死去,只有少量存活,就用复制算法;在老年代对象存活率高,没有额外的空间对它进行担保分配,就必须使用“标记-清除”或者“标记-整理”算法进行回收。
2.具体过程
2.1 对象分类
内存中的对象按照生命周期的长短大致可以分为三种,以下是借 左潇龙大神 的命名来理解:
1、夭折对象:朝生夕灭的对象,通俗点讲就是活不了多久就得死的对象。
例子:某一个方法的局域变量、循环内的临时变量等等。
2、老不死对象:这类对象一般活的比较久,岁数很大还不死,但归根结底,老不死对象也几乎早晚要死的,但也只是几乎而已。
例子:缓存对象、数据库连接对象、单例对象(单例模式)等等。
3、不灭对象:此类对象一般一旦出生就几乎不死了,它们几乎会一直永生不灭,记得,只是几乎不灭而已。
例子:String池中的对象(享元模式)、加载过的类信息等等。
夭折对象和老不死对象都在JAVA堆,而不灭对象在方法区。这两个区域都是所有线程共享的。而 GC 主要在 java 堆中进行,因此分代收集算法主要针对夭折对象和老不死对象。
2.2 进行收集
夭折对象朝生夕灭,存活时间短,存活率不高,因此夭折对象是最适合使用复制算法的。
小疑问:50%内存的浪费怎么办?
答疑:因为夭折对象一般存活率较低,因此可以不使用50%的内存作为空闲,一般的,使用两块10%的内存作为空闲和活动区间,而另外80%的内存,则是用来给新建对象分配内存的。一旦发生GC,将10%的活动区间与另外80%中存活的对象转移到10%的空闲区间,接下来,将之前90%的内存全部释放,以此类推。
我们来看一下具体的过程:
新生代回收过程PS,有两点要注意的:
1.使用这样的方式,我们只浪费了10%的内存。这个是可以接受的,因为我们换来了内存的整齐排列与GC速度。
2.这个策略的前提是,每次存活的对象占用的内存不能超过这10%的大小,一旦超过,多出的对象将无法复制。
为了解决上面那个异常的情况,高手们将 JAVA 堆分成两部分来处理,上述三个区域则是第一部分,称为新生代或者年轻代。而余下的一部分,专门存放老不死对象的则称为年老代。
老不死对象:这一类对象存活率非常高,因为它们大多是从新生代转过来的。就像人一样,存活的时间长了,就变成老不死对象了。
通常情况下,以下两种情况发生的时候,对象会从新生代区域转到年老带区域。
1、在新生代里的每一个对象,都会有一个年龄,当这些对象的年龄到达一定程度时(年龄就是熬过的GC次数,每次GC如果对象存活下来,则年龄加1),则会被转到年老代,而这个转入年老代的年龄值,一般在JVM中是可以设置的。
2、在新生代存活对象占用的内存超过10%时,则多余的对象会放入年老代。这种时候,年老代就是新生代的“备用仓库”。
针对老不死对象的特性,显然不再适合使用复制算法,因为它的存活率太高,而且不要忘了,如果年老代再使用复制算法,它可是没有备用仓库的。因此一般针对老不死对象只能采用“标记/整理”或者“标记/清除”算法。
还有一种对象是不灭对象,这种对象在 HotSpot 虚拟机里面是存放在方法区的,也称为永久代。分代收集算法不会作用在此区域。
JVM 在进行 GC 时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。因此 GC 按照回收的区域又分了两种类型,一种是普通GC(minor GC),一种是全局GC(major GC or Full GC),它们所针对的区域如下。
-
普通GC(minor GC):只针对新生代区域的GC。
-
全局GC(major GC or Full GC):针对年老代的GC,偶尔伴随对新生代的GC以及对永久代的GC。
由于年老代与永久代相对来说 GC 效果不好,而且二者的内存使用增长速度也慢,因此一般情况下,需要经过好几次普通 GC ,才会触发一次全局 GC 。
网友评论