Java8的GC变化

查看Java8的默认GC

在这里插入图片描述

1. cmd命令行查看Java8的GC:

java -XX:+PrintCommandLineFlags -version

结果如下:

-XX:InitialHeapSize=132397312 // JVM默认初始化堆大小
-XX:MaxHeapSize=2118356992 //JVM堆的默认最大值
-XX:+PrintCommandLineFlags 
-XX:+UseCompressedClassPointers 
-XX:+UseCompressedOops 
-XX:-UseLargePagesIndividualAllocation 
-XX:+UseParallelGC //Java8使用的GC类型
java version "1.8.0_20" //使用的java版本
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

结果分析:由结果可以看出Java8的GC情况是:-XX:+UseParallelGC,即Parallel Scavenge(新生代) + Parallel Old(老生代),实际上几个主流Java版本的GC情况如下:
jdk1.7 默认垃圾收集器Parallel Scavenge(新生代【标记-复制算法】)+Parallel Old(老年代【标记整理算法】)
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代【标记-复制算法】)+Parallel Old(老年代【标记整理算法】)
jdk1.9 默认垃圾收集器G1【从局部(两个Region之间)来看是基于"标记—复制"算法实现,从整体来看是基于"标记-整理"算法实现】

Parallel Scavenge收集器-标记-复制算法(Copy)
复制算法是针对标记—清除算法的缺点,在其基础上进行改进而得到的,它将可用内存按容量分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块内存上面,然后再把已使用过的内存空间一次清理掉。复制算法有如下优点:

每次只对一块内存进行回收,运行高效

只需移动栈顶指针,按顺序分配内存即可,实现简单

内存回收时不用考虑内存碎片的出现

它的缺点是:可一次性分配的最大内存缩小了一半

复制算法的执行情况如下图所示:

  • 回收前状态

在这里插入图片描述

  • ** 回收后状态**

在这里插入图片描述
现在的商业虚拟机都采用这种收集算法来回收新生代,新生代中的对象98%都是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块比较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是说,每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的空间会被浪费。
当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖于老年代进行分配担保,所以大对象直接进入老年代。

Parallel Old收集器 - 标记—整理算法(Mark-Compact)
复制算法比较适合于新生代,在老年代中,对象存活率比较高,如果执行较多的复制操作,效率将会变低,所以老年代一般会选用其他算法,如标记—整理算法。该算法标记的过程与标记—清除算法中的标记过程一样,但对标记后出的垃圾对象的处理情况有所不同,它不是直接对可回收对象进行清理,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存。标记—整理算法的回收情况如下所示:

  • 回收前状态:
    在这里插入图片描述

  • 回收后状态:
    在这里插入图片描述

G1 GC

G1 GC全称为Garbage First GC,也就是垃圾优先GC。从Java 7中就开始引入了G1,以用于支持更大的(超过4GB)的堆内存。G1收集器也是一种并发收集器。它会利用多个后台线程来扫描堆,但和其他GC不同的是,它将堆内存划分为多个大小相等的region。每个region可以在 1M - 32M,region的具体大小取决于Heap的大小。我们可以通过:

-XX:G1HeapRegionSize=n

指定。

和它的名字一样,G1 GC会优先扫描包含最多垃圾对象的区域。

默认,G1 GC是不开启的,我们来看下JVM默认参数:

bool UseG1GC = false

要开启G1,可以使用:

-XX:+UseG1GC

G1 GC尽量减少不再使用的对象耗尽堆内存的机会。因为堆内存一旦耗尽,就不得不执行Major GC而导致STW。另外,它还有另外一个优势,它可以对堆内存进行整理。G1将对象从堆中的一个或多个region复制到单个region中,在该过程中进行整理并释放内存。所以,G1的每一次回收,都会减少内存碎片。

相比于CMS,它仅在Major GC时,才会进行整理。

img

这是Oracle官方提供的一个G1的堆内存示意图。我们可以明显地看到,使用G1 GC,堆内存被分为了一个个的region(上图中的一个个灰色的格子)。而分代的概念是逻辑性的。

上图,浅蓝色的表示是年轻代。当我们创建对象时,会分配到逻辑年轻代的region中。当逻辑年轻代满了时,逻辑年轻代的region会进行GC。G1允许,同时收集逻辑年轻代和逻辑老年代。上图中,用红色表示正在gc的region。G1进行GC时,会将存活的对象复制到空的region中,然后根据存活对象的age,决定是复制到Survior(标有s的格子)或者复制到老年代的region中。标有H的region是1.5倍以上大小,并经过特殊处理,可以用于存放更大的对象。

我们可以通过对G1GC指定最大的暂停时间:

-XX:MaxGCPauseMillis=200

G1 GC会自动调整其参数以达到我们预期的目标。

优先使用G1

HotSpot官方将G1作为CMS的替代品,也就是说,我们应该优先使用G1 GC。G1对内存管理更友好、而且能够保证更稳定的STW时间。

关于大堆

尽管这些年,大家一直在避免使用大堆,很多Java开发人员从单个JVM迁移到多个JVM,例如:微服务。微服务可以隔离不同的应用程序模块,简化部署、并且避免将应用程序类加载到内存需要更多的成本。

但即便如此,大家还是在尽量避免较大的Heap长时间STW。Java8 Upate 20做了一次重要的优化,G1 GC可以进行字符串重复删除。字符串占用了大量的JVM内存,这一次优化可以让G1 GC可以识别在整个堆中重复多次的字符串,让它们应用同样的一个Char[],避免一样的字符串在内存中有太多副本。这项配置默认是关闭的:

bool UseStringDeduplication = false
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值