JVM底层原理及调优
1.java虚拟机内存模型(JVM内存模型)
1.堆(-Xms -Xmx -Xmn)
java堆,也称为GC堆,是JVM中所管理的内存中最大的一块内存区域,是线程共享的,在JVM启动时创建。存放了对象的实例及数组(所有new的对象),
无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中;
2.线程栈
每个线程存在一个独立的线程栈内存区域,每个线程栈内存中会为每个方法创建各自的栈帧,栈帧中包含:局部变量表、操作数栈、动态链接、方法出口等;
栈中存放:方法调用过程中基本数据类型的变量(int、short、long、byte、float、double、boolean、char等)以及对象的引用变量;
其中64位长度的long和double类型的数据会占用两个局部变量的空间,其余数据类型只占一个;
3.方法区(元空间/永久代)
存放常量、静态变量、类信息、即时编译器后的代码,运行时常量池:方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等描述信息外,
还有一项信息就是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中,
元空间取代永久代,隔离堆和元空间的垃圾回收,避免频繁Full GC以及OOM等问题;
4.程序计数器
当前线程所执行的字节码的行号指示器,程序计数器的意义:
1.确保多线程程序的正常运行
2.java是多线程的,意味着线程存在上下文切换
2.GC的基础知识
2.1 什么是垃圾? 没有任何引用指向的一个对象或者多个对象(循环引用)
2.2 如何定位垃圾? 引用计数和roots可达性分析 (java使用的是根可达性分析算法)
在Java语言中,GC Roots包括:
线程栈变量
静态变量
常量池
JNI指针
2.3 GC垃圾回收算法比较
GC回收算法比较 |
特性 |
标记清除算法 |
位置分散,容易产生碎片 |
复制算法 |
浪费空间 |
标记压缩算法 |
效率低(适用老年代) |
分代收集算法 |
算法搭配灵活(new-复制,old-标记压缩) |
2.4 新生代 = Eden + 2个survivor区 new:old = 1: 2
2.5 新生代、老年代数据流转大致过程
新生代 = Eden + 2个survivor区
1. YGC回收之后,大多数的对象会被回收,活着的进入s0
2. 再次YGC,活着的对象eden + s0 -> s1
3. 再次YGC,eden + s1 -> s0
4. 年龄足够 -> 老年代 (15 CMS 6)
5. s区装不下 -> 老年代
老年代
1. 顽固分子
2. 老年代满了FGC Full GC
GC回收
1.尽量减少FGC
2 minorGC = YoungGC
3 majorGC = FullGC
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
a.调用System.gc时,系统建议执行Full GC,但是不必然执行
b.老年代空间不足
c.方法去空间不足
d.通过Minor GC后进入老年代的平均大小大于老年代的可用内存
e.由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
2.6 常见的垃圾回收器
1. Serial 年轻代 串行回收 GC线程回收,其他线程暂停
2. PS 年轻代 并行回收
3. ParNew 年轻代 配合CMS的并行回收 Serial收集器新生代的并行版本
4. SerialOld
5. ParallelOld
6. ConcurrentMarkSweep(CMS) 老年代 并发的, 垃圾回收和应用程序同时运行,降低STW的时间(200ms)
CMS问题较多,一般都需要手动指定。CMS采用标记清除算法回收,所以就会存在产生碎片的问题,当老年代分配的对象分配不下的情况,需要配合SerialOld进行老年代的回收
7. G1(10ms)
8. ZGC (1ms) 相当于 C++
9. Shenandoah
10.Eplison
1.8默认的垃圾回收:PS + ParallelOld
3.GC调优准备
3.1 了解生产环境下的垃圾回收器组合 打印JVM内存信息 java -XX:+PrintCommandLineFlags
3.2 JVM的命令行参数参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
* JVM参数分类
> 标准: - 开头,所有的HotSpot都支持
> 非标准:-X 开头,特定版本HotSpot支持特定命令
> 不稳定:-XX 开头,下个版本可能取消
1. java -XX:+PrintCommandLineFlags
2. java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags
3. java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags
4. java -XX:+PrintFlagsInitial 默认参数值
5. java -XX:+PrintFlagsFinal 最终参数值
6. java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数
7. java -XX:+PrintFlagsFinal -version |grep GC
* 常见垃圾回收器组合参数设定:(1.8)
* -XX:+UseSerialGC = Serial New (DefNew) + Serial Old
* 小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
* -XX:+UseParNewGC = ParNew + SerialOld
* 这个组合已经很少用(在某些版本中已经废弃)
* https://stackoverflow.com/questions/34962257/why-remove-support-for-parnewserialold-anddefnewcms-in-the-future
* UseConcMarkSweepGC = ParNew + CMS + Serial Old
* UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】
* UseParallelOldGC = Parallel Scavenge + Parallel Old
* UseG1GC = G1
常用的GC组合
参数 |
描述 |
UseSerialGC |
Serial New+ Serial Old |
UseParNewGC |
ParNew+Serial Old(rarely dispose) |
UseConcMarkSweepGC |
ParNew+CMS+Serial Old |
UseParallelGC/UseParallelOldGC |
Parallel Scavenge+Parallel Old(1.8默认) |
UseG1GC |
G1 |
2.3 JVM调优的步骤
* 步骤: 1. 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器) 1. 响应时间、停顿时间 2. 吞吐量 = 用户时间 / 用户时间 + GC时间 响应时间和吞吐量无法同时调优 响应时间快 == GC停顿时间短 2. 选择回收器组合 3. 计算内存需求 4. 设定年代大小、升级年龄 5. 设定日志参数 1. -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause 6. 观察日志情况
4.GC日志查看
通过在java命令种加入参数来指定对应的gc类型,打印gc日志信息并输出至文件等策略
java -XX:+PrintGCDateStamaps -XX:+PrintGCDetails GCTest
-XX:+PrintGC 输出GC日志 -XX:+PrintGCDetails 输出GC的详细日志 -XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式) -XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800) -XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息 -Xloggc:../logs/gc.log 日志文件的输出路径
2019-11-19T11:50:42.352+0800[当前时间戳]: 0.169[时间戳]: [GC[Young GC (Allocation Failure) [PSYoungGen: 63744K[回收前年轻代大小]->9680K[回收后年轻代大小](74240K)总年轻代大小] 63744K回收前堆大小->62928K回收后堆大小(243712K分配堆的总大小), 0.0092841【消耗总时间】 secs] [Times: user=0.02【用户花费时间】 sys=0.03【系统花费时间】,real =0.01 secs [总消耗时间]】
2019-11-19T11:50:42.369+0800: 0.186: [GC (Allocation Failure) [PSYoungGen: 73412K->9728K(138240K)] 126661K->126473K(307712K), 0.0106607 secs [Times: user=0.03 sys=0.03, real=0.01 secs】
2019-11-19T11:50:43.326+0800: 1.142: [Full GC (Ergonomics) [PSYoungGen: 798012K->797708K(808960K)] [ParOldGen: 2708855K->2708855K(2708992K)] 3506867K->3506564K(3517952K), [Metaspace: 2735K->2735K(1056768K)], 0.0182299 secs] [Times: user=0.04 sys=0.00, real=0.02 secs]
2019-11-19T11:50:43.344+0800: 1.160: [Full GC (Allocation Failure) [PSYoungGen: 797708K->797708K(808960K)] [ParOldGen: 2708855K->2708844K(2708992K)] 3506564K->3506552K(3517952K), [Metaspace: 2735K->2735K(1056768K)], 0.6587112 secs] [Times: user=6.22 sys=0.02, real=0.66 secs]
Heap
PSYoungGen total 808960K, used 798720K [0x000000076d580000【起始地址】, 0x000000079fa00000【占用空间结束地址】, 0x00000007c0000000【整体空间结束地址】)
eden space 798720K, 100% used [0x000000076d580000,0x000000079e180000,0x000000079e180000)
from space 10240K, 0% used [0x000000079eb00000,0x000000079eb00000,0x000000079f500000)
to space 9728K, 0% used [0x000000079e180000,0x000000079e180000,0x000000079eb00000)
ParOldGen total 2708992K, used 2708845K [0x00000006c8000000, 0x000000076d580000, 0x000000076d580000)
object space 2708992K, 99% used [0x00000006c8000000,0x000000076d55b440,0x000000076d580000)
Metaspace used 2770K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 264K, capacity 386K, committed 512K, reserved 1048576K