JVM数据区在JDK1.8带来的变化以及GC

        前面有介绍过jvm的在jdk1.7的运行时数据区,现在讲讲JDK1.8带来的变化,JDK1.8的运行时数据区把方法区移除了,变成了元数据区,所以之前的那个图里面的数据块应该变成如下:

JVM数据区在JDK1.8带来的变化以及GC_第1张图片

        在jdk1.8之前的版本,我们通常把堆分为新生代,老年代和永久代(方法区)(我们通常认为方法区也是属于堆的),新生代又包含了eden,from和to(survivor),当对象存活了超过两个survivor的时候(在To里面通常由个年龄阈值作为晋升判断)会被转移到老年代中,这时候这个对象的数据就会存储在方法区内(GC在方法区的收益甚微),当这边的内存不够时就会报OOMError:PermGen,这时候就可能会发生内存泄露问题。为了解决这个问题,jdk1.8完全把存放元数据的永久内存从堆内存转到了本地内存。

        

        对象分配规则:

        对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。

        大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。

        长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。

        动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。

        空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。



        我们知道对于开发者而言,java和c/c++的最大区别时java开发者并不需要知道程序执行时的内存细节,这也是java能够多年占据编程语言排行榜前几名的原因。我准备从5个角度来描述GC收集:

        1.GC执行的时间点:具体来讲,这个时间并不是由我们控制,而是由JVM里面设置的GC策略决定的,即使我们调用System.gc()也没法保证即时生效。

        2.GC执行针对的目标:简单来说,GC针对的目标时被GC收集器判定为死亡的对象,通常我们使用可达性分析算法来判定。以一系列GC Roots为起点,如果有某个对象没有任何一个GC Roots对其可达(无引用关联)的时候,就会判定这个对象已是可回收的对象,但是这时候对象并没有被完全回收。

        3.GC执行的流程:接着2来说,当GC拿到一个‘死亡’对象的时候会给其添加一个标记,然后进行筛选,筛选的条件是该对象是否有必要执行finalize()方法,如果该方法已经被虚拟机执行过finalize方法或者没有覆盖finlize方法,则立即回收,否则将它置于一个F-Queue队列里面并由一个低优先级的Finalizer线程执行它。

        4.GC使用的算法:①标记清除算法:算法执行过程和名字一致,后果是会带来大量内存碎片,如果碰到要放大对象的时候就不得不触发GC ②复制算法:将内存的可用对象同等的复制到另一块相同大小的内存里,这种方法是使用空间换时间的做法。通常我们的新生代的GC收集都是采用此方法,这也就是为什么Eden:From:To=8:1:1的原因之一 ③标记整理算法:先标记,再将存活对象移动到一块变成连续的,再清除边界外的部分。

        5.内存回收的具体实现(针对HotSpot VM):

        ①Serial 收集器 :单线程(Stop The World) 使用标记整理算法,新生代收集器

        ②ParNew收集器:Serial的多线程版本,新生代收集器

        ③Parallel Scavenge收集器:以吞吐量为目标的收集器。Throughtout=运行用户代码时间/(垃圾收集时间+运行用户代码时间)

        ④Serial Old 收集器:Serial 的老年代版本

        ⑤Parallel Old收集器:Parallel Scavenge收集器的老年代版本

        ⑥CMS收集器:以获取最短回收停顿时间为目标,使用标记清除算法,整个过程是跟随余户线程并发执行的,包含4个步骤:

            1)初始标记 2)并发标记 3)重新标记 4)并发清除   其中重新标记是为了修正在用户程序运作而导致变动的那部分对象的标记记录。。它的缺点是对CPU资源敏感和会产生大量内存碎片,还有就是在标记过后用户线程产生的垃圾没法及时清理,必须等待下一次GC

        ⑦G1 收集器:目标是取代CMS收集器并且能独立进行对整个堆空间GC。

        优点:1)能使用多cpu缩短停顿,通过并发使得用户线程不需要停顿 2)空间整合:整体基于标记整理算法,局部使用复制算法--不产生内存碎片 3)停顿可预测,并且能由用户指定 4)分代收集      第三点的实现实际上是把全区域分割成多个Region进行回收并跟踪,并将各个Region垃圾堆积的价值大小建立优先级并按照优先级回收。

        运作流程:

        每个Region维护一个Remembered Set,里面记录了该Region里面对象的引用关系,使得即便新生代的对象引用了老年代的对象也不用全堆扫描。具体流程:1)初始标记2)并发标记3)最终标记4)筛选回收 与CMS的流程的区别在于最后在回收的时候会建立优先级列表,然后根据不同的策略分级回收。


参考资料:《深入理解Java虚拟机--JVM高级特性和最佳实践》

你可能感兴趣的:(java)