一文学会JVM性能优化
实战性能优化
1 重新认知JVM
之前我们画过一张图,是从Class文件到类装载器,再到运行时数据区的过程,现在咱们把这张图不妨丰富完善一下,展示了JVM的大体物理结构图。
执行引擎:用于执行JVM字节码指令
主要由两种实现方式:
(1)将输入的字节码指令在加载时或执行时翻译成另外一种虚拟机指令;
(2)将输入的字节码指令在加载时或执行时翻译成宿主主机本地CPU的指令集。这两种方式对应着字节码的解释执行和即时编译。
9.2 堆内存溢出
9.2.1 代码
记得设置参数比如-Xmx20M -Xms20M
9.2.2 运行结果
访问->http://localhost:8080/heap
Exception in thread "http-nio-8080-exec-2" java.lang.OutOfMemoryError: GC overhead limit exceeded
9.2.3 回顾jps和jinfo
9.2.4 回顾jmap手动导出和参数自动导出
jmap手动导出:jmap -dump:format=b,file=heap.hprof PID
参数自动导出:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heapdump.hprof
9.3 方法区内存溢出
比如向方法区中添加Class的信息
9.3.1 asm依赖和Class代码
9.3.2 代码
设置Metaspace的大小,比如-XX:MetaspaceSize=50M -XX:MaxMetaspaceSize=50M
9.3.3 运行结果
访问->http://localhost:8080/nonheap
9.4 虚拟机栈
9.4.1 代码演示StackOverFlow
9.4.2 运行结果
9.4.3 说明
Stack Space用来做方法的递归调用时压入Stack Frame(栈帧)。所以当递归调用太深的时候,就有可能耗尽Stack Space,爆出StackOverflow的错误。
-Xss128k:设置每个线程的堆栈大小。JDK 5以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。
9.5 线程死锁
9.5.1 代码
9.4.2 运行结果
9.4.3 jstack分析
把打印信息拉到最后可以发现
9.4.4 jvisualvm
将线程信息dump出来
9.6 垃圾收集
内存被使用了之后,难免会有不够用或者达到设定值的时候,就需要对内存空间进行垃圾回收。
9.6.1 垃圾收集发生的时机
GC是由JVM自动完成的,根据JVM系统环境而定,所以时机是不确定的。
当然,我们可以手动进行垃圾回收,比如调用System.gc()方法通知JVM进行一次垃圾回收,但是具体什么时刻运行也无法控制。也就是说System.gc()只是通知要回收,什么时候回收由JVM决定。
但是不建议手动调用该方法,因为消耗的资源比较大。
一般以下几种情况会发生垃圾回收
(1)当Eden区或者S区不够用了
(2)老年代空间不够用了
(3)方法区空间不够用了
(4)System.gc()
虽然垃圾回收的时机是不确定的,但是可以结合之前一个对象的一辈子案例,文字图解再次梳理一下堆内存回收的流程。
一个对象的一辈子
我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。
有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。
于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。
9.6.2 实验环境准备
我的本地机器使用的是jdk1.8和tomcat8.5,大家也可以使用linux上的tomcat,然后把gc日志下载下来即可。
9.6.3 GC日志文件
回顾升华一下垃圾收集器图
要想分析日志的信息,得先拿到GC日志文件才行,所以得先配置一下,之前也看过这些参数。
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:$CATALINA_HOME/logs/gc.log
比如打开windows中的catalina.bat,在第一行加上
set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:gc.log
这样使用startup.bat启动tomcat的时候就能够在当前目录下拿到gc.log文件
可以看到默认使用的是ParallelGC
9.6.3.1 Parallel GC日志
【吞吐量优先】
2019-06-10T23:21:53.305+0800: 1.303: [GC (Allocation Failure) [PSYoungGen: 65536K[Young区回收前]->10748K[Young区回收后](76288K[Young区总大小])] 65536K[整个堆回收前]->15039K[整个堆回收后](251392K[整个堆总大小]), 0.0113277 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
`注意`如果回收的差值中间有出入,说明这部分空间是Old区释放出来的
9.6.3.2 CMS日志
【停顿时间优先】
参数设置
-XX:+UseConcMarkSweepGC
重启tomcat获取gc日志,这里的日志格式和上面差不多,不作分析。
9.6.3.3 G1日志
G1日志格式参考链接:
https://blogs.oracle.com/poonam/understanding-g1-gc-logs
【停顿时间优先】
why?
https://blogs.oracle.com/poonam/increased-heap-usage-with-g1-gc
参数设置
-XX:+UseG1GC
9.6.4 GC日志文件分析工具
9.6.4.1 gceasy
可以比较不同的垃圾收集器的吞吐量和停顿时间
9.6.4.2 GCViewer
9.6.5 G1调优
是否选用G1垃圾收集器的判断依据
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/G1.html#use_cases
(1)50%以上的堆被存活对象占用
(2)对象分配和晋升的速度变化非常大
(3)垃圾回收时间比较长
(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=200 设置最大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
9.6.6 G1调优的最佳实践
官网建议:
(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跟不上垃圾产生的速度。最终退化成Full GC。所以对这个参数的调优是一个持续的过程,逐步调整到最佳状态。暂停时间只是一个目标,并不能总是得到满足。
(3)使用-XX:ConcGCThreads=n来增加标记线程的数量
IHOP如果阀值设置过高,可能会遇到转移失败的风险,比如对象进行转移时空间不足。如果阀值设置过低,就会使标记周期运行过于频繁,并且有可能混合收集期回收不到空间。
> IHOP值如果设置合理,但是在并发周期时间过长时,可以尝试增加并发线程数,调高ConcGCThreads。
(4)MixedGC调优
-XX:InitiatingHeapOccupancyPercent
-XX:G1MixedGCLiveThresholdPercent
-XX:G1MixedGCCountTarger
-XX:G1OldCSetRegionThresholdPercent
(5)适当增加堆内存大小
9.7 一张图总结JVM性能优化
全文完!thanks for watching
相关阅读:
一文学会JVM性能优化的更多相关文章
- jvm性能优化及内存分区
jvm性能优化及内存分区 2012-09-17 15:51:37 分类: Java Some of the default values for Sun JVMs are listed below. ...
- JVM 性能优化, Part 4: C4 垃圾回收
ImportNew注:本文是JVM性能优化 系列-第4篇.前3篇文章请参考文章结尾处的JVM优化系列文章.作为Eva Andreasson的JVM性能优化系列的第4篇,本文将对C4垃圾回收器进行介绍. ...
- JVM性能优化, Part 3 垃圾回收
ImportNew注:本文是JVM性能优化 系列-第3篇-<JVM性能优化, Part 3 —— 垃圾回收> 第一篇 <JVM性能优化, Part 1 ―― JVM简介 > 第 ...
- JVM性能优化, Part 2 ―― 编译器
作为JVM性能优化系列文章的第2篇,本文将着重介绍Java编译器,此外还将对JIT编译器常用的一些优化措施进行讨论(参见“JVM性能优化,Part 1″中对JVM的介绍).Eva Andreasson ...
- JVM性能优化, Part 1 ―― JVM简介
JVM性能优化这些列文章共分为5章,是ImportNew上面翻译自Javaworld: 第1章:JVM技术概览 第2章:编译器 第3章:垃圾回收 第4章:并发垃圾回收 第5章:可伸缩性 众所周知,Ja ...
- JVM性能优化简介
01. JVM是什么 概述: 大白话: 全称Java Virtual Machine(Java虚拟机), 它是一个虚构出来的计算机, 通过实际的计算机来模拟 ...
- JVM性能优化系列-(1) Java内存区域
1. Java内存区域 1.1 运行时数据区 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.主要包括:程序计数器.虚拟机栈.本地方法栈.Java堆.方法区(运 ...
- JVM性能优化系列-(2) 垃圾收集器与内存分配策略
2. 垃圾收集器与内存分配策略 垃圾收集(Garbage Collection, GC)是JVM实现里非常重要的一环,JVM成熟的内存动态分配与回收技术使Java(当然还有其他运行在JVM上的语言,如 ...
- JVM性能优化系列-(3) 虚拟机执行子系统
3. 虚拟机执行子系统 3.1 Java跨平台的基础 Java刚诞生的宣传口号:一次编写,到处运行(Write Once, Run Anywhere),其中字节码是构成平台无关的基石,也是语言无关性的 ...
随机推荐
- C#总结(七)动态加载C++动态链接库
C#调用C++ 链接库的方式分为静态调用和动态调用这两种方式.静态调用之前的文章里面都有介绍,使用.net 提供的DllImport 导入相关的C++ 库即可.请看之前的文章,https://www. ...
- C#线程学习笔记八:async & await入门一
一.涉及内容 async & await是C# 5.0引入的,控制台输出所使用的$符号(拼接字符串)是C# 6.0引入的,其功能类似于string.Format()方法. 二.多线程.异步.同 ...
- leetcode之缺失的第一个正数
给定一个未排序的整数数组,找出其中没有出现的最小的正整数. 示例 1: 输入: [1,2,0]输出: 3示例 2: 输入: [3,4,-1,1]输出: 2示例 3: 输入: [7,8,9,11,12] ...
- 「SAP技术」SAP VL02N 执行批次拆分报错,说不允许批次拆分?
1,如下新建的DN 80017843,storage location 字段值为空.VL02N 试图去做批次拆分失败,系统报错说,Batch split is not permitted for ma ...
- 使用SQL生成指定数据库的数据字典(MSSQL)
USE DBNAME --指定要生成数据字典的数据库 GO SELECT 表名= CASE WHEN a.colorder= 1 THEN d.name ELSE '' END, 表说明= CASE ...
- CentOS7 下nginx与PHP的安装与配置
下载Nginx 1.在服务器上新建文件夹 /home/soft/ ; 2.cd /home/soft/ => 执行命令下载Nginx wget http://nginx.or ...
- docker alphine 安装vim 等软件
alphine 不带 yum,所以只能用apt-get 安装 apt-get update apt-get install vim apt-get install net-tools
- JavaScript图形实例:四瓣花型图案
设有坐标计算公式如下: X=L*(1+SIN(4α))*COS(α) Y=L*(1+SIN(4α))*SIN(α) 用循环依次取α值为0~2π,计算出X和Y,在canvas画布中对坐标位置(X,Y)描 ...
- 14集超详细视频教程,手把手教你用数据神器Hawk!
沙漠君在闭关4个月后,终于把开源数据神器Hawk(详细介绍在这里)的文档和教学视频基本录制完毕, 并同步更新在有爱无广告二次元的B站! 教程总时间超过3个小时,覆盖了网页采集器,数据清洗,文件读写等方 ...
- 想精通分布式以及高并发架构?那你得先搞定ZooKeeper架构原理!
Zookeeper是分布式一致性问题的工业解决方案,是Apache Hadoop下解决分布式一致性的一个组件,后被分离出来成为Apache的顶级项目. 工程来源:是雅虎公司内部项目,据说雅虎内部很多项 ...