如何优雅的学习JVM,综合篇(五)
一、重新认识JVM
在之前的我们画过一张图,是从class文件到类装载器,再到运行时数据区的过程。今天把这张图进行升级,画一张完整的JVM大体的物理结构图。
二、 GC优化
内存被使用了之后,难免会有不够用或者达到设定值的时候,就需要对内存空间进行垃圾回收。
2.1 垃圾收集发生的时间
GC由JVM自动完成的,根据JVM系统环境而定的,所以时机是不确定的。当然,我们也可以进行手动垃圾回收,比如调用System.gc()方法通知JVM进行一次垃圾回收,但是具体什么时刻运行也无法控制。也就是System.gc()只是通知要回收,什么时候回收由JVM自行决定。但是不建议手动进行垃圾回收,因为消耗的资源比较大。
一般以下几种情况会进行垃圾回收
- 当Eden区或者S区不够用
- 老年代空间不够用
- 方法区看空间不够用
- System.gc()
2.2 项目环境准备
此次案例我们使用SpringBoot来创建项目,然后配置对应的参数。
2.3 GC日志文件
要分析日志的信息,首先得拿到GC日志文件才行,所以得配置一下启动参数
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:gc.log
然后启动项目
可以看到默认使用的是ParallelGC
1、ParallelGC日志
- 吞吐量优先,日志分析
2020-02-13T16:01:45.974+0800: 3.592: [GC (Allocation Failure) [PSYoungGen: 65536K【Young区回收前】->8294K【Young区回收后】(76288K【Young区总大小】)] 65536K【整个堆回收前】->8310K【整个堆回收后】(251392K【整个堆总大小】), 0.0253276 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
如果回收的差值有出入,则说明这部分空间是Old区释放出来的
2、CMS GC日志
- 停顿时间优先
参数设置:-XX:+UseConcMarkSweepGC -Xloggc:cms-gc.log
3、G1 GC日志
- 停顿时间优先
参数设置:-XX:+UseG1GC -Xloggc:g1-gc.log
-XX:+UseG1GC # 使用了G1垃圾收集器
#什么时候发生的GC,相对的时间刻,GC发生的区域young,总共花费的时间,0.00444s
2020-02-13T16:31:01.099+0800: 2.230: [GC pause (G1 Evacuation Pause) (young), 0.0044422 secs]
#多少个垃圾回收线程,并行的时间
[Parallel Time: 3.4 ms, GC Workers: 4]
#GC线程开始相对于上面的0.2230的时间刻
[GC Worker Start (ms): Min: 2230.2, Avg: 2230.2, Max: 2230.3, Diff: 0.1]
#每个工作线程扫描根的时间
[Ext Root Scanning (ms): Min: 0.5, Avg: 0.7, Max: 1.0, Diff: 0.5, Sum: 2.9]
#每个线程更新记忆集所花费的时间
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
理解G1日志格式: https://blogs.oracle.com/poonam/understanding-g1-gc-logs
2.4 GC日志文件分析工具
1、gceasy
官网: https://gceasy.io/
可以比较不同的垃圾收集器的吞吐量和停顿时间,比如打开cms-gc.log和g1-gc.log
2、GCViewer
2.5 G1调优与最佳指南
是否选用G1垃圾收集器的判断依据,官网: https://docs.oracle.com/javase/8/docs/technotes/guides/vm/G1.html#use_cases
- 50%以上的堆被存活对象占用
- 对象分配和晋升的速度变化非常大
- 垃圾回收时间比较长
思考:使用G1 GC增加堆使用率 https://blogs.oracle.com/poonam/increased-heap-usage-with-g1-gc
1、使用G1GC垃圾收集器: -XX:+UseG1GC
修改配置参数,获取到gc日志,使用GCViewer分析吞吐量和响应时间
Throughput Min Pause Max Pause Avg Pause GC count
99.16% 0.00016s 0.0137s 0.00559s 12
2、调整内存大小再获取gc日志分析
-XX:MetaspaceSize=100M
-Xms300M
-Xmx300M
比如设置堆内存的大小,获取到gc日志,使用GCViewer分析吞吐量和响应时间
Throughput Min Pause Max Pause Avg Pause GC count
98.89% 0.00021s 0.01531s 0.00538s 12
3、调整最大停顿时间
-XX:MaxGCPauseMillis=20 设置最大GC停顿时间指标
比如设置最大停顿时间,获取到gc日志,使用GCViewer分析吞吐量和响应时间
Throughput Min Pause Max Pause Avg Pause GC count
98.96% 0.00015s 0.01737s 0.00574s 12
4、启动并发GC时堆内存占用百分比
-XX:InitiatingHeapOccupancyPercent=45
G1用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的 使用比例。值为 0 则表示“一直执行GC循环)'. 默认值为 45 (例如, 全部的 45% 或者使用了45%).
比如设置该百分比参数,获取到gc日志,使用GCViewer分析吞吐量和响应时间
Throughput Min Pause Max Pause Avg Pause GC count
98.11% 0.00406s 0.00532s 0.00469s 12
最佳指南
官网建议: https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations
1、不要手动设置新生代和老年代的大小,只要设置整个堆的大小
G1收集器在运行过程中,会自己调整新生代和老年代大小,其实是通过adapt代的大小调整对象晋升的速度和年龄,从而到达为收集器设置的暂停时间目标,如果是手动设置了大小就意味着放弃了G1的自动调优
2、不断调优暂停时间目标
一般情况下这个时间值设置到100ms或者200ms都是可以的(不同情况会不一样),但是如果设置成50ms就太不合理了。暂停时间设置的太短,就会出现G1 GC跟不上垃圾生产的速度,最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。暂停时间只是一个目标,并不是总能得到满足。
3、使用-XX:ConcGCThreads=n来增加标记线程的数量
IHOP如果阀值设置过高,可能会遇到转移失败的风险,比如对象进行转移时空间不足。如果阀值设置过低,就会使标记周期运行过于频繁,并且有可能混合收集期回收不到空间。IHOP值如果设置合理,但是在并发周期时间过长时,可以尝试增加并发线程数,调高ConcGCThreads。
4、MixedGC调优
-XX:InitiatingHeapOccupancyPercent
-XX:G1MixedGCLiveThresholdPercent
-XX:G1MixedGCCountTarger
-XX:G1OldCSetRegionThresholdPercent
5、适当增加堆内存大小
三、高并发场景案例分析
四、JVM性能优化指南
五、常见问题的思考与解决
5.1 内存泄漏与内存溢出的区别
内存泄漏:对象无法得到及时的回收,持续占用内存空间,从而造成内存空间的浪费。
内存溢出:内存泄漏到一定的程度就会导致内存溢出,但是内存溢出也有可能是大对象导致的。
5.2 young gc会有stw吗?
不管什么 GC,都会有 stop-the-world,只是发生时间的长短。
5.3 major gc和full gc的区别
major gc指的是老年代的gc,而full gc等于young+old+metaspace的gc。
5.4 G1与CMS的区别是什么
CMS 用于老年代的回收,而 G1 用于新生代和老年代的回收。 G1 使用了 Region 方式对堆内存进行了划分,且基于标记整理算法实现,整体减少了垃圾碎片的产生。
5.5 什么是直接内存
直接内存是在java堆外的、直接向系统申请的内存空间。通常访问直接内存的速度会优于Java堆。因此出于性能的考 虑,读写频繁的场合可能会考虑使用直接内存。
5.6不可达的对象一定要被回收吗?
即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
5.7 方法区中的无用类回收
方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 “无用的类” :
该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
加载该类的 ClassLoader 已经被回收。
该类对应的 java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。
5.8 不同的引用
JDK1.2以后,Java对引用进行了扩充:强引用、软引用、弱引用和虚引用
来源:合肥网站优化
原文地址:https://www.cnblogs.com/vwvwvwgwgvervae/p/12994141.html
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- ExpiringMap,一个可以用于缓存的 Map
- Leetcode 121. 买卖股票的最佳时机 (DP,模拟)
- MQ 系列之初识消息中间件
- Leetcode 第23场双周赛C 5361. 圆和矩形是否有重叠 (计算几何 初中数学)
- vue-cli 4 快速构建一个 Vue 项目
- SpringDataJPA 系列之快速入门
- Leetcode 第23场双周赛B 5362. 构造 K 个回文字符串(map,思维)
- Java 水题系列(1)数字金字塔
- Leetcode 836. 矩形重叠
- 详解 Vue 目录及配置文件之 config 目录
- Nginx 搭建静态资源服务
- vue 使用自定义字体
- vue 跨域问题
- Shell 递归查找文件
- element-ui 实现嵌套表格