-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathJVM.txt
350 lines (272 loc) · 18.3 KB
/
JVM.txt
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
有哪些地方打破了JAVA的类加载机制
tomcat、SPI、OSGI
OSGI:实现了模块化、每个模块可以独立安装、启动、停止、卸载
类加载过程
加载、验证、准备、解析、初始化
加载:将外部的.class文件加载到java方法区中
验证:不是所有的.class文件都能加载,太不安全了,需要验证,容易受到恶意代码的攻击
一些低版本的JVM无法加载高版本的类库,不符合规范将抛出java.lang.VerifyError错误
准备:为一些静态变量分配内存,并初始化为默认值,实例对象这个时候还没有分配内存,
这些动作在方法区做的
解析:将符号引用替换为直接引用,主要包括类、接口、类方法、接口方法、字段的解析
初始化:初始化成员变量,开始执行字节码
static语句块只能访问定义在static语句块之前的变量
JVM会保证在子类的初始化方法执行之前,父类的初始化方法已经执行完毕
JVM第一个被执行的初始化方法一定是java.lang.object,也意味着父类定义的static语句块要优先于子类的
类的初始化和对象的初始化
有哪些类加载器
Bootstrap ClassLoader
这是加载器中的大 Boss,任何类的加载行为,都要经它过问。它的作用是加载核心类库,
也就是 rt.jar、resources.jar、charsets.jar 等。当然这些 jar 包的路径是可以指定的,
-Xbootclasspath 参数可以完成指定操作。
这个加载器是 C++ 编写的,随着 JVM 启动。
Extention ClassLoader
扩展类加载器,主要用于加载 lib/ext 目录下的 jar 包和 .class 文件。
同样的,通过系统变量 java.ext.dirs 可以指定这个目录。
这个加载器是个 Java 类,继承自 URLClassLoader。
App ClassLoader
这是我们写的 Java 类的默认加载器,有时候也叫作 System ClassLoader。
一般用来加载 classpath 下的其他所有 jar 包和 .class 文件,
我们写的代码,会首先尝试使用这个类加载器进行加载。
Custom ClassLoader
自定义加载器,支持一些个性化的扩展功能。
简述双亲委派机制
除了顶层的启动类加载器,其余的加载器在加载之前都会委派给父加载器进行加载,层层向上传递
父类无法加载,才会自己加载
如何替换JDK的类
当JAVA的原生API不能满足需求时,比如我们要修改HashMap类,就要用到Java的endorsed技术
把自己的HashMap类打成jar包,放到-Djava.endorsed.dirs指定的目录中,类名和包名必须和jdk自带的一样
但是java.lang包下面的类受特殊保护,除外
java提供了endorsed技术:
关于endorsed:可以的简单理解为-Djava.endorsed.dirs指定的目录面放置的jar文件,
将有覆盖系统API的功能。可以牵强的理解为,将自己修改后的API打入到JVM指定的启动API中,
取而代之。但是能够覆盖的类是有限制的,其中不包括java.lang包中的类。
JVM的内存区域是肿么高效划分的
java的自动内存管理机制相比于C++的手动管理,方便很多
可以执行字节码的模块叫做执行引擎
执行引擎在线程切换的时候怎么恢复,依靠的就是程序计数器
堆是共享的,占用的内存最大
本地内存包括直接内存和元数据空间
栈、程序计数器的维度是线程
解释一下虚拟机栈
栈先进后出,栈里的每条数据都是一个栈帧,JAVA方法被调用的时候,都会创建一个栈帧,并入栈,完成调用就出栈
每个栈帧包含:局部变量表、操作数栈、动态链接、返回地址
程序在线程之间切换,肿么知道线程执行到哪个地方被挂起
程序计数器:记录当前线程所执行的字节码的行号指示器
为什么有MetaSpace区域,他有什么问题
放的是类的信息、常量池、方法数据和方法代码,元空间就是方法区
我们常说的字符串常量,存放在哪里
由于常量池,java7之后,放在了堆中,我们创建的字符串,将会在堆中分配
JVM的运行区域是栈,存储区域是堆,很多变量在编译器就已经固定了
怎么查看字节码文件,字节码文件长什么样子,
javap是JDK自带的反解析工具,作用是将.class字节码文件解析成可读的文件格式
jclasslib是一个图形化的工具,能够更加直观的查看字节码的内容
对象初始化后,具体的字节码又是怎么执行的
如何创建一个对象
使用Class.newInstance()方法
使用Constructor.newInstance()方法
反序列化
使用Object的clone方法
JVM是如何判断哪些对象应该被回收,哪些应该被保持呢
GC ROOTS:与GC ROOTS有关联的都是存活对象
从GC ROOTS向下溯源搜索,会产生一个叫做Reference Chain的链条,
当一个对象不跟任何一个GC ROOT有关联的时候就可以被GC了
GC ROOt有哪些
java线程中当前正在被调用的方法的引用类型参数、局部变量、临时值等,就是与栈帧相关的各种引用
当前被加载的Java类
Java类中的静态变量
常量池的引用类型
JVM内部数据结构的一些引用
用于同步监控的对象引用
弱引用有什么用处
强引用:即使OOM,指向的对象也不会被回收,只有和GC ROOT断绝关系,才会被回收
软引用:内存不足就会回收软引用指向的对象,回收以后还是不足,才会OOM,通常用于网页缓存和图片缓存
软引用和一个引用队列联合使用,如果软引用指向的对象被垃圾回收,
JAVA虚拟机会把这个软引用加入到与之关联的引用队列之中。
弱引用:更短的生命周期,一旦GC就回收
虚引用:主要用来跟踪对象被垃圾回收的活动,在对象被回收的时候,虚引用会被添加到与之关联的引用队列中
内存哪些区域会发生OOM
除了程序计数器
引起OOM通常都有哪些原因
内存的容量太小,需要扩容,或者需要调整堆的空间
发生了内存泄漏,没有及时的切断与GC ROOT的关系,
比如线程池的线程,在复用的情况下忘记清理ThreadLocal的内容
JVM中有哪些垃圾回收算法,他们各有什么优劣
内存空间达到一定条件的时候,就会自动触发GC
GC之前都是先找出活跃的对象
复制算法:内存利用率低, 但效率最高
标记清除算法:有碎片
标记整理算法:效率比前两者差,但解决了内存利用率低和碎片的问题
解释一下弱代假设
大部分对象可以分为两类:大部分死得快,其他的活得长
分为年轻代、老年代
说一下年轻代的回收机制
年轻代采用复制算法,年轻代分为Eden和两个幸存者空间,比例8:1:1,-XX:SurvivorRatio配置
会造成10%的空间浪费
当年轻代中Eden区分配满了的时候,就会触发年轻代的GC(Minor GC)
eden区发生GC,会把存活的对象移动到from survivor,如果from满了,会采用复制算法,
将存活的对象复制到to区,将from和eden区清空,整个过程中,有一个survivor是空的
说一下老年代
一般使用标记清除、标记整理算法,老年代的对象存活率比较高,拷贝不划算,采用就地收集的方式
对象进入老年代有哪些途径
提升:年轻代的对象每经历一次Minor GC,存活下来的对象的年纪就+1,达到阈值就提升到老年代
如果对象不可达,只有老年代发生GC,才会被清理掉
阈值采用参数-XX:MaxTenuringThreshold进行配置,最大15,采用4bit存储
分配担保:minor GC以后存活的对象From Survivor放不下了,就需要依赖老年代进行分配担保,
这个时候,对象就直接在老年代分配
大对象直接进入老年代
超过某个大小的对象直接在老年代分配,通过参数-XX:PreTenureeSizeThreshold进行配置
默认为0,意思是全部首选eden区分配
动态判定对象年龄
有的垃圾回收算法,并不要求age必须达到15才能晋升到老年代,会使用一些动态的计算方法,
如果幸存区中相同年龄对象大小的和大于幸存区的一半,大于等于age的对象直接进入老年代
服务卡顿的元凶到底是谁
STW
某个高并发服务的峰值流量是 10 万次/秒,后面有 10 台负载均衡的机器,
那么每台机器平均下来需要 1w/s。假如某台机器在这段时间内发生了 STW,持续了 1 秒,
那么本来需要 10ms 就可以返回的 1 万个请求,需要至少等待 1 秒钟。
在用户那里的表现,就是系统发生了卡顿。如果我们的 GC 非常的频繁,
这种卡顿就会特别的明显,严重影响用户体验。
TLAB:ThreadLocalAllocationBuffer,JVM默认给每个线程开辟一个buffer区域,用来加速对象分配
这个Buffer区域就放在eden区,TLAB是一种优化技术,
解释卡表卡页的概念
卡表就是用于标记卡页状态的一个集合,每个卡表项对应一个卡页
其实老年代被分成众多的卡页,一般2的n次方
如果年轻代有对象分配,而且老年代有对象指向这个新对象,那么这个老年代对象所对应的内存卡页就会
标识为dirty,卡表只需要非常小的存储空间就可以保留这些状态,垃圾回收的时候,先读取卡表,进行快速判断
HotSpot垃圾回收器有哪些
年轻代垃圾回收器
Serial GC:最简单的GC,轻量级的,占用的资源少,单线程,GC过程会暂停一切用户线程,通常用在客户端应用上
ParNew GC:Serial的多线程版本,由多条GC线程并行GC,GC过程需要停止用户线程,追求低停顿时间
多CPU环境下会提升一定性能,但线程间切换需要开销,所以单CPU环境下不如Serial
Parallel Scanvenge GC:多线程版本,与ParNew的区别:追求CPU吞吐量,能够在较短时间内完成指定任务
适合没有交互的后台计算,弱交互强计算。ParNew追求低停顿时间,适合交互式应用,强交互弱计算
老年代垃圾回收器
Serial Old GC:单线程版本,同样适合客户端,年轻代的Serial使用复制算法,老年代的Serial old使用标记整理算法
Parallel old 是Parallel Scanvenge的老年代版本,追求CPU吞吐量
CMS GC:Concurrent mark sweep GC是以获取最短GC停顿时间为目标的GC,GC的时候GC线程和用户线程
并发执行,长期看会被G1替代
Minor GC:年轻代的GC
Major GC:老年代的GC
Full GC:全堆GC,比如元空间引起的年轻代和老年代的回收
简述CMS
CMS:concurrent mark-sweep GC,主要并发标记清除垃圾收集器,主要是为了老年代GC避免出现长时间的卡顿,但他并不是一个老年代GC
使用的主要是sweep不是compact,所以主要问题是碎片化
随着JVM的长时间运行,碎片化越来越严重,只有通过full GC才能完成整理
CMS回收过程
初始标记:只标记直接关联GC ROOTS的对象,不用向下追溯,这个过程是STW,但时间短
并发标记:在初始标记的基础之上,进行并发标记,用于标记所有可达的对象,持续时间长,但与用户线程并行
这一阶段对象可能发生的变化:
有些对象直接分配到了老年代或者从新生代晋升到老年代
老年代或者新生代的对象引用发生了变化
并发预清理:避免回扫年轻代的大量对象
最终标记:STW,尝试在年轻代尽可能为空的情况下运行最终标记阶段,以免多次STW,
这是第二次STW,目标是完成老年代所有存活对象的标记
经过多轮的预清理,一直在和用户线程玩追赶游戏,有可能跟不上引用变化的速度
所以需要STW来处理
CMS GC把垃圾回收过程分成了多个过程,影响最大的不是STW本身,而是之前的预处理阶段
并发清除:与用户线程并行,删除掉不可达的对象,并回收他们的空间,这一过程产生的垃圾称为浮动垃圾,待下一过程GC
并发重置:与用户线程并行,重置CMS算法相关的内部数据,为下一次GC做准备
总结一下CMS都会有哪些停顿
初始标记:这部分停顿时间较短
Minor GC:在并发预清理阶段对年轻代的回收,
最终标记:由于并发预清理阶段的存在,STW时间短
Serial-old收集老年代的停顿,主要发生在预留空间不足的情况下,时间会持续很长
Full GC:永久代空间耗尽的时候的操作,由于会有整理阶段,持续时间较长。
CMS的优势和劣势
优势:低延迟,对于大堆来说,大部分垃圾回收过程并发执行
劣势:内存碎片问题,Full GC的整理阶段会造成长时间STW
CMS提供了两个参数来解决UseCMSCompactAtFullCollection,默认开启,表示Full GC的时候进行内存碎片整理
这个过程STW
CMSFullGCBeforeCompaction:每隔多少次不压缩Full GC之后,执行一次压缩Full GC,默认为0
需要预留空间,用来分配并发清除阶段产生的浮动垃圾
G1的回收原理,为什么G1回收性能比传统GC回收性能好
为什么G1如此完美还有ZGC
相比CMS,设置参数更少,更为人性化
为什么叫G1
garbageFirst GC,堆被划分成若干个小堆区,大小固定为1M到32M之间2的n此方
大小超过小堆区50%的对象将被分配到Humongous Region
G1垃圾回收过程
如何选择GC
如果堆不是很大,100MB左右,选择串行GC效率最高,-XX:UseSerialGC
应用运行在单核上,SerialGC依然合适,
如果应用吞吐量优先,选择Parallel Scanvange,-XX:UseParrallelGC
如果应用对响应时间要求较高,想要最少的停顿,1s的STW会引起大量的失败请求,那么选择G1,CMS,ZGC
大流量应用的特点:
对延迟特别敏感的系统应用,吞吐量一般可以通过堆机器解决
一般达到亿级流量的系统,承接请求的都不是一台服务器,接口都会要求快速响应,一般不会超过100ms
这种系统一般都是电商、游戏、社交、支付场景,要求短、平、快
考核指标:
TPS:每秒处理的事务数
AVG:平均响应时间
TP:TP90代表有90%的接口响应时间小于X秒
写一个JVM调优的实战例子
问题是这样的,我们的机器是 4C8GB 的,分配给了 JVM 1024*8GB/3*2= 5460MB 的空间。
那么年轻代大小就有 5460MB/3=1820MB。进而可以推断出,Eden 区的大小约 1456MB,
那么大约只需要 12 秒,就会发生一次 Minor GC。不仅如此,每隔半个小时,会发生一次 Major GC。
如果我们把一半空间给年轻代。也就是下面的配置:
-XX:+UseConcMarkSweepGC -Xmx5460M -Xms5460M -Xmn2730M
Full GC的执行时机
老年代空间不足的时候执行
元空间扩容的时候
JVM优化的思路:
程序优化,效果通常是最好的
扩容,
参数调优,在成本、吞吐量、延迟之间找到一个平衡点
可以增加一些原则来辅助完成优化:长时间的压测,使用jmeter,线上有多个节点,在其中的几个节点
进行优化,有效果后再全面推进
简述Concurrent Mode Failure的错误
由于CMS在执行的过程中,用户线程还需要运行,那就需要足够的内存空间供用户使用,
如果等老年代空间快满了,再开启GC,用户线程可能产生Concurrent Mode Failure的错误,
这时会临时启用Serial Old重新进行老年代GC,造成很长时间的STW
所以在老年代利用率70%的时候,就可以GC了,参数可配置
如何使用jstat命令查看JVM的GC情况
面对海量的GC日志参数,如何快速抓住问题根源
你不得不掌握的日志分析工具
GC 日志输出相关参数
-verbose:gc
打印 GC 日志
PrintGCDetails
打印详细 GC 日志
PrintGCDateStamps
系统时间,更加可读,PrintGCTimeStamps 是 JVM 启动时间
PrintGCApplicationStoppedTime
打印 STW 时间
PrintTenuringDistribution
打印对象年龄分布,对调优 MaxTenuringThreshold 参数帮助很大
loggc
将以上 GC 内容输出到文件中
OOM 时的参数
HeapDumpOnOutOfMemoryError
OOM 时 Dump 信息,非常有用
HeapDumpPath
Dump 文件保存路径
ErrorFile
错误日志存放路径
OmitStackTraceInFastThrow,
这是 JVM 用来缩简日志输出的。
分析以下命令含义
grep -n real gc.log | awk -F"=| " '{ if($8>0.1){ print }}'
就是筛选停顿超过 100ms 的 GC 日志和它的行数(G1)
jstat命令的用法
gc: 显示和GC相关的堆信息;
gcutil: 显示垃圾回收信息;
gccause: 显示垃圾回收 的相关信息(同 -gcutil),同时显示最后一次或当前正在发生的垃圾回收的诱因;
gcnew: 显示 新生代 信息;
gccapacity: 显示各个代的容量以及使用情况;
gcmetacapacity: 显示元空间metaspace 的大小;
gcnewcapacity: 显示新生代大小和使用情况;
gcold: 显示老年代和永久代的信息;
gcoldcapacity: 显示老年代的大小;
printcompilation: 输出JIT编译 的方法信息;
class: 显示类加载ClassLoader的相关信息;
compiler: 显示JIT编译的相关信息;
学习一下jstat的用法
老年代溢出为什么那么可怕
元空间也有溢出,怎么优化
如何配置栈的大小,避免栈溢出
进程突然死掉,没有留下任何信息时如何进行排查
信号9和信号15的区别
在使用 kill -9 前,应该先使用 kill -15,给目标进程一个清理善后工作的机会。
如果没有,可能会留下一些不完整的文件或状态,从而影响服务的再次启动。
隔离就是把你的这台机器从请求列表里摘除,比如把nginx相关的权重设成0