JVM

jvm

Class格式

Java虚拟机规范规定,Class文件格式采用类似C语言结构体的伪结构来存储数据,这种结构只有两种数据类型:无符号数和表。

无符号数

属于基本数据类型,主要可以用来描述数字、索引符号、数量值或者按照UTF-8编码构成的字符串值,大小使用u1、u2、u4、u8分别表示1字节、2字节、4字节和8字节。

是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有的表都习惯以“_info”结尾。表主要用于描述有层次关系的复合结构的数据,比如方法、字段。需要注意的是class文件是没有分隔符的,所以每个的二进制数据类型都是严格定义的。具体的顺序定义如下:

jvm

从二进制的数据来看:

jvm

通过javap编译成可视化语言来看:

jvm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

cafe babe 0000 0034 000f 0a00 0300 0c07
000d 0700 0e01 0006 3c69 6e69 743e 0100
0328 2956 0100 0443 6f64 6501 000f 4c69
6e65 4e75 6d62 6572 5461 626c 6501 0004
6d61 696e 0100 1628 5b4c 6a61 7661 2f6c
616e 672f 5374 7269 6e67 3b29 5601 000a
536f 7572 6365 4669 6c65 0100 134a 766d
436c 6173 7346 6f72 6d61 742e 6a61 7661
0c00 0400 0501 0013 6b75 726f 2f4a 766d
436c 6173 7346 6f72 6d61 7401 0010 6a61
7661 2f6c 616e 672f 4f62 6a65 6374 0021
0002 0003 0000 0000 0002 0001 0004 0005
0001 0006 0000 001d 0001 0001 0000 0005
2ab7 0001 b100 0000 0100 0700 0000 0600
0100 0000 0300 0900 0800 0900 0100 0600
0000 1900 0000 0100 0000 01b1 0000 0001
0007 0000 0006 0001 0000 0006 0001 000a
0000 0002 000b
1
2
3
4
5
6
7
魔法数字: cafe babe
次版本号: 0000
主版本号: 0034 JDK1.8
常量数量: 000f 从1开始
#1常量 : 0a 表示表中第十项(CONSTANT_Methodref_info)
: 00 03 指向#3常量
: 00 0c 指向#13常量

jvm

详情可查阅查阅:

jvm

类初始化的过程

jvm

加载

1.通过一个类的全限定名来获取定义此类的二进制字节流。

2.将这个字节流所代表的的静态存储结构转化成访问区的运行时数据结构。

3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

验证

文件格式验证、元数据验证、字节码验证、符号引用验证等等。

如验证是否以0xCAFEBABE开头

准备

为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。

这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配到Java堆中。

正常情况下,这里初始化的值是静态变量的数据类型的默认值,而不是属性指定的值,如果它还被final修饰了,那么将会在这个阶段直接初始化成属性指定的值。

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

初始化

执行类构造器<clinit>()方法,<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,静态语句块中只能访问到定义在静态语句之前的变量

也就是说,静态属性和静态代码块的赋值和调用在初始化过程中执行,先执行父类的,再执行子类的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class T001_ClassLoadingProcedure {
public static void main(String[] args) {
//ClassLoader加载T对象:
//1.加载
//2.验证
//3.准备--初始化静态变量默认值count = 0; T t = null
//4.解析--按照顺序赋值静态变量 count = 2; T t = new T();--->调用构造方法-->count++;
System.out.println(T.count); //输出3
}
}

class T {
public static int count = 2; //0
public static T t = new T(); // null


private T() {
count ++;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class T001_ClassLoadingProcedure {
public static void main(String[] args) {
//ClassLoader加载T对象:
//1.加载
//2.验证
//3.准备--初始化静态变量默认值count = 0; T t = null
//4.解析--按照顺序赋值静态变量 T t = new T();--->调用构造方法-->count++; count = 1;
// --按照顺序赋值静态变量 count = 2;(覆盖掉之前的值)
System.out.println(T.count); //输出2
}
}

class T {

public static T t = new T(); // null
public static int count = 2; //0

private T() {
count ++;
}
}

如果是Object o = new Object(),有以下几步:

1、申请内存空间,这时候成员变量均是默认值

2、调用构造方法,初始化成员变量值

3、建立栈上和堆内存对象的关联关系

1
2
3
4
5
6
//当我们调用构造方法时,java的底层的字节码指令如下:
0: new #2 // class java/lang/Object 申请内存空间
3: dup // 复制内存空间地址,供以调用构造方法时出栈使用
4: invokespecial #1 // Method java/lang/Object."<init>":()V 调用构造方法
7: astore_1 // Object o 指向开辟的内存地址
8: return

类加载器

jvm

jvm

如果一个类加载器收到了类加载的请求,它不会先尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己加载。

这里的父-子是通过使用组合关系,而不是使用继承关系。

父类加载器不是类加载器的加载器,也不是类加载器的父类加载器。双亲委派是一个孩子向父亲方向,然后父亲向孩子方向的双亲委派过程。

为什么用双亲委派机制?

安全,保证了Java程序的稳定运行。避免核心类库被用户覆盖。

查看各个类加载器加载的路径及信息可以查阅Launcher.java

1
2
3
BootStrap ClassLoader:sun.boot.class.path
ExtClassLoader:java.ext.dirs
AppClassLoader:java.class.path

JDK破坏双亲委派机制的历史

双亲委派模型的第一次被破坏发生在双亲委派模型出现之前,由于双亲委派模型在JDK1.2之后才被引入,为了向前兼容,JDK1.2之后添加了一个findClass()方法。

双亲委派模型的第二次被破坏是由于模型自身的缺陷导致的,有些标准服务是由启动类加载器(Bootstrap)去加载的,但它又需要调用独立厂商实现并部署在应用程序的ClassPath下的代码,为了解决这个问题,引入了线程上下文类加载器,如果有了线程上下文类加载器,父类加载器将会请求子类加载器去完成类加载动作。

双亲委派模型的第三次被破坏是由于用户对程序动态性的追求导致的。如热替换、热部署。

假设每个程序都有一个自己的类加载器,当需要更换一个代码片段时,就把这个代码片段连同类加载器一起换掉实现代码的热替换。

自定义类加载器的实现是通过继承ClassLoader并复写它的findClass方法即可。若要破坏双亲委派模型,则还需要重写loadClass方法。

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
//File To byte[]
byte[] bytes = FileUtils.readFileToByteArray(new File("xxx"));
//调用父类的defineClass装载
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name); //throws ClassNotFoundException
}

编译器和解释器

Java默认采用混合模式,初期通过编译器编译Class文件的代码,当出现热点代码时,会通过JIT解释器把热点代码解释成本地代码,提高运行效率。

1
2
3
4
5
6
# 热点代码的阈值频次
-XX:CompileThreshold = 10000
# 使用编译器运行
-Xcomp
#使用解释器运行
-Xint

内存模型

Synchronized加锁原语、CPU缓存、MESI、缓存行、缓存对齐 详细内容

对象定位

句柄池、直接指针

原语指令

1
2
3
4
5
6
7
8
9
10
11
public class Hello_03 {
public static void main(String[] args) {
Hello_03 h = new Hello_03();
int i = h.m1();
}
public int m1() {
int i = 1;
i = i++;
return i;
}
}

上文代码将解析成以下原语指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#main方法
#开辟堆上的内存空间,并压入栈中
0 new #2 <com/jvm/c4_RuntimeDataAreaAndInstructionSet/Hello_03>
#复制栈中的内存空间对象地址,并压入栈中
3 dup
#弹出栈中复制的对象,调用构造方法<init>初始化属性
4 invokespecial #3 <com/jvm/c4_RuntimeDataAreaAndInstructionSet/Hello_03.<init>>
#弹出内存空间对象地址,赋值地址给局部变量表中index=1的对象
7 astore_1
#把局部变量表index=1的对象压入操作数栈中
8 aload_1
#使用invokevirtual执行m1方法,将返回值压入栈中
9 invokevirtual #4 <com/jvm/c4_RuntimeDataAreaAndInstructionSet/Hello_03.m1>
#把方法返回的结果集弹出操作数栈,赋值局部变量表中index=2的对象
12 istore_2
#结束方法
13 return
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#m1方法
#压入常量值 1入操作数栈 const前面的i表示整数int
0 iconst_1
#常量值1出栈,赋值局部变量表中index=1的对象
1 istore_1
#把局部变量表中index=1的对象压入栈中
2 iload_1
#局部变量表中的index=1的对象自增1
3 iinc 1 by 1
#弹出栈,赋值给局部变量表中index=1对象
6 istore_1
#把i压入操作数栈中(压入调用该方法的操作数栈中)
7 iload_1
8 ireturn
1
2
3
4
5
6
7
8
9
10
#局部变量表
#main方法---静态方法没有this
index name
0 args
1 h
2 i
#m1方法---index=0是this对象
index name
0 this
1 i
1
2
3
4
5
6
7
8
9
10
11
invokeinterface:
用以调用接口方法,在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
invokevirtual:
指令用于调用对象的实例方法,根据对象的实际类型进行分派,用于多态,public方法
invokestatic:
调用一个类的静态方法
invokespecial:
指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法(构造方法)、私有方法和父类方法。
invokedynamic:
JDK1.7新加入的一个虚拟机指令,它允许应用级别的代码来确定执行哪一个方法调用。
只有在调用要执行的时候,才会进行这种判断,从而达到动态语言的支持。如lumbda的函数式接口(A::a)

垃圾回收

详细内容

参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#小型程序。默认情况下不会是这种选项,HotSpot会根据计算及配置和JDK版本自动选择收集器
-XX:+UseSerialGC = Serial New (DefNew) + Serial Old

#这个组合已经很少用(在某些版本中已经废弃)
-XX:+UseParNewGC = ParNew + SerialOld

-XX:+UseConc(urrent)MarkSweepGC = ParNew + CMS + Serial Old

#JDK1.8默认
-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认) 【PS + SerialOld】

-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old

#JDK1.9默认
-XX:+UseG1GC = G1
1
2
查看默认参数配置
+XX:+PrintCommandLineFlags -version

标准: - 开头,所有的HotSpot都支持

非标准:-X 开头,特定版本HotSpot支持特定命令

不稳定:-XX 开头,下个版本可能取消

1
2
3
4
5
6
7
8
9
10
public class HelloGC {
public static void main(String[] args) {
System.out.println("HelloGC!");
List list = new LinkedList();
for(;;) {
byte[] b = new byte[1024*1024];
list.add(b);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
#区分概念:内存泄漏memory leak,内存溢出out of memory
java -XX:+PrintCommandLineFlags HelloGC
#-Xmn10M 新生代大小 -Xms40M 堆最小内存 -Xmx60M 堆最大内存 -XX:+PrintGC输出GC信息
java -Xmn10M -Xms40M -Xmx60M -XX:+PrintCommandLineFlags -XX:+PrintGC HelloGC
#打印GC详细信息 打印GC时间 打印GC的原因
PrintGCDetails PrintGCTimeStamps PrintGCCauses
java -XX:+UseConcMarkSweepGC -XX:+PrintCommandLineFlags HelloGC
java -XX:+PrintFlagsInitial 默认参数值
java -XX:+PrintFlagsFinal 最终参数值
java -XX:+PrintFlagsFinal | grep xxx 找到对应的参数
java -XX:+PrintFlagsFinal -version |grep GC

GC日志详解

调优

  1. 吞吐量:用户代码时间 /(用户代码执行时间 + 垃圾回收时间)
  2. 响应时间:STW越短,响应时间越好

所谓调优,首先确定,追求吞吐量优先,还是响应时间优先?还是在满足一定的响应时间的情况下,要求达到多大的吞吐量…,吞吐量优先 一般(PS + PO),响应时间 一般(1.8 G1)

什么是调优?

  1. 根据需求进行JVM规划和预调优
  2. 优化运行JVM运行环境(慢,卡顿)
  3. 解决JVM运行过程中出现的各种问题(OOM)

规划调优

  • 调优,从业务场景开始,没有业务场景的调优都是耍流氓

  • 无监控(压力测试,能看到结果),不调优

  • 步骤:

    1. 熟悉业务场景(没有最好的垃圾回收器,只有最合适的垃圾回收器)

      1. 响应时间、停顿时间 [CMS G1 ZGC] (需要给用户作响应)
      2. 吞吐量 = 用户时间 /( 用户时间 + GC时间) [PS]

      预调优

    2. 选择回收器组合

    3. 计算内存需求(经验值 1.5G 16G)

    4. 选定CPU(越高越好)

    5. 设定年代大小、升级年龄

    6. 设定日志参数(循环5个日志文件,100M)

      1. -Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
      2. 或者每天产生一个日志文件
    7. 观察日志情况

预调优

QPS:一秒内查询的并发量 TPS:一秒内业务的并发量

案例1:垂直电商,最高每日百万订单,处理订单系统需要什么样的服务器配置?

这个问题比较业余,因为很多不同的服务器配置都能支撑(1.5G 16G)

1小时360000集中时间段, 100个订单/秒,(找一小时内的高峰期,1000订单/秒)

经验值,

非要计算:一个订单产生需要多少内存?512K * 1000 500M内存

专业一点儿问法:要求响应时间100ms

压测!

案例2:12306遭遇春节大规模抢票应该如何支撑?

12306应该是中国并发量最大的秒杀网站:

号称并发量100W最高

CDN -> LVS -> NGINX -> 业务系统 -> 每台机器1W并发(10K问题) 100台机器

普通电商订单 -> 下单 ->订单系统(IO)减库存 ->等待用户付款

12306的一种可能的模型: 下单 -> 减库存 和 订单(redis kafka) 同时异步进行 ->等付款

减库存最后还会把压力压到一台服务器

可以做分布式本地库存 + 单独服务器做库存均衡

大流量的处理方法:分而治之

怎么得到一个事务会消耗多少内存?

  1. 弄台机器,看能承受多少TPS?是不是达到目标?扩容或调优,让它达到
  2. 用压测来确定

优化环境

有一个50万PV的资料类网站(从磁盘提取文档到内存)原服务器32位,1.5G 的堆,用户反馈网站比较缓慢,因此公司决定升级,新的服务器为64位,16G 的堆内存,结果用户反馈卡顿十分严重,反而比以前效率更低了

1
2
3
4
5
6
7
8
1.为什么原网站慢? 
很多用户浏览数据,很多数据load到内存,内存不足,频繁GC,STW长,响应时间变慢

2.为什么会更卡顿?
内存越大,FGC时间越长

3.咋办?
PS -> PN + CMS 或者 G1

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

1
2
3
4
5
6
**CPU100%那么一定有线程在占用系统资源**
1. 找出哪个进程cpu高(top)
2. 该进程中的哪个线程cpu高(top -Hp)
3. 导出该线程的堆栈 (jstack)
4. 查找哪个方法(栈帧)消耗时间 (jstack)
5. 工作线程占比高 | 垃圾回收线程占比高

系统内存飙高,如何查找问题?

1
2
1. 导出堆内存 (jmap)
2. 分析 (jhat jvisualvm mat jprofiler ... )

如何监控JVM?

jstat jvisualvm jprofiler arthas top…

jstack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
jstack 
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F [-m] [-l] <pid>
(to connect to a hung process)
jstack [-m] [-l] <executable> <core>
(to connect to a core file)
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
(to connect to a remote debug server)

Options:
-F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
-m to print both java and native frames (mixed mode)
-l long listing. Prints additional information about locks
-h or -help to print this help message
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@localhost ~]# jstack -l <pid>
//JVM内部的Reference Handler的守护线程 线程号 nid=0x12f27 十六进制
"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f00e41d1000 nid=0x12f27 in Object.wait() [0x00007f00ad648000]
//线程状态WAITING,等待其他线程nonitor唤醒
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000006c80eada8> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
//持有的锁
Locked ownable synchronizers:
- None

"VM Thread" os_prio=0 tid=0x00007f00e41c7000 nid=0x12f26 runnable

//GC线程正在运行
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f00e401e800 nid=0x12f1e runnable

两个线程都互相等待锁信息,死锁。

1
2
3
4
5
6
7
8
9
10
11
"Thread-1":
at com.kuro.concurrent.LockedOwnThread.run(LockedOwnThread.java:47)
- waiting to lock <0x000000076c5806f8> (a java.lang.Class for java.lang.Object)
- locked <0x000000076c636568> (a java.lang.Class for com.mirana.concurrent.LockedOwnThread)
- locked <0x000000076c6392f0> (a com.kuro.concurrent.LockedOwnThread)
"Thread-0":
at com.kuro.concurrent.LockedOwnThread$AThread.run(LockedOwnThread.java:27)
- waiting to lock <0x000000076c636568> (a java.lang.Class for com.mirana.concurrent.LockedOwnThread)
- locked <0x000000076c5806f8> (a java.lang.Class for java.lang.Object)

Found 1 deadlock.

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* 从数据库中读取信用数据,套用模型,并把结果进行记录和传输
*/

public class T15_FullGC_Problem01 {

private static class CardInfo {
BigDecimal price = new BigDecimal(0.0);
String name = "张三";
int age = 5;
Date birthdate = new Date();

public void m() {}
}

private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,new ThreadPoolExecutor.DiscardOldestPolicy());

public static void main(String[] args) throws Exception {
executor.setMaximumPoolSize(50);

for (;;){
modelFit();
Thread.sleep(100);
}
}

private static void modelFit(){
List<CardInfo> taskList = getAllCardInfo();
taskList.forEach(info -> {
// do something
executor.scheduleWithFixedDelay(() -> {
//do sth with info
info.m();

}, 2, 3, TimeUnit.SECONDS);
});
}

private static List<CardInfo> getAllCardInfo(){
List<CardInfo> taskList = new ArrayList<>();

for (int i = 0; i < 100; i++) {
CardInfo ci = new CardInfo();
taskList.add(ci);
}

return taskList;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
1.java -Xms200M -Xmx200M -XX:+PrintGC T15_FullGC_Problem01

2.一般是运维团队首先受到报警信息(CPU Memory)

3.top命令观察到问题:内存不断增长 CPU占用率居高不下

4.top -Hp 观察进程中的线程,哪个线程CPU和内存占比高

5.jps定位具体java进程
jstack 定位线程状况
重点关注:WAITING BLOCKED
waiting on <0x0000000088ca3310> (a java.lang.Object)
假如有一个进程中100个线程,很多线程都在waiting on ,一定要找到是哪个线程持有这把锁
怎么找?搜索jstack dump的信息,找 ,看哪个线程持有这把锁RUNNABLE

为什么阿里规范里规定,线程的名称(尤其是线程池)都要写有意义的名称
怎么样自定义线程池里的线程名称?(自定义ThreadFactory)

6.jinfo pid

7.jstat -gc 动态观察gc情况 / 阅读GC日志发现频繁GC / arthas观察 / jconsole/jvisualVM/ Jprofiler(最好用)
jstat -gc 4655 500 : 每个500个毫秒打印GC的情况

8.jmap - histo 4655 | head -20,查找有多少对象产生

9jmap -dump:format=b,file=xxx pid :
线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合)
1:设定了参数HeapDump,OOM的时候会自动产生堆转储文件
2:很多服务器备份(高可用),停掉这台服务器对其他服务器不影响
3:在线定位(一般小点儿公司用不到)

10.java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError T15_FullGC_Problem01

11.使用MAT / jhat /jvisualvm 进行dump文件分析
jhat -J-mx512M xxx.dump http://localhost:7000
拉到最后:找到对应链接 可以使用OQL查找特定问题对象

12.找到代码的问题
原因是因为创建线程的频率比消费线程的频率高,线程池队列上锁引起等待的线程堆积过多,才造成的对象过多内存泄漏

远程连接

jconsole远程服务器程序,程序启动需要增加以下参数:

1
java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false XXX

案例汇总

OOM产生的原因多种多样,有些程序未必产生OOM,不断FGC(CPU飙高,但内存回收特别少) (上面案例)

  1. 硬件升级系统反而卡顿的问题(见上)

  2. 线程池不当运用产生OOM问题(见上)

  3. tomcat http-header-size过大问题

  4. lambda表达式导致方法区溢出问题(MethodArea / Perm Metaspace)

    1
    2
    3
    4
    5
    6
    7
    8
    java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:388)
    at sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain(InstrumentationImpl.java:411)
    Caused by: java.lang.OutOfMemoryError: Compressed class space
  5. 栈溢出问题 -Xss设定太小

  6. 比较一下这两段程序的异同,分析哪一个是更优的写法:

    1
    2
    3
    4
    5
    6
    7
    8
    Object o = null;
    for(int i=0; i<100; i++) {
    o = new Object();
    //业务处理
    }
    for(int i=0; i<100; i++) {
    Object o = new Object();
    }
  7. 重写finalize引发频繁GC 小米云,HBase同步系统,系统通过nginx访问超时报警,最后排查,C++程序员重写finalize引发频繁GC问题 为什么C++程序员会重写finalize?(new delete) finalize耗时比较长(200ms)

  8. 如果有一个系统,内存一直消耗不超过10%,但是观察GC日志,发现FGC总是频繁产生,会是什么引起的? System.gc()

参数汇总

GC常用参数

  • -Xmn -Xms -Xmx -Xss 年轻代 最小堆 最大堆 栈空间
  • -XX:+UseTLAB 使用TLAB,默认打开
  • -XX:+PrintTLAB 打印TLAB的使用情况
  • -XX:TLABSize 设置TLAB大小
  • -XX:+DisableExplictGC System.gc()不管用 ,FGC
  • -XX:+PrintGC
  • -XX:+PrintGCDetails
  • -XX:+PrintHeapAtGC
  • -XX:+PrintGCTimeStamps
  • -XX:+PrintGCApplicationConcurrentTime (低) 打印应用程序时间
  • -XX:+PrintGCApplicationStoppedTime (低) 打印暂停时长
  • -XX:+PrintReferenceGC (重要性低) 记录回收了多少种不同引用类型的引用
  • -verbose:class 类加载详细过程
  • -XX:+PrintVMOptions
  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 必须会用
  • -Xloggc:opt/log/gc.log
  • -XX:MaxTenuringThreshold 升代年龄,最大值15
  • 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 … 这些不建议设置

Parallel常用参数

  • -XX:SurvivorRatio
  • -XX:PreTenureSizeThreshold 大对象到底多大
  • -XX:MaxTenuringThreshold
  • -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
  • -XX:+UseAdaptiveSizePolicy 自动选择各区大小比例

CMS常用参数

  • -XX:+UseConcMarkSweepGC
  • -XX:ParallelCMSThreads CMS线程数量
  • -XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)
  • -XX:+UseCMSCompactAtFullCollection 在FGC时进行压缩
  • -XX:CMSFullGCsBeforeCompaction 多少次FGC之后进行压缩
  • -XX:+CMSClassUnloadingEnabled
  • -XX:CMSInitiatingPermOccupancyFraction 达到什么比例时进行Perm回收
  • GCTimeRatio 设置GC时间占用程序运行时间的百分比
  • -XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代

G1常用参数

  • -XX:+UseG1GC
  • -XX:MaxGCPauseMillis 建议值,G1会尝试调整Young区的块数来达到这个值
  • -XX:GCPauseIntervalMillis ?GC的间隔时间
  • -XX:+G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32。 随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长 ZGC做了改进(动态区块大小)
  • G1NewSizePercent 新生代最小比例,默认为5%
  • G1MaxNewSizePercent 新生代最大比例,默认为60%
  • GCTimeRatio GC时间建议比例,G1会根据这个值调整堆空间
  • ConcGCThreads 线程数量
  • InitiatingHeapOccupancyPercent 启动G1的堆空间占用比例

最后更新: 2020年12月21日 21:15

原始链接: https://midkuro.gitee.io/2020/10/29/jvm-base/

× 请我吃糖~
打赏二维码