-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
634 lines (493 loc) · 39.6 KB
/
index.html
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
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
<!DOCTYPE html>
<html>
<head><meta name="generator" content="Hexo 3.8.0">
<meta charset="utf-8">
<title>A Simple Blog</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta property="og:type" content="website">
<meta property="og:title" content="A Simple Blog">
<meta property="og:url" content="http://youjielee.com/index.html">
<meta property="og:site_name" content="A Simple Blog">
<meta property="og:locale" content="zh-Hans">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="A Simple Blog">
<link rel="alternate" href="/atom.xml" title="A Simple Blog" type="application/atom+xml">
<link rel="icon" href="/favicon.png">
<link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="/css/style.css">
</head>
</html>
<body>
<div id="container">
<div id="wrap">
<header id="header">
<div id="banner"></div>
<div id="header-outer" class="outer">
<div id="header-title" class="inner">
<h1 id="logo-wrap">
<a href="/" id="logo">A Simple Blog</a>
</h1>
</div>
<div id="header-inner" class="inner">
<nav id="main-nav">
<a id="main-nav-toggle" class="nav-icon"></a>
<a class="main-nav-link" href="/">Home</a>
<a class="main-nav-link" href="/archives">Archives</a>
</nav>
<nav id="sub-nav">
<a id="nav-rss-link" class="nav-icon" href="/atom.xml" title="RSS Feed"></a>
<a id="nav-search-btn" class="nav-icon" title="Search"></a>
</nav>
<div id="search-form-wrap">
<form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit"></button><input type="hidden" name="sitesearch" value="http://youjielee.com"></form>
</div>
</div>
</div>
</header>
<div class="outer">
<section id="main">
<article id="post-《JVM G1源码分析及调优》读书笔记5" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2020/02/10/《JVM G1源码分析及调优》读书笔记5/" class="article-date">
<time datetime="2020-02-09T16:29:30.727Z" itemprop="datePublished">2020-02-10</time>
</a>
</div>
<div class="article-inner">
<div class="article-entry" itemprop="articleBody">
<h1 id="G1混合回收"><a href="#G1混合回收" class="headerlink" title="G1混合回收"></a>G1混合回收</h1><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>因为涉及老年代的回收 而老年代的空间一般都比较大 手机老年代可能会花费更多的时间 所以涉及老年代的混合手机算法也不同于新生代回收算法 最明显的就是引入了<strong>并发标记</strong>(标记工作线程与Mutator同时运行)</p>
<p>混合回收可以总结为两个阶段</p>
<ul>
<li>并发标记 目的是识别老年代分区中的活跃对象 并计算分区中垃圾对象所占的多少 用于垃圾回收过程中判断是否回收分区</li>
<li>垃圾回收 这个过程和新生代回收的步骤完全一致 重用了新生代回收的代码 最大的不同时在回收时不仅仅回收新生代分区 同时回收并发标记中识别到的<strong>垃圾多</strong>的老年代分区</li>
</ul>
<h2 id="并发标记算法详解"><a href="#并发标记算法详解" class="headerlink" title="并发标记算法详解"></a>并发标记算法详解</h2><p>在堆分区分配对象的时候 对象都是联系分配的 没有填充满的TLAB填充一个dummy的Int数组 所以设计几个滋镇分别是<code>Bottom</code> <code>Pre</code> <code>Next</code> 和<code>Top</code> 用Prev指针指向<strong>上一次</strong>并发处理后的地址 用Next指向并发标记开始之前内存已经分配成功的地址 党鞭法标记开始之后 如果有新的对象分配 可以移动Top指针 使Top指针指向当前内存分配成功的地址 Next指针和Top指针之间的地址就是Mutator新增的对象使用的地址 当并发标记结束之后 只需要把<code>Prev</code>指针设置为<code>Next</code>指针即可开始新一轮的标记处理</p>
<p><code>Prev</code> 和<code>Next</code> 指针解决了并发标记<strong>工作内存区域</strong>的问题 还需要再引入两个额外的数据结构来记录内存标记的状态 典型的是使用位图来只是哪块内存已经使用 哪块内存还未使用 所以并发标记引入两个位图<code>PrevBitMap</code>和NextBitMap 用PrevBitmap记录prev指针之前内存的标记状况 用<code>NextBitmap</code>来标识整个内存到<code>next</code>指针之前的标记状态 </p>
<blockquote>
<p>既然NextBitmap包含了整个使用内存的标记状态 为什么要引入PrevBitmap这个数据结构</p>
<p>如果并发标记每次都成功 我们确实可以不需要PrevBitmap 只需要根据这个BitMapjinx 清除即可</p>
<p>但如果标记失败 我们将丢失上一次对Prev指针之前所有内存的标记状况 即当发生失败不能完成并发标记时需要重新标记整个内存 “明显有问题”</p>
</blockquote>
<h2 id="并发标记算法的难点"><a href="#并发标记算法的难点" class="headerlink" title="并发标记算法的难点"></a>并发标记算法的难点</h2><p>并发标记的主要问题是垃圾回收器在标记对象的过程中 Mutator可能正在改变对象关系图 从而造成漏标和错标 错标不会影响程序的正确性 只会造成所谓的<strong>浮动垃圾</strong> 但漏标则会导致科大对象被当做垃圾手机掉 从而影响程序的正确性 为了区别对象的不同状态 引入了三色标记法</p>
<ul>
<li>白色:标识对象尚未被垃圾收集器访问过</li>
<li>黑色:表示对象已经被垃圾收集器访问过 且这个对象的所有引用都已经被扫描过</li>
<li>灰色:标识对象已经被垃圾收集器访问过 但这个对象上至少存在一个引用还没有被扫描过</li>
<li>对象消失问题:原本应该是黑色的对象被<strong>误标</strong>为白色 当且仅当同时满足两个条件 只需要破坏其一即可<ul>
<li>赋值器插入了一条或多条从黑色对象到白色对象的新引用<ul>
<li>CMS的增量更新方案:黑色对象一旦新插入了只想白色对象的引用之后 就变成灰色</li>
</ul>
</li>
<li>赋值器删除了全部灰色对象到该白色对象的直接或者间接引用<ul>
<li>G1的原始快照SATB方案:无论运用关系删除与否 都会按照刚刚开始扫描那一刻的对象图快照进行搜索</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="G1混合回收的步骤"><a href="#G1混合回收的步骤" class="headerlink" title="G1混合回收的步骤"></a>G1混合回收的步骤</h2><ul>
<li>第一阶段:并发标记<ul>
<li>初始标记子阶段<ul>
<li>负责标记所有直接科大的跟对象(栈对象、全局对象、JNI对象等) 根式对象图的起点 引起初始标记需要将Mutator线程暂停 即STW 混合手机中的初始标记与新生代的初始标记几乎一样 实际上混合手机的初始标记时借用了新生代手机的结果 即新生代垃圾回收后的新生代Survivor分区作为根 所以混合手机一定发生在新生代回收之后 且不需要再进行一次初始标记</li>
</ul>
</li>
<li>并发标记子阶段</li>
<li>再标记子阶段</li>
<li>清理子阶段</li>
</ul>
</li>
<li>第二阶段:垃圾回收</li>
</ul>
</div>
<footer class="article-footer">
<a data-url="http://youjielee.com/2020/02/10/《JVM G1源码分析及调优》读书笔记5/" data-id="ck6f91t3f0006rsreaqw9huwo" class="article-share-link">Share</a>
</footer>
</div>
</article>
<article id="post-《JVM G1源码分析及调优》读书笔记4" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2020/02/10/《JVM G1源码分析及调优》读书笔记4/" class="article-date">
<time datetime="2020-02-09T16:29:30.725Z" itemprop="datePublished">2020-02-10</time>
</a>
</div>
<div class="article-inner">
<div class="article-entry" itemprop="articleBody">
<h1 id="G1新生代回收"><a href="#G1新生代回收" class="headerlink" title="G1新生代回收"></a>G1新生代回收</h1><p>当内存分配的时候 剩余的空间不能满足要分配的对象时就会优先触发<strong>新生代回收</strong>(YGC) G1的YGC是针对部分内存进行的垃圾回收 所以YGC话费的时间通常都比较小 G1每次YGC回收的分区数目是不固定的 但每一次YGC都是手机所有的新生代分区 所以每一次YGC之后都会调整新生代分区的数目(根据前文提到的预测停顿模型)</p>
<h2 id="YGC算法概述"><a href="#YGC算法概述" class="headerlink" title="YGC算法概述"></a>YGC算法概述</h2><p>YGC主要分成两部分:并行部分和其他部分 YGC的执行顺序</p>
<ol>
<li><p>进行收集之前需要STW <strong>G1 YGC是全程STW</strong> 所以 可以不考虑并发情况</p>
</li>
<li><p>选择要手机的CSet(对于YGC来说整个新生代分区都是CSet)</p>
</li>
<li><p>进入并行任务处理</p>
<ol>
<li><p>根扫描并处理:处理过程会把跟直接引用的对象复制到新的Survivor区 然后把引用对象的field、入栈等待后续的扫描复制处理</p>
<ul>
<li>JAVA根:主要是类加载器和线程栈<ul>
<li>类加载器主要是遍历这个类加载器中所有存活的Klass并复制到Survivor或者晋升到老年代</li>
<li>线程栈机会处理普通线程栈分配的局部变量 也会处理本地方法栈访问的堆对象</li>
</ul>
</li>
<li>JVM根:通常是全局对象 比如 <code>Universe</code> <code>JNIHandles</code> <code>ObjectSynchronize</code> <code>FlatProfiler</code> <code>Management</code> <code>JvmtiExport</code> <code>SystemDictionary</code> <code>StringTable</code></li>
</ul>
</li>
<li><p>处理老年代分区到新生代分区的引用:首先会更新所有的代际引用 即更新RSer 然后从RSet触发 把RSet所在卡表对应的分区内存块的所有对象都认为是跟 吧这些跟引用的对象复制到新的Survivor区 然后把被引用对象的field入栈等待后续的复制处理</p>
<blockquote>
<p>当发现对象需要被复制 先复制对象到先的位置 复制之后把老对象(对象老位置的引用)的对象投标记位11 然后把对象投里边的指针指向新的对象(对象新位置的引用) 这样当一个对象呗多个对象引用时 只有第一次遍历对象时候才需要被复制 后续都不需要复制了 直接通过这个指针就能找到新的对象 后边的重复引用直接修改自己的指针指向新的对象就完成了遍历 参见笔记1 对象头小节介绍</p>
</blockquote>
</li>
<li><p>JIT代码扫描 </p>
<blockquote>
<p><code>[Code Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.6]</code></p>
<p>扫描code root耗时。code root指的是经过JIT编译后的代码里,引用了heap中的对象。引用关系保存在RSet中</p>
</blockquote>
</li>
<li><p>根据栈中的对象 进行深度递归遍历复制对象</p>
<blockquote>
<p>复制时 有个与TLAB类似的数据结构PLAB(Promotion local allocate buffer) 用于复制 先使用PLAB方法直接在PLAB中分配新的对象 如果分配失败 则尝试分配一个PLAB或者直接在对重分配对象 这里和TLAB类似</p>
</blockquote>
</li>
</ol>
</li>
<li><p>其他任务处理 大部分是串行执行</p>
<ol>
<li>JIT代码位置更新 在并行任务中已经对代码进行了扫描和复制 这里会更新相关指针所指向的位置</li>
<li>引用处理 几把引用中使用的存活对象也复制到新的分区 否则会造成错误</li>
<li>字符串去重优化回收 JDK8 G1新引入功能 优化字符串使用效率</li>
<li>清除卡表 吧全局卡表中已经处理过的分区对应的卡表清空</li>
<li>JIT代码回收</li>
<li>Evac(复制转移)失败 则进行处理 主要工作是恢复对象头</li>
<li>引用(Reference)再处理: 把引用中还活着的对象放入引用队列中</li>
<li>进行Redirty 主要工作是重构RSet:因对象移动需要更新老年代到新生代的引用 <strong>通常并发执行</strong></li>
<li>释放CSet:启动释放内存 即把<strong>这些分区放入自由列表</strong> 供后续使用</li>
<li>尝试大对象回收:只要判断这些大对象所在的分区是否有RSet引用 且只需要判断大对象所在第一个分区 果果没有引用则说明整个大对象肯定死亡 有引用则说明大对象可能还活着 在并发标记中进一步处理</li>
<li>尝试扩展内存 根据<code>GCTimeRatio</code>和<code>G1ExpandByPercentOfAvailable</code>来判断是否可以扩展 如果可以则扩展</li>
<li>调整新生代分区的数据 调整<code>RefinementZone</code>阈值等</li>
<li>如果可能 启动并发标记:使用的内存超过一定阈值则可以启动 具体在<strong>混合回收</strong>中介绍</li>
</ol>
</li>
</ol>
<h2 id="YGC代码分析"><a href="#YGC代码分析" class="headerlink" title="YGC代码分析"></a>YGC代码分析</h2><h3 id="并行任务"><a href="#并行任务" class="headerlink" title="并行任务"></a>并行任务</h3><p>并行任务处理主要通过工作线程<code>FlexibleWorkGang</code>来执行任务<code>G1ParTask</code> 这个任务主要分为</p>
<ul>
<li><p>根扫描并处理 针对所有的根 对可达对象做</p>
<ul>
<li>如果对象还没有设置过标记信息 把对象从Eden复制到Survivor 然后针对对象的没一个field 如果引用的分区在CSet 则把对象的地址加入到 <code>G1PaeScanThreadState</code>(PSS)中待扫描处理 如果字段不在CSet 则更新该对象所在堆分区对应的RSet</li>
<li>更新跟对象到对象新的地址</li>
</ul>
</li>
<li><p>处理老年代分区到新生代分区的引用</p>
<ul>
<li>处理Dirty card 更新Rset 更新老年代分区到新生代分区的引用</li>
<li>扫描RSet 吧引用者作为根 从根触发 对科大对象进行根扫描并处理</li>
<li>复制 在PSS中队列的对象都是活跃对象 没一个对象都要复制到Survivor区 然后针对该对象的没一个字段如果引用的分区在CSet 则把对象的地址加入到 <code>G1PaeScanThreadState</code>(PSS)中待扫描处理 循环知道队列中没有对象</li>
</ul>
</li>
<li><p>如何做并行处理</p>
<ul>
<li><p>对于Java根处理来说 根对象有多个 所以分配一个对象来存储各个并行任务的状态 在使用的时候多个线程通过CAS获取数组中的原色来保证并行执行任务</p>
</li>
<li><p>对于RSer根来说 处理的时候是根据分区来并行处理 及老年代对象引用了多个CSet中不同的分区 也没问题 因为此时仅仅标记对象 及时一个引用对象被处理多次 也只是标记出来 所以没有问题</p>
</li>
<li><p>在Evac转移复制中 因为每次处理对象的时候 需要对对象进行复制 这个时候是需要多个线程使用CAS来保证串行 先把对象标记位待回收 之后才能复制 即只有一个线程复制成功 其他线程都会重用这个新复制的对象</p>
<blockquote>
<p>Evac因为涉及对象复制 非常耗时 所以这个阶段还提供 <strong>任务窃取</strong>的功能 在并发执行的过程中GC线程有限处理本地的队列 当本地的队列没有任务的时候 窃取别的队列的任务 帮助别的队列</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</div>
<footer class="article-footer">
<a data-url="http://youjielee.com/2020/02/10/《JVM G1源码分析及调优》读书笔记4/" data-id="ck6f91t3d0005rsrexf5obso0" class="article-share-link">Share</a>
</footer>
</div>
</article>
<article id="post-《JVM G1源码分析及调优》读书笔记3" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2020/02/10/《JVM G1源码分析及调优》读书笔记3/" class="article-date">
<time datetime="2020-02-09T16:29:30.723Z" itemprop="datePublished">2020-02-10</time>
</a>
</div>
<div class="article-inner">
<div class="article-entry" itemprop="articleBody">
<h1 id="G1的Refine线程"><a href="#G1的Refine线程" class="headerlink" title="G1的Refine线程"></a>G1的Refine线程</h1><p>名词释义:</p>
<ul>
<li>Refine线程:ConcurrentG1RefineThread主要指处理RSet的线程</li>
</ul>
<h2 id="记忆集与卡表"><a href="#记忆集与卡表" class="headerlink" title="记忆集与卡表"></a>记忆集与卡表</h2><ul>
<li>Rset和卡表是为了记录对象在不同代际之间的引用关系 目的是在垃圾回收时能够快速地找到活跃对象 而不必遍历整个堆空间</li>
<li>两种方法记录引用关系 Point Out & Point In<ul>
<li>Point out记录操作简单 但需要对Rset做全部扫描</li>
<li>Point in记录操作复杂 但在标记扫描时能直接找到有用和无用的对象 不需要额外扫描 因为RSet里边的对象可以认为就是根对象<ul>
<li>G1收集器里边 使用了Point In的方法 算法可以简化为找到需要收集的分区集合 所以YGC扫描Root Set 和Rset就可以了 </li>
</ul>
</li>
</ul>
</li>
<li>简单的方式是在Rset里记录所有的引用关系 但并不是最优的设计方案 故需要优化<ul>
<li>需要额外的内存空间:通常是JVM最大的额外开销 一般在1%~20%之间</li>
<li>可能导致浮动垃圾:由于根据RSet回收 而RSet里边的对象可能已经死亡 被误认为是活跃对象</li>
</ul>
</li>
<li>分析哪些引用关系需要记录在RSet中<ul>
<li>分区内部有引用:<strong>不需要</strong> 回事是针对一个分区而言 要么都回收 要么都不回收</li>
<li>新生代分区至新生代分区有引用:<strong>不需要</strong> G1的三种回收算法都会全量处理新生代分区</li>
<li>新生代分区到老年代分区有引用:<strong>不需要</strong> YGC针对新生代分区 不需要 Mix会以新生代分区作为根 Full对全分区处理 也不需要</li>
<li>老年代分区到新生代分区有引用:<strong>需要</strong> YGC是有两种根 一种是栈空间/全局空间变量的引用 第二种即为老年代分区到新生代分区的引用</li>
<li>老年代分区到老生代分区有引用:<strong>需要</strong> 混合GC时只有部分老年代分区被回收所以必须记录引用关系 快速找到哪些对象时活跃的</li>
</ul>
</li>
<li>PRT:记录每个分区所有引用者的信息 的数据结构<code>HeapRegion.HeapRegionRemSet.OtherRegionsTable</code> <code>PerRegionTable</code> 每个HeapRegion都包含有一个PRT 有三种不同的粒度实现 引用的次数可能很多也可能很少 为了提高效率 采用了动态化的数据结构存储<ul>
<li>稀疏PRT: 通过哈希表方式 默认长度是4 </li>
<li>细粒度PRT: 通过PRT指针的指针 可以理解为是PRT指针的数组 数组长度可指定也自动计算 PTR=HeapRegion的起始位置加上一个位图(描述分区的引用关系 大小为HeapRegionSize/512)</li>
<li>粗粒度PRT:通过位图标识 每一位标识对应的分区有引用到该分区数据结构</li>
<li>以上三类前者最大值被突破之后 开启下一等级粒度处理PRT</li>
</ul>
</li>
</ul>
<h2 id="Refine线程的功能和原理"><a href="#Refine线程的功能和原理" class="headerlink" title="Refine线程的功能和原理"></a>Refine线程的功能和原理</h2><ul>
<li>是G1新引入的并发线程池 默认数目为 G1ConcRefinementThread+1 有以下两大功能</li>
<li>处理新生代分区的抽样并在满足响应时间的指标下更新YHR的数目 通常由一个线程来处理</li>
<li>管理RSet 这是Refine最主要的功能 RSet的更新并不是同步完成的<ul>
<li>G1会把所有的引用关系都先放入到一个队列中成为DCQ(dirty card queue) 然后使用线程来消费这个队列已完成更新 正常来说有G1ConcRefinementThread个线程处理(实际除了Refine线程之外 GC线程或者Mutator线程也可能会更新RSet —并发编程模式中的线程窃取) DCQ通过DCQS管理 每个Refine线程并发地负责DCQS中的某几个DCQ </li>
</ul>
</li>
</ul>
<h3 id="抽样线程"><a href="#抽样线程" class="headerlink" title="抽样线程"></a>抽样线程</h3><p>Refine线程池的最后一个线程就是抽样线程 他的主要作用是设置新生代的个数 使G1满足垃圾回收的预测停顿时间 在预测时 需要同时考虑最小分区的下限和上限</p>
<h3 id="管理RSet"><a href="#管理RSet" class="headerlink" title="管理RSet"></a>管理RSet</h3><p>G1中使用Refine线程<strong>异步</strong>地维护和管理引用关系</p>
<p>每个Mutator线程有个私有的队列(G1UpdateBufferSize 默认256)DCQ 当队列满(也可手工提交)则将这个队列通过JavaThread中的静态方法找到全局DCQS放到<strong>全局的DCQS</strong>中 后续此线程的对象应用关系直接放入到DCQ中</p>
<p>当DCQS中DCQ的个数大于一个阈值(GreenZone)通过全局Monitor发送Notify通知0号Refine线程启动 </p>
<p>如果DCQS满了 Mutator就不继续往DCQS中添加(说明引用太多了 Refine线程负责太重) 这个Mutator就会暂停其他代码执行 替代Refine线程来更新RSet 这个就产生了业务暂停</p>
<p>DCQS队列长度依赖于Refine线程的个数 最大为RedZone的个数</p>
<p>Refine线程的初始化是在GC管理器初始化的时候进行 但如果没有足够多的引用关系变更 这些Refine线程都是空转 所以需要有一个机制能动态激活和冻结线程 JVM通过wait和notify机制实现:0~n-1线程 前一个线程忙 激活下一个 后一个线程自己空闲主动冻结自己 如前所述<strong>0号线程由Mutator激活</strong> 故0号等待的Monitor也是全局变量 其他线程中的Monitor为局部变量</p>
<h2 id="RefineZone"><a href="#RefineZone" class="headerlink" title="RefineZone"></a>RefineZone</h2><p>基于RSet空间及Mutator暂停业务线程考虑 我们可以设置多个Refine线程工作 在不同的时间负载下启用的线程不同 通过Refinement Zone控制 G1提供三个值 分别是Green Yellow和Red 将整个Queue set划分为4个区 白 绿 黄 和红</p>
<ul>
<li>白区:Refine不处理 交由GC线程处理DCQ</li>
<li>绿区:Refine线程开始启动 并且根据queue set数值的大小启动不同数量的Refine线程来处理DCQ</li>
<li>黄区:所有的Refine线程(除了抽样线程)都参与DCQ处理</li>
<li><p>红区:不仅仅Refine线程参与处理RSet Mutator也参与处理DCQ</p>
</li>
<li><p>参数设置规则:G1ConcRefinementGreenZone G1ConcRefinementYellowZone G1ConcRefinementRedZone 默认为0 自动推导 推导规则为</p>
</li>
<li><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">G1ConcRefinementGreenZone=ParallelGCThreads </span><br><span class="line">G1ConcRefinementYellowZone=3 * G1ConcRefinementGreenZone </span><br><span class="line">G1ConcRefinementRedZone=6 * G1ConcRefinementGreenZone</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h2 id="RSet涉及的写屏障"><a href="#RSet涉及的写屏障" class="headerlink" title="RSet涉及的写屏障"></a>RSet涉及的写屏障</h2><p>Refine引用关系的变更即对象的赋值对反馈在RSet中 需要写屏障:在改变特定内存的值是额外执行的一些动作 如每一次讲一个新生代对象的引用修改为只想新生代对象 都会被写屏障捕获 并且记录下来 因此在新生代回收的时候可以避免扫描整个老年代来查找根</p>
<p>G1垃圾回收器的RSet在写变更的时候通过插入一段额外的代码把引用关系放入到DCQ中 随后Refine线程更新RSet 记录堆分区内部中对象的指针 目前是操作之后 而对于写屏障来说 过来掉不必要的操作是十分必要的 技能几块复制器的速度也能减轻回收器的负担 G1有三重过滤</p>
<ul>
<li><p>不记录新生代到新生代或老年代的引用 没有必要(详细讨论参见G1笔记文章2 )在写屏障时过滤</p>
</li>
<li><p>过滤同一个分区内部引用 在RSet处理时过滤</p>
</li>
<li>过滤掉空引用 在RSet处理时过滤</li>
</ul>
<blockquote>
<p>由于G1对写屏障的复杂操作要比CMS消耗更多的运算资源 所以CMS的写屏障是直接的同步操作 而G1就不得不将其实现为类似于消息队列的结构 异步完成</p>
</blockquote>
<p>G1垃圾回收器的写屏障使用一种两级的缓存结构(用queue set实现)</p>
<ul>
<li>线程queue set:所有的线程都会把写屏障的记录写写入到线程自由的queue set中装满之后 就会把queue set放到 后述的global set of filled queue中 而后再申请一个queue set</li>
<li>的global set of filled queue:所有线程共享的一个全局的 存放填满了的DCQS的集合</li>
</ul>
</div>
<footer class="article-footer">
<a data-url="http://youjielee.com/2020/02/10/《JVM G1源码分析及调优》读书笔记3/" data-id="ck6f91t370004rsre3zb4abv4" class="article-share-link">Share</a>
</footer>
</div>
</article>
<article id="post-《JVM G1源码分析及调优》读书笔记" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2020/02/10/《JVM G1源码分析及调优》读书笔记/" class="article-date">
<time datetime="2020-02-09T16:29:30.642Z" itemprop="datePublished">2020-02-10</time>
</a>
</div>
<div class="article-inner">
<div class="article-entry" itemprop="articleBody">
<h1 id="《JVM-G1源码分析及调优》读书笔记1"><a href="#《JVM-G1源码分析及调优》读书笔记1" class="headerlink" title="《JVM G1源码分析及调优》读书笔记1"></a>《JVM G1源码分析及调优》读书笔记1</h1><h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><p>JVM基础回顾 详见《垃圾回收及内存分配》</p>
<p>垃圾回收算法分为:标记清除 复制 标记压缩</p>
<p>垃圾回收方法分为:串行回收、并行回收、并发回收</p>
<p>内存管理上分为:代管理和非代管理</p>
<h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>G1与历史回收器差别在与 后者针对堆空间的管理方式都是连续的 连续的内存将导致垃圾回收时间过长 停顿时间不可控,因此G1将堆拆分成一系列的分区,在一个时间段内,大部分的垃圾收集操作只针对一部分分区,而不是整个堆或者老年代</p>
<ul>
<li>G1 会根据预测时间动态改变新生代的大小 –G1以预测时间为导向</li>
<li>G1老年代的收集不会为了释放老年代的空间对整个老年代做回收,在任意时刻只有一部分老年代分区会被回收,并且 这部分老年代分区将在<strong>下一次</strong>增量回收时与<strong>所有的新生代</strong>一起被收集 即混合回收 在选择老年代分区的时候 优先考虑垃圾多的分区</li>
</ul>
<p>实现角度看 G1是复合算法 吸收了以下算法的优势</p>
<ul>
<li>列车算法 对内存进行分区</li>
<li>CMS 对分区进行并发标记</li>
<li>最老优先 最老的数据优先收集</li>
</ul>
<h2 id="G1的基本概念"><a href="#G1的基本概念" class="headerlink" title="G1的基本概念"></a>G1的基本概念</h2><h3 id="分区"><a href="#分区" class="headerlink" title="分区"></a>分区</h3><p>大概分为4类</p>
<ul>
<li>自由分区</li>
<li>新生代分区</li>
<li>大对象分区<ul>
<li>大对象头分区</li>
<li>大对象连续分区</li>
</ul>
</li>
<li>老年代分区</li>
</ul>
<p>HR大小的下限1M 上限32M (1、2、4、8、16、32M) 默认堆分区2048个 HR大小支持启发式推断</p>
<p>G1大对象不进入新生代空间 直接进入老年代 大小为region_size的一半</p>
<p>设置新生代大小:G1NewSizePercent~G1MaxNewSizePercent</p>
<ul>
<li>新生代不会动态变化意味着G1后续对新生代垃圾回收的时候可能不能满足期望停顿的时间</li>
</ul>
<h3 id="G1停顿预测模型"><a href="#G1停顿预测模型" class="headerlink" title="G1停顿预测模型"></a>G1停顿预测模型</h3><p>衰减平均数学方法 核心是近期的数据给予更高的权重</p>
<h3 id="卡表与位图"><a href="#卡表与位图" class="headerlink" title="卡表与位图"></a>卡表与位图</h3><p>记录分区Region互相引用关系</p>
<p>做法两种:</p>
<p>1、遍历整个A区 按字移动(因字节对齐故不需要按字节移动)或优化为按对象移动(参照上一章OopMap)效率太低</p>
<p>2、借助额外的数据结构描述这个关系 类似位图的卡表</p>
<p>卡表:引入目的是为了对内存的引用关系做标记 从而根据引用关系快速便利活跃对象 </p>
<p> 优化:1个位只能描述两个状态 不够 增加至一个字节描述状态一个字 但开销达到25% JVM选择512字节</p>
<p>1个位描述一个字:32位机器(一个字位32位)需要<strong>32KB</strong>描述1M(32KB*32=1M) </p>
<p>1字节描述一个位:32位机器(一个字位32位)需要<strong>256KB</strong>描述1M((256KB/8)*32=1M) </p>
<p>1字节描述512字节:32位机器(一个字位32位)需要<strong>2KB</strong>描述1M((256KB/8)*32=1M) <strong>涉及多次引用 使用时候再遍历</strong> </p>
<h3 id="对象头"><a href="#对象头" class="headerlink" title="对象头"></a>对象头</h3><p>对象数据结构=对象头+实例数据+对齐填充</p>
<p>对象头=标记信息+元数据</p>
<h4 id="标记信息"><a href="#标记信息" class="headerlink" title="标记信息"></a>标记信息</h4><p>在32位JVM中用32位进行描述 组合情况如下 与GC有关的是标记位11 对应前边30位用在对象复制转移</p>
<p><img src="https://bbs-img.huaweicloud.com/blogs/img/1576818812657009.png" alt></p>
<h4 id="元数据"><a href="#元数据" class="headerlink" title="元数据"></a>元数据</h4><p>对应全局OopMap 每个InstanceKlass对象中也维护了一个Map(OopMapBlock)标识引用或立即数 用于确认</p>
<h3 id="线程"><a href="#线程" class="headerlink" title="线程"></a>线程</h3><p><img src="https://bbs-img.huaweicloud.com/blogs/img/1576819408276194.png" alt></p>
<ul>
<li><p>JavaThread :执行Java代码的线程</p>
<ul>
<li>CompilerThread :执行JIT的线程</li>
</ul>
</li>
<li><p>WatcherThread :执行周期任务的线程 比如内存管理小对象ChunkPool的清理和内存 抽样任务 </p>
<p>MemProfilerTask等</p>
</li>
<li><p>NameThread:JVM内部使用线程</p>
<ul>
<li>VMThread:JVM执行GC的同步线程 JVM最关键的线程之一 主要是处理垃圾回收 所有的垃圾回收操作都是从VMThread触发</li>
</ul>
</li>
<li>ConcurrentGCThread:并发执行GC任务的线程 比如G1中的ConcurrentMarkThread和COncurrentG1RefineThread 分别处理并发标记和并发Refine</li>
<li>WorkerThread:工作线程,并发执行 可以理解为是一个线程池 里边线程执行GC任务(G1ParTask),VMThread会触发这些任务的调度执行</li>
</ul>
<blockquote>
<p>1、G1中不要设置MaxNewSize、NewSize、Xmn和NewRatio G1对内存的管理不是连续的 故重新分区代价不高 最终要的 是G1的目标满足垃圾收集停顿 这需要G1根据停顿时间动态调整收集了分区</p>
<p>2、MaxGCPauseMillis:期望停顿时间 可根据系统配置和业务动态调整</p>
<p>3、G1ReservePercent:新生代保留内存(默认10个)即在初始化或者内存扩展/收缩是计算更新有多少分区是保留的</p>
</blockquote>
</div>
<footer class="article-footer">
<a data-url="http://youjielee.com/2020/02/10/《JVM G1源码分析及调优》读书笔记/" data-id="ck6f91t3i0008rsrehao4ik19" class="article-share-link">Share</a>
</footer>
</div>
</article>
<article id="post-Hello-Hexo" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2019/04/21/Hello-Hexo/" class="article-date">
<time datetime="2019-04-21T02:02:53.000Z" itemprop="datePublished">2019-04-21</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2019/04/21/Hello-Hexo/">Hello Hexo</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
</div>
<footer class="article-footer">
<a data-url="http://youjielee.com/2019/04/21/Hello-Hexo/" data-id="ck6f91t1i0000rsres1llmxaj" class="article-share-link">Share</a>
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/github/">github</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/hexo/">hexo</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/npm/">npm</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/基础/">基础</a></li></ul>
</footer>
</div>
</article>
<article id="post-hello-world" class="article article-type-post" itemscope itemprop="blogPost">
<div class="article-meta">
<a href="/2019/04/21/hello-world/" class="article-date">
<time datetime="2019-04-20T17:09:35.697Z" itemprop="datePublished">2019-04-21</time>
</a>
</div>
<div class="article-inner">
<header class="article-header">
<h1 itemprop="name">
<a class="article-title" href="/2019/04/21/hello-world/">Hello World</a>
</h1>
</header>
<div class="article-entry" itemprop="articleBody">
<p>Welcome to <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/" target="_blank" rel="noopener">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html" target="_blank" rel="noopener">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues" target="_blank" rel="noopener">GitHub</a>.</p>
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/writing.html" target="_blank" rel="noopener">Writing</a></p>
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/server.html" target="_blank" rel="noopener">Server</a></p>
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/generating.html" target="_blank" rel="noopener">Generating</a></p>
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/deployment.html" target="_blank" rel="noopener">Deployment</a></p>
</div>
<footer class="article-footer">
<a data-url="http://youjielee.com/2019/04/21/hello-world/" data-id="ck6f91t2w0002rsrebnge1rbr" class="article-share-link">Share</a>
<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/github/">github</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/hexo/">hexo</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/npm/">npm</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/基础/">基础</a></li></ul>
</footer>
</div>
</article>
</section>
<aside id="sidebar">
<div class="widget-wrap">
<h3 class="widget-title">Tags</h3>
<div class="widget">
<ul class="tag-list"><li class="tag-list-item"><a class="tag-list-link" href="/tags/github/">github</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/hexo/">hexo</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/npm/">npm</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/基础/">基础</a></li></ul>
</div>
</div>
<div class="widget-wrap">
<h3 class="widget-title">Tag Cloud</h3>
<div class="widget tagcloud">
<a href="/tags/github/" style="font-size: 10px;">github</a> <a href="/tags/hexo/" style="font-size: 10px;">hexo</a> <a href="/tags/npm/" style="font-size: 10px;">npm</a> <a href="/tags/基础/" style="font-size: 10px;">基础</a>
</div>
</div>
<div class="widget-wrap">
<h3 class="widget-title">Archives</h3>
<div class="widget">
<ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2020/02/">February 2020</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2019/04/">April 2019</a></li></ul>
</div>
</div>
<div class="widget-wrap">
<h3 class="widget-title">Recent Posts</h3>
<div class="widget">
<ul>
<li>
<a href="/2020/02/10/《JVM G1源码分析及调优》读书笔记5/">(no title)</a>
</li>
<li>
<a href="/2020/02/10/《JVM G1源码分析及调优》读书笔记4/">(no title)</a>
</li>
<li>
<a href="/2020/02/10/《JVM G1源码分析及调优》读书笔记3/">(no title)</a>
</li>
<li>
<a href="/2020/02/10/《JVM G1源码分析及调优》读书笔记/">(no title)</a>
</li>
<li>
<a href="/2019/04/21/Hello-Hexo/">Hello Hexo</a>
</li>
</ul>
</div>
</div>
</aside>
</div>
<footer id="footer">
<div class="outer">
<div id="footer-info" class="inner">
© 2020 LYJ<br>
Powered by <a href="http://hexo.io/" target="_blank">Hexo</a>
</div>
</div>
</footer>
</div>
<nav id="mobile-nav">
<a href="/" class="mobile-nav-link">Home</a>
<a href="/archives" class="mobile-nav-link">Archives</a>
</nav>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<link rel="stylesheet" href="/fancybox/jquery.fancybox.css">
<script src="/fancybox/jquery.fancybox.pack.js"></script>
<script src="/js/script.js"></script>
</div>
</body>
</html>