JAVA虚拟机-JVM学习随笔

JVM内存结构

首先jvm结构分三部分:类装载器子系统,运行时数据区,执行引擎
Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分为若干个不同的数据区域。每个区域都有各自的作用

运行时数据区

程序寄存器、Java虚拟机栈、Java堆、方法区、运行时常量池、本地方法栈等

程序寄存器

每个线程都拥有一个程序寄存器,是线程私有的,用来存储指向下一条指令的地址
在创建线程的时候,创建相应的程序寄存器
执行本地方法时,程序寄存器的值为 undefined
是一块比较小的内存空间,是唯一一个在JVM规范中没有规定 OutOfMemoryError 的内存区域

Java栈

栈由一系列帧(栈帧)(Frame)组成(因此Java栈也叫做帧栈),是线程私有的
栈帧用来保存一个方法的局部变量、操作数栈(Java没有寄存器,所有参数传递使用操作数栈)、常量池指针、动态链接、方法返回等
每一次方法调用创建一个帧,并压栈,退出方法的时候,修改栈顶指针就可以把栈帧中的内容销毁
局部变量表存放了编译期可知的各种基本数据类型和引用类型,每个 slot 存放32位的数据,long、double、占两个槽位
栈的优点: 存取速度比堆块,仅次于寄存器
栈的缺点:存在栈中的数据大小、生存区是在编译器决定的,缺乏灵活性

Java堆

用来存放应用系统创建的对象和数组,所有线程共享 Java 堆
GC主要管理堆空间,对分代GC来说,堆也是分代的
堆的优点:运行期动态分配内存大小,自动进行垃圾回收;
堆的缺点:效率相对较慢

方法区

方法区是线程共享的,通常用来保存装载的类的结构信息
通常和元空间关联在一起,但具体的跟JVM实现和版本有关
JVM规范把方法区描述为堆的一个逻辑部分,但它有一个别名称为 Non-heap(非堆),应是为了与 Java 堆分开

运行时常量池

是Class文件中每个类或接口的常量池表,在运行期间的表示形式,通常包括:类的版本、字段、方法、接口等信息在方法区中分配
通常在加载类和接口到JVM后,就创建相应的运行时常量池

本地方法栈

在 JVM 中用来支持 native 方法执行的栈就是本地方法栈

GC 常用参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-Xmn                                            年轻代
-Xms 最小堆
-Xmx 最大堆
-Xss 栈空间
-XX:+UseTLAB 使用TLAB(本地线程分配缓冲区),默认打开
-XX:+PrintTLAB 打印TLAB的使用情况
-XX:TLABSize 设置TLAB大小
-XX:+DisableExplictGC 禁用System.gc()不管用,防止FGC
-XX:+PrintGC 打印GC日志
-XX:+PrintGCDetails 打印GC详细日志信息
-XX:+PrintHeapAtGC 打印GC前后的详细堆栈信息
-XX:+PrintGCTimeStamps 打印时间戳
-XX:+PrintGCApplicationConcurrentTime 打印应用程序时间
-XX:+PrintGCApplicationStoppedTime 打印暂停时长
-XX:+PrintReferenceGC 记录回收了多少种不同引用类型的引用
-verbose:class 类加载详细过程
-XX:+PrintVMOptions JVM参数
-XX:+PrintFlagsFinal 查看JVM所有参数值
-XX:+PrintFlagsInital 查看JVM参数启动的初始值
-Xloggc:opt/log/gc.log GC日志的路径以及文件名称
-XX:MaxTenuringThreshold 升代年龄,最大值15

垃圾收集器组合选择

  • 单CPU或小内存,单机程序
    • -XX:+UseSerialGC
  • 多CPU,需要最大吞吐量,如后台计算型应用
    • -XX:+UseParallelGC
    • -XX:+UseParallelOldGC
  • 多CPU,追求低停顿时间,需快速响应如互联网应用
    • -XX:+UseConcMarkSweepGC
    • -XX:+ParNewGC

参数 新生代垃圾收集器 新生代算法 老年代垃圾收集器 老年代算法
-XX:+UseSerialGC SerialGC 复制 SerialOldGC 标记-整理
-XX:+UseParallelGC ParNew 复制 SerialOldGC 标记-整理
-XX:+UseParallelGC
-XX:UseParallelOldGC
Parallel[Scavenge] 复制 SerialOldGC 标记-整理
-XX:+UseConcMarkSweepGC ParNew 复制 SerialOldGC 标记-整理
-XX:+UseG1GC G1整体上采用标记-整理算法 局部通过复制算法,不会产生内存碎片 CMS+SerialOldGC的收集器组合,SerialOldGC作为CMS出错的后备垃圾收集器 标记-清除

系统CPU经常100%,如何调优

CPU100%,那肯定是有线程一直在占用着系统资源,所以具体方法如下:

1、找出哪个进程cpu占用高 (top 命名)
2、该进程中的哪个线程cpu占用高 (top -Hp $pid 命令)
3、将十进制的tid转化为十六进制 (printf %x $tid 命令)
4、导出该线程的堆栈 (jstack $pid > $pid.log 命令)
5、查找哪个方法(栈帧)消耗时间 (less $pid.log)
6、可以确认工作线程占比高还是垃圾回收线程占比高
7、修改代码

实际调优场景

OOM - zipkin分布式链路追踪

项目之前采取zipkin做分布式链路追踪,后来换成skywalking,所以将zipkin相关配置和代码都移除了,但是忘记移除maven坐标了,运行一段时间后导致了频繁full gc,最后OOM了

因为配置了OOM后自动生成dump1文件,所以分析dump文件发现大对象是zipkin包里的ConcurrentHashMap$Node,通过观察zipkin的自动配置类ZipkinAutoConfiguration发现即使没有任何zipkin的配置,只要有zipkin的依赖都会创建一个异步报告者,默认的采样率为10%。所以即使不配置相关项,也会以默认采样率10%,发送到zipkin,这是默认的地址是http://localhost:9411/,此时发送到localhost:9411显然会被拒绝。导致堆中的异常实例堆积,从而OOM

1
2
3
4
5
6
private float probability = 0.1f;

@ConfigurationProperties("spring.zipkin")
public class ZipkinProperties {
private String baseUrl = "http://localhost:9411/";
}