
JVM从入门到熟悉(五)
写在前面
本文主要介绍垃圾回收器和运行时数据区的布局关系、介绍GC日志文件、GC调优思路,以及介绍生产环境可能需要进行性能优化的场景和优化思路,最后会例举一些常见的思考问题。
一、垃圾回收器的位置
之前我们画过一张图,是从class文件到类加载器,再到运行时数据区的过程。我们现在再完善下图片内容,如下图所示,可以看到垃圾回收器是在执行引擎的一部分。

二、GC优化
内存被使用了之后,难免会有不够用或者达到设定值的时候,就需要对内存空间进行垃圾回收。
2.1. 垃圾收集发生的时机
GC是由JVM自动完成的,根据JVM系统环境而定,所以时机是不确定的。 当然,我们可以手动进行垃圾回收,比如调用System.gc()方法通知JVM进行一次垃圾回收,但是具体什么时刻运行也无法控制。也就是说System.gc()只是通知要回收,什么时候回收由JVM决定。 但是不建议手动调用该方法,因为消耗的资源比较大。
一般以下几种情况会发生垃圾回收
- 当Eden区或者S区不够用了
- 老年代空间不够用了
- 方法区空间不够用了
- System.gc()
2.2. GC日志文件
2.2.1. Parallel GC日志
【吞吐量优先】
如果回收的差值中间有出入,说明这部分空间是Old区释放出来的
2.2.2. CMS GC日志
【停顿时间优先】
参数设置:-XX:+UseConcMarkSweepGC -Xloggc:cms-gc.log
2.2.3. G1 GC日志
【停顿时间优先】
参数设置:-XX:+UseG1GC -Xloggc:g1-gc.log
理解G1日志格式:https://blogs.oracle.com/poonam/understanding-g1-gc-logs
2.3. GC日志文件分析工具
1、gceasy
可以比较不同的垃圾收集器的吞吐量和停顿时间
比如打开cms-gc.log和g1-gc.log
2、GCViewer
2.4. G1调优与思路
2.4.1 调优过程
是否选用G1垃圾收集器的判断依据
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/G1.html#use_cases
(1)50%以上的堆被存活对象占用
(2)对象分配和晋升的速度变化非常大
(3)垃圾回收时间比较长
思考 :https://blogs.oracle.com/poonam/increased-heap-usage-with-g1-gc
(1) 使用G1GC垃圾收集器: -XX:+UseG1GC
比如设置堆内存的大小,获取到gc日志,使用GCViewer分析吞吐量和响应时间
(2) 调整内存大小再获取gc日志分析
修改配置参数,获取到gc日志,使用GCViewer分析吞吐量和响应时间,如果增加新生代的大小,虽然Miner gc次数会减少,但是停顿时间会增加。
(3) )调整最大停顿时间
比如设置最大停顿时间,获取到gc日志,使用GCViewer分析吞吐量和响应时间,停顿时间减少了,意味着垃圾回收次数就会增加
(4) 启动并发GC时堆内存占用百分比
比如设置该百分比参数,获取到gc日志,使用GCViewer分析吞吐量和响应时间
2.4.2. 调优思路
(1) 不要手动设置新生代和老年代的大小,只要设置整个堆的大小
(2) 不断调优暂停时间目标
(3) 使用-XX:ConcGCThreads=n来增加标记线程的数量
(4) MixedGC调优
(5) 适当增加堆内存大小
可以减少gc的次数,这样减少程序停顿的时间
三、高并发场景分析
以每秒3000笔订单为例
四、性能优化指南
一般我们生产环境不外乎会遇到这几类问题需要涉及性能优化
- GC频繁,young GC / Full GC
- 死锁
- OOM
- 线程池不够用
- CPU负载过高
五、常见问题思考
1、内存泄漏与内存溢出的区别?
内存泄漏:对象无法得到及时的回收,持续占用内存空间,从而造成内存空间的浪费。
内存溢出:内存泄漏到一定的程度就会导致内存溢出,但是内存溢出也有可能是大对象导致的。
2、young gc会有stw吗?
不管什么 GC,都会有 stop-the-world,只是发生时间的长短。
3、major gc和full gc的区别?
major gc指的是老年代的gc,而full gc等于young+old+metaspace的gc。
4、G1与CMS的区别是什么?
CMS 用于老年代的回收,而 G1 用于新生代和老年代的回收。
G1 使用了 Region 方式对堆内存进行了划分,且基于标记整理算法实现,整体减少了垃圾碎片的产生。
5、什么是直接内存?
直接内存是在java堆外的、直接向系统申请的内存空间。通常访问直接内存的速度会优于Java堆。因此出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。
6、不可达的对象一定要被回收吗?
即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 fifinalize 方法。当对象没有覆盖 fifinalize 方法,或 fifinalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
7、方法区中的无用类回收?
方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类” :
-
该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
-
加载该类的 ClassLoader 已经被回收。
-
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。
8、不同的引用
JDK1.2以后,Java对引用进行了扩充:强引用、软引用、弱引用和虚引用