JavaJava 程序员

JVM中GC相关的干货以及GC友好相关的代码编写规范

2022-06-17  本文已影响0人  程序花生

本文的来历是公司希望写点新员工培训的资料,GC分配给到了我,所以利用这个机会将GC相关的知识整理下,也顺便复习下这部分号称JAVA最没用也最有用的知识。

由于面向的是工作年限不长的新员工,所以本文不会将太多特别深的理论知识,直接上干货,让大家能快速掌握工作中需要了解的GC相关的知识以及GC友好的代码规范。至于进阶以及高级的内容大家可以按需学习,毕竟JVM是java的基础,也是java “where amazing happen”的地方,知识的广度和深度可想而知。现在很多大厂都在根据自己的业务来配置队伍定制开发和维护业务友好的专用JVM,由此可见一般。

JVM的GC

JVM为什么要GC

在正式讲解之前,先科普下基础知识,了解的小伙伴直接略过往下看。

首先什么是GC? GC是英文Garbage-Collection(垃圾回收)的缩写。是JVM对java中的对象按规则进行回收释放的过程。

其次JVM为什么要GC呢? 在同为面向对象的C++中,如果你新建了一个对象,需要手动维护和管理整个对象的生命周期,如果处理不当就会造成内存泄漏,这对C++的新手来说是很大的挑战,所以C++的上手门槛还是有的;而java由于引入了JVM,对象回收的事情交给她就可以了,程序员只需要负责创建对象即可(此处说法为了便于理解并不是很严谨,在某些场景下还是需要注意下的,否则也是会引起内存泄漏的,下面的章节会详细说明)。JVM就像一个终极优秀的女佣,在接到你给的配置和策略后,忠实的完成自己处理和回收对象的本职工作。

GC相关的基本概念

上面简单介绍了下GC相关的知识,下面来学习下GC相关的几个概念:

JVM 运行模式有如下这两种:

当JVM用于启动GUI界面的交互应用时适合于使用client模式,当JVM用于运行服务器后台程序时建议用Server模式。

现在大多数模式下默认使用的都是Server模式。如果实在不知道怎么选,那就交给JVM自己去处理,JVM如果不显式指定是-Server模式还是-client模式,JVM能够根据下列原则进行自动判断(适用于Java5版本或者Java以上版本)。

查看当前 JVM 运行模式的指令:java -version

GC常用的收集器

下面再来看下图解HotSpot虚拟机所包含的收集器:

图中展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。下面是详细的分类:

下面就根据上面的分类来进行详细讲解,由于除了G1外都是分代的收集器,所以下面的内容按照新生代+老年代进行典型搭配讲解:

Serial 收集器 + Serial Old 收集器

Serial + Serial Old收集器是最基本的、发展历史最悠久的收集器。

特点:

  1. 在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用。
  2. 作为CMS收集器的后备方案,在并发收集Concurent Mode Failure时使用。

开启此组合的配置项为-XX:+UseSerialGC

Serial / Serial Old收集器工作流程图:

Parallel Scavenge 收集器 + Parallel Old 收集器

这个组合就厉害多了,首先Parallel就代表着这是并行处理的收集器,效率比上面的两位高很多。其中Parallel Scavenge使用复制算法多线程的进行年轻代的GC;Parallel Old使用采用标记-整理算法多线程的进行老年代的GC。

另外这个组合的目标是可以通过设置吞吐量以及最大停顿时间来控制GC的进行,该组合通过提供一个GC自适应调节策略来达到上面的目标。

GC自适应调节策略: Parallel Scavenge收集器可设置-XX:+UseAdptiveSizePolicy参数。当开关打开时不需要手动指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等,虚拟机会根据系统的运行状况收集性能监控信息,动态设置这些参数以提供最优的停顿时间和最高的吞吐量,这种调节方式称为GC的自适应调节策略。

Parallel Scavenge收集器使用两个参数控制吞吐量:

在JDK1.8时,开启此组合的配置项为-XX:+UseParallelGC

Parallel Scavenge/Parallel Old收集器工作流程图:

ParNew 收集器 + CMS 收集器

这是G1GC出现之前服务器端最常用的组合,ParNew负责年轻代的GC,CMS负责老年代的GC

ParNew 收集器

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程外其余行为均和Serial收集器一模一样。

特点:

应用场景:

ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为它是除了Serial收集器外,唯一一个能与CMS收集器配合工作的。

ParNew收集器工作流程图:

CMS 收集器 CMS收集器是个既并发又并行的收集器,在多线程的情况下还能和用户线程并行执行,基于标记-清除算法实现垃圾回收,最大程度的减少了STW的时间。

工作流程主要有如下 4 个步骤:

在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿;需要停顿的阶段正常情况下耗时很短,所以CMS正常情况下能很好地控制停顿时间。

但 CMS 收集器也有如下缺点:

在JDK1.8时,开启此组合的配置项为-XX:+UseConcMarkSweepGC

CMS收集器的工作流程图:

G1 收集器

G1(Garbage-First),它是一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。HotSpot开发团队赋予它的使命是未来可以替换掉 CMS 收集器。

使用复制 + 标记-整理算法收集新生代和老年代垃圾。

G1为了实现 STW 的时间可预测,首先要有一个思想上的改变。G1 将堆内存“化整为零”,新生代和老年代不再物理隔离,而是将堆内存划分成多个大小相等独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。回收器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是 新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。

另外Region中还有一类特殊的Humongous区域,专门用来存储大对象。 G1认为只要大小超过了一个Region 容量一半的对象即可判定为大对象。

而对于那些超过了整个 Region 容量的超级大对象,将会被存放N个连续的Humongous Region之中,G1 的进行回收大多数情况下都把Humongous Region作为老年代的一部分来进行看待。

G1中两个可配置参数:

工作流程主要有如下 4 个步骤:

开启G1垃圾收集器的方式:-XX:+UseG1GC

G1收集器工作流程图:

GC总结

上面介绍了几种常用的GC组合,当前服务端的配置基本上都集中在后两种组合上,那么问题来了,如何进行选择呢,此处给个最简单的区别方法,以堆内存的大小作为评判标准:

GC友好的代码

如果说上面的垃圾回收策略和服务端或者后端工程师关系比较密切的话,那么下面要说的就和所有java程序员有关了,如果在GC策略调优到达瓶颈后,性能的差别就会产生在代码的编写质量了。下面就来说说GC友好代码有哪些策略。

新建对象时的选择

尽量使用更多生命周期短的、小的、不改变指向(immutable)的对象;如果无法达到上述目标,那尽量做好对象重用以及对象管理,防止内存泄漏。原因如下:

所以为中间结果分配小对象看起来并不是一件坏事,起码对于GC是这样的。

将用完的对象手动设置为NULL有用吗?

其实没什么卵用,还使得代码的可读性下降了不少。JIT Compiler会自动分析local变量的生命周期。

只有一种情况可能需要手动处理,比如有一个巨大的常驻内存的集合缓存对象,里面某些数据在业务完成后必须要手动释放,否则该集合对象可能会越来越大,也就是传说中的内存泄漏,最终的结果可能就是OOME了。

当然,异常处理的catch模块或者finally模块中资源释放的代码还是必须的。

尽量别手动调用System.gc()以及finalize()

在某些高级组件的高级功能中可能会调用这两个方法,平时我们的编码中基本上用不上,所以还是别抢JVM的工作了,让JVM自己去处理吧。

WeakReference&&SoftReference的使用

这是个平时不怎么起眼,偶然知道了又觉得很有用的Java特征。

大家都知道Java里所有对象除int等基本类型外,都是Pass by Reference的指针,实例只要被一个对象连着,就不会被收集。

WeakReference就是真正意义上的C++指针,只是单纯的指向一个对象,而不会影响对象的引用计数;SoftReference更特别,在内存足够时,对象会因为SoftReference的存在而不被收集,但内存不足时,对象就还是会被收集。

另外还可以使用ReferenceQueue的机制,使得对象被回收后再次获取时能获得通知,在某些场景下还是很有用处的。

所以某些缓存类的数据可以使用上面两种对象对象进行存储,WeakReference在GC时直接就被回收,SoftReference则会根据内存情况来决定是否回收该对象。

如何避免内存泄漏

内存泄漏乍一听很高大上的概念,但是java不是有垃圾收集器吗?怎么还泄漏呢?还真能,下面来分析下java内存泄漏的几种原因:

内存泄漏检测工具:

总结

前文说了GC是java中最没用也是最有用的知识,说它没用是因为大多数程序员,尤其是刚入行的程序员,GC的调优理他们很遥远,基本上不会接触到,也不会耽误写代码。说它有用是因为这是java的jvm的一个核心内容,在降低了java使用门槛的同时也是java amazing的地方,当你要进阶成高级程序员或者架构师时,这部分内容就会变得十分有必要的了,所以学习下这部分知识肯定不亏,起码在java程序员面试中这基本上是必问的知识。

作者:克己l守心
链接:https://juejin.cn/post/7109664057002557470
来源:稀土掘金

上一篇 下一篇

猜你喜欢

热点阅读