内存溢出包含很多种情况,我在平常工作中遇到最多的就是堆溢出。有一次线上遇到故障,重新
启动后,使用 jstat 命令,发现 Old 区在一直增长。我使用 jmap 命令,导出了一份线上堆栈,然后
使用 MAT 进行分析。通过对 GC Roots 的分析,我发现了一个非常大的 HashMap 对象,这个原本
是有位同学做缓存用的,但是一个无界缓存,造成了堆内存占用一直上升。后来,将这个缓存改
成 guava 的 Cache,并设置了弱引用,故障就消失了。
这个回答不是十分出彩,但着实是常见问题,让人挑不出毛病。
GC 垃圾回收算法与垃圾收集器的关系?
常用的垃圾回收算法有标记清除、标记整理、复制算法等。引用计数器也算是一种,但是没有垃
圾回收器使用这种算法,因为有循环依赖的问题。
很多垃圾回收器都是分代回收的。对于年轻代,主要有 Serial、ParNew等垃圾回收器,回收过程主
要使用复制算法。
老年代的回收算法有 Serial、CMS 等,主要使用标记清除、标记整理算法等。
我们线上用的较多的是 G1,也有年轻代和老年代的概念,不过它是一个整堆回收器,它的回收对
象是小堆区。
在目前 G1 大行其道的今天,实在没必要再纠结 CMS 这么难用的东西了。
生产上如何配置垃圾收集器的?
首先是内存大小问题,基本上每一个内存区域我都会设置一个上限,来避免溢出问题,比如元空
间。通常,堆空间我会设置成操作系统的 2/3(这是想给其他进程和操作系统预留一些时间),超过
8GB 的堆优先选用 G1。
接下来,我会对 JVM 进行初步优化。比如根据老年代的对象提升速度,来调整年轻代和老年代之
间的比例。
再接下来,就是专项优化,主要判断的依据就是系统容量、访问延迟、吞吐量等。我们的服务是
高并发的,所以对 STW 的时间非常敏感。
我会通过记录详细的 GC 日志,来找到这个瓶颈点,借用 gceasy(重点)这样的日志分析工具,很容
易定位到问题。之所以选择采用工具,是因为 gc 日志看起来实在是太麻烦了,gceasy 号称是 AI 学
习分析问题,可视化做的较好。
怎么查看服务器默认的垃圾回收器是哪一个?
这通常会使用另外一个参数:-XX:+PrintCommandLineFlags 可以打印所有的参数,包括使用的垃圾
回收器。
假如生产环境 CPU 占用过高,请谈谈你的分析思路和定位。
这个可真是太太太常见了,不过已经烂大街了。如果你还是一个有经验的开发者,不知道的话,
需要反省一下了。
首先,使用 top-日命令获取占用 CPU 最高的线程,并将它转化为 16 进制。