-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
1581 lines (1538 loc) · 181 KB
/
Copy pathsearch.xml
File metadata and controls
1581 lines (1538 loc) · 181 KB
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
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>JDK21适用指南</title>
<url>/2024/08/01/2024-08-01-jdk21-upgrade-guide/</url>
<content><![CDATA[<p>当前升级JDK的动力实际上主要来自三项:ZGC、虚拟线程、衍生生态</p>
<h2 id="1-总体分析"><a href="#1-总体分析" class="headerlink" title="1.总体分析"></a>1.总体分析</h2><p>一下对JDK高版本主要特性做一个总览分析</p>
<span id="more"></span>
<h3 id="ZGC"><a href="#ZGC" class="headerlink" title="ZGC"></a>ZGC</h3><p>优点:ZGC基本消除了GC带来的延迟,并且对大对象有更好的处理</p>
<p>不足:ZGC吞吐量比不上G1,即便分代ZGC吞吐量有了很大提升,但与G1依然有一定差距。</p>
<h3 id="虚拟线程"><a href="#虚拟线程" class="headerlink" title="虚拟线程"></a>虚拟线程</h3><p>优点:适用IO密集型应用,一方面可以提高系统性能,另一方面可以极大消除线程带来的内存压力</p>
<p>不足:在目前JDK21的版本中,虚拟线程存在的坑还比较多,个人不建议在可靠性要求较高的应用上使用</p>
<h3 id="衍生生态"><a href="#衍生生态" class="headerlink" title="衍生生态"></a>衍生生态</h3><p>许多Java生态的框架都基于高版本jdk开发,并不再维护低JDK版本。如果想要探索基于高版本的框架,那么升级JDK也是势在必行的了。</p>
<h2 id="2-什么应用适合升级JDK?"><a href="#2-什么应用适合升级JDK?" class="headerlink" title="2.什么应用适合升级JDK?"></a>2.什么应用适合升级JDK?</h2><table><colgroup><col width="183"><col width="183"><col width="183"><col width="183"></colgroup>
<thead>
<tr>
<th>目标</th>
<th>适用应用</th>
<th>具体版本</th>
<th>风险</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>ZGC</strong></td>
<td><ol>
<li><strong>GC延迟敏感 </strong>的应用,例如缓存应用等</li>
<li>本地有 <strong>大缓存 </strong>, <strong>频繁生成大对象 </strong>的应用</li>
</ol></td>
<td>如果对吞吐量追求不高,可以考虑JDK17以上;<br>如果对吞吐量有较高要求,需要升级JDK21,并且开启分代特性。</td>
<td>分代ZGC并没有完全成熟,经过充分的验证后才可上线</td>
</tr>
<tr>
<td><strong>虚拟线程</strong></td>
<td><strong>IO密集型应用 </strong>都可以升级(文件IO除外)</td>
<td>JDK21</td>
</tr>
<tr>
<td><strong>衍生生态</strong></td>
<td>需要按照自己实际的调研进行升级考虑,例如S pring Modulith等</td>
<td>——</td>
<td>——</td>
</tr>
</tbody>
</table>
<h2 id="3-实际收益"><a href="#3-实际收益" class="headerlink" title="3. 实际收益"></a>3. 实际收益</h2><h3 id="分代ZGC"><a href="#分代ZGC" class="headerlink" title="分代ZGC"></a>分代ZGC</h3><p>我们对一个缓存型服务升级了JDK21,并使用了分代ZGC,在没有明显观察到资源占用提高的前提下解决了GC带来的长尾问题。</p>
<p>我们在3.28下午发布了一个集群,这是这个集群发布前后的长尾响应耗时情况</p>
<p>这是与JDK11 G1集群的对比,黄线是JDK11+G1,绿线是JDK21+ZGC</p>
<p>另外也可以参考Netflix这篇很好的文章 <a href="https://netflixtechblog.com/bending-pause-times-to-your-will-with-generational-zgc-256629c9386b">https://netflixtechblog.com/bending-pause-times-to-your-will-with-generational-zgc-256629c9386b</a></p>
<h3 id="虚拟线程-1"><a href="#虚拟线程-1" class="headerlink" title="虚拟线程"></a>虚拟线程</h3><p>我们在一个 IO 密集型服务进行了全面jdk21升级,并进行了虚拟线程灰度验证。</p>
<p>下图验证时对比非虚拟线程,平台线程数从2000降低到300+(7.19-7.26期间是由于某个依赖组件不支持虚拟线程,某个依赖组件里面开了300个平台线程,7.26后某个依赖组件替换为虚拟线程池)</p>
<p>对于虚拟线程,我们单独开了一个集群用于灰度(下图vt 集群)</p>
<p>下图中黄色线是JDK21集群,绿色为JDK21+虚拟线程集群</p>
<table>
<thead>
<tr>
<th></th>
<th>非虚拟线程(jdk21) p95</th>
<th>虚拟线程(jdk21) p95</th>
<th>降幅</th>
</tr>
</thead>
<tbody><tr>
<td>列表查询</td>
<td>284</td>
<td>239</td>
<td>19%</td>
</tr>
<tr>
<td>详情查询</td>
<td>589</td>
<td>493</td>
<td>16.3%</td>
</tr>
</tbody></table>
<p>加上升级jdk21本身带来的优化,对比jdk11的应用,虚拟线程+jdk21带来的p95降幅大概在20%</p>
<p>TODO:</p>
<p>使用虚拟线程后,不再需要像传统线程池一样进行参数调整,例如线程数,队列大小等等。</p>
<p>以前使用的线程池似乎会在某些场景下存在任务积压的问题,导致耗时大面积上升,切换后我们会进行压测,预期能将单机流量提升3倍以上。</p>
]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>JDK21</tag>
<tag>JVM</tag>
<tag>ZGC</tag>
<tag>Virtual Threads</tag>
</tags>
</entry>
<entry>
<title>JDK21升级记录</title>
<url>/2024/08/18/2024-08-18-jdk21-upgrade-notes/</url>
<content><![CDATA[<h1 id="技术分享PPT,可以参考上面来做GC选型,特性选型,升级实践等"><a href="#技术分享PPT,可以参考上面来做GC选型,特性选型,升级实践等" class="headerlink" title="技术分享PPT,可以参考上面来做GC选型,特性选型,升级实践等"></a>技术分享PPT,可以参考上面来做GC选型,特性选型,升级实践等</h1><h1 id="1-准备工作"><a href="#1-准备工作" class="headerlink" title="1.准备工作"></a>1.准备工作</h1><h2 id="idea-升级"><a href="#idea-升级" class="headerlink" title="idea 升级"></a>idea 升级</h2><h2 id="Maven"><a href="#Maven" class="headerlink" title="Maven"></a>Maven</h2><p>maven下图的这个限制是在maven3.5.3加的,在idea 2023.6以后会影响依赖正常import</p>
<p><img src="21.JDK21%E5%8D%87%E7%BA%A7-image2024-8-14_16-24-37.png"></p>
<span id="more"></span>
<ul>
<li><p>springboot2:3.16.0</p>
</li>
<li><p>springboot3:3.10.0-Java21</p>
</li>
</ul>
<h1 id="2-新特性一览"><a href="#2-新特性一览" class="headerlink" title="2.新特性一览"></a>2.新特性一览</h1><h3 id="注意,预览功能非必要不建议使用!"><a href="#注意,预览功能非必要不建议使用!" class="headerlink" title="注意,预览功能非必要不建议使用!"></a>注意,预览功能非必要不建议使用!</h3><h2 id="JDK8-JDK21"><a href="#JDK8-JDK21" class="headerlink" title="JDK8-JDK21"></a>JDK8-JDK21</h2><p><a href="https://openjdk.org/jeps/0">https://openjdk.org/jeps/0</a></p>
<p><a href="https://openjdk.org/projects/jdk/17/jeps-since-jdk-11">JEPs in JDK 17 integrated since JDK 11</a></p>
<p><a href="https://openjdk.org/projects/jdk/21/jeps-since-jdk-17">JEPs in JDK 21 integrated since JDK 17</a></p>
<p><a href="https://openjdk.org/projects/jdk9/">https://openjdk.org/projects/jdk9/</a></p>
<p><a href="https://openjdk.org/projects/jdk/10/">https://openjdk.org/projects/jdk/10/</a></p>
<p><a href="https://openjdk.org/projects/jdk/11/">https://openjdk.org/projects/jdk/11/</a></p>
<p><a href="https://openjdk.org/projects/amber/">https://openjdk.org/projects/amber/</a></p>
<p>以下表格(去除了大部分日常开发不需要感知的特性)中按照个人理解根据重要性(影响程度,以及我们日常开发需要关注的程度)进行排序,会比较主观,欢迎补充和指正</p>
<p>这个仓库演示了jdk9-18的新特性 <a href="https://github.com/nipafx/demo-java-x">https://github.com/nipafx/demo-java-x</a></p>
<p>oracle给出的重大变更指南 <a href="https://docs.oracle.com/en/java/javase/21/migrate/significant-changes-jdk-release.html">https://docs.oracle.com/en/java/javase/21/migrate/significant-changes-jdk-release.html</a></p>
<h3 id="语言"><a href="#语言" class="headerlink" title="语言"></a>语言</h3><table>
<thead>
<tr>
<th>Reference</th>
<th>JDK Version</th>
<th>Description</th>
<th>Project&Tags</th>
</tr>
</thead>
<tbody><tr>
<td><a href="https://openjdk.org/jeps/395">JEP 395: Records</a></td>
<td>16</td>
<td>充当不可变数据的载体的类,与元组的形式类似,拥有 <strong>极简单的类结构</strong>,有一些序列化相关的特性需要注意。</td>
<td>Amber - language</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/430">JEP 430: String Templates (Preview)</a></td>
<td>21</td>
<td>提供了一种字符串模板和嵌入表达式生成字符串的方式,并且 <strong>性能接近使用+来进行拼接</strong>!JMH测试结果: <br><a href="21.JDK21%E5%8D%87%E7%BA%A7-StringConcatenationBenchmark.svg">StringConcatenationBenchmark.svg</a><br><a href="https://ionutbalosin.com/2024/02/jvm-performance-comparison-for-jdk-21/">https://ionutbalosin.com/2024/02/jvm-performance-comparison-for-jdk-21/</a><br>目前打算重做这个功能,这也是我不建议开启预览功能的原因之一 <a href="https://mail.openjdk.org/pipermail/amber-spec-experts/2024-April/004106.html">https://mail.openjdk.org/pipermail/amber-spec-experts/2024-April/004106.html</a></td>
<td>Amber - language、performance<br></td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/361">JEP 361: Switch Expressions</a><br><a href="https://openjdk.org/jeps/394">JEP 394: Pattern Matching for instanceof</a><br><a href="https://openjdk.org/jeps/440">JEP 440: Record Patterns</a><br><a href="https://openjdk.org/jeps/441">JEP 441: Pattern Matching for switch</a></td>
<td>14<br>16<br>21<br>21</td>
<td>对 <strong>switch表达式的简化</strong>,包含instanceof的类型转换简化,对多种类型的支持(包含Record)<br>另外还额外对Record的模式匹配进行了简化,对嵌套的Record简化比较明显 <a href="https://belief-driven-design.com/looking-at-java-21-record-patterns-b5282/">https://belief-driven-design.com/looking-at-java-21-record-patterns-b5282/</a></td>
<td>Amber - language</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/286">JEP 286: Local-Variable Type Inference</a></td>
<td>10</td>
<td>笔者的建议是在特定情境下可以使用本特性,大部分是不关心具体的类型信息,或者可以在上下文中明确直接看到类型信息的时候。<br>例如在Stream中使用groupingBy的时候,这种时候类型信息会很长,但我们其实不太关注那一串具体的类型信息。<br>更具体的使用规范可以看 <a href="https://openjdk.org/projects/amber/guides/lvti-style-guide">https://openjdk.org/projects/amber/guides/lvti-style-guide</a></td>
<td>Amber - language</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/378">JEP 378: Text Blocks</a></td>
<td>15</td>
<td>可以使用三个双引号 <code>"""text""" </code><strong><code>包含多行字符串 </code></strong><code>。</code></td>
<td>Amber - language</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/409">JEP 409: Sealed Classes</a></td>
<td>17</td>
<td>密封类,可以 <strong>限制哪些其他类或接口</strong>可以继承或实现它们。</td>
<td>Amber - specification</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/431">JEP 431: Sequenced Collections</a></td>
<td>21</td>
<td>提供了一套 <strong>有序集合接口标准</strong><a href="https://www.infoq.com/news/2023/03/collections-framework-makeover/">https://www.infoq.com/news/2023/03/collections-framework-makeover/</a></td>
<td>Amber - specification</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/309">JEP 309: Dynamic Class-File Constants</a></td>
<td>11</td>
<td>提供了类似 invokedynamic 的常量创建形式,一种使用场景是通过这种方式实现高性能懒加载的单例</td>
<td>Valhalla</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/443">JEP 443: Unnamed Patterns and Variables (Preview)</a></td>
<td>21</td>
<td>对于没有用的变量可以用_代替命名表示省略<br><a href="https://www.baeldung.com/java-unnamed-patterns-variables">https://www.baeldung.com/java-unnamed-patterns-variables</a></td>
<td>Amber - language</td>
</tr>
</tbody></table>
<h3 id="JVM-amp-Compiler"><a href="#JVM-amp-Compiler" class="headerlink" title="JVM&Compiler"></a>JVM&Compiler</h3><table>
<thead>
<tr>
<th>Reference</th>
<th>JDK Version</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr>
<td><a href="https://openjdk.org/jeps/377">ZGC(JEP377) </a>& <a href="https://openjdk.org/jeps/379">Shenandoah(JEP379)</a></td>
<td>15</td>
<td>ZGC和Shenandoah发布了生产版本</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/439">JEP 439: Generational ZGC</a></td>
<td>21</td>
<td>ZGC支持分代了,提升很大,但是目前还存在一些bug</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/307">JEP 307: Parallel Full GC for G1</a></td>
<td>10</td>
<td>G1支持并发的fullgc了,16GB堆也可以在秒级以内了</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/158">JEP 158: Unified JVM Logging</a><br><a href="https://openjdk.org/jeps/271">JEP 271: Unified GC Logging</a></td>
<td>9</td>
<td>规范化了虚拟机里日志的打印,可以参考 <a href="https://juejin.cn/post/6873020339752796167">OpenJDK 11 JVM日志相关参数解析与使用</a></td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/345">JEP 345: NUMA-Aware Memory Allocation for G1</a></td>
<td>14</td>
<td>支持了对NUMA架构的感知,性能提升较大</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/280">JEP 280: Indify String Concatenation</a></td>
<td>9</td>
<td>编译时通过invokedynamic实现字符串拼接(以前是stringBuilder),可以更灵活地实现字符串拼接了,另外也为多语言铺路</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/312">JEP 312: Thread-Local Handshakes</a></td>
<td>10</td>
<td>线程局部握手,对偏向锁之类的场景可以不用等全局安全点了(偏向锁只是一个举例,后面被干掉了)</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/451">JEP 451: Prepare to Disallow the Dynamic Loading of Agents</a></td>
<td>21</td>
<td>jdk准备在将来的版本中默认禁止对正在运行的JVM加载agent(启动时可以)</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/346">JEP 346: Promptly Return Unused Committed Memory from G1</a></td>
<td>12</td>
<td>G1会更好地返还内存给操作系统(但对我们没用)</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/344">JEP 344: Abortable Mixed Collections for G1</a></td>
<td>12</td>
<td>G1能够通过划分必须回收区域、非必需回收区域来更精确地控制MixedGC的耗时。</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/318">JEP 318: Epsilon: A No-Op Garbage Collector (Experimental)</a></td>
<td>11</td>
<td>无GC的收集器,这玩意一般用不上,也只打算作为实验特性,开发者担心用户不小心误用,还是作为实验特性的好</td>
</tr>
</tbody></table>
<h3 id="Other"><a href="#Other" class="headerlink" title="Other"></a>Other</h3><table><colgroup><col width="183"><col width="183"><col width="183"><col width="183"></colgroup>
<thead>
<tr>
<th>Reference</th>
<th>JDK Version</th>
<th>Description</th>
<th>Project&Tags</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://openjdk.org/jeps/444">JEP 444: Virtual Threads</a></td>
<td>21</td>
<td>Loom</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/446">JEP 446: Scoped Values (Preview)</a></td>
<td>21</td>
<td>能在许多场景下替代ThreadLocal,但注意! <strong>它并不是为了取代 ThreadLocal </strong>!也取代不了<br>ThreadLocal变量修改难以追踪,难以调试;<br>容易忘记remove导致内存泄漏;<br>使用InheritableThreadLocal在子线程中传递父线程变量时, API 要求在其他线程中看不到更改线程局部变量的线程副本,因此会做拷贝逻辑 。<br><br>Scoped Value管理的变量不可变,代码更易读,并且通过不可变特性避免了拷贝的内存开销;<br>作用域更明确,生命周期结束后自然回收,内存更安全。<br><br>由于二者设计逻辑不同,依然存在一些场景适配工作量较大,例如将threadLocal当作资源池的场景。<br>最典型的,例如jackson的资源池,log4j2的ReusableMessage <a href="https://github.com/apache/logging-log4j2/pull/1401">https://github.com/apache/logging-log4j2/pull/1401</a><br></td>
<td>Loom</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/446">JEP 453: Structured Concurrency (Preview)</a></td>
<td>21</td>
<td><a href="https://www.infoq.com/news/2023/06/structured-concurrency-jdk-21/">https://www.infoq.com/news/2023/06/structured-concurrency-jdk-21/</a><br>例如<br> <pre><code class="language-java">Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier<String> user = scope.fork(() -> findUser());
Supplier<Integer> order = scope.fork(() -> fetchOrder());
<pre><code> scope.join() // Join both subtasks
.throwIfFailed(); // ... and propagate errors
// Here, both subtasks have succeeded, so compose their results
return new Response(user.get(), order.get());
}
//...
</code></pre>
<p>}<br></p></code></pre>结构化并发提供了一种任务组的结构化组织方式,我们可以比较灵活地处理这些任务,这种方式与虚拟线程的适配是很好的;<br>通过拓展StructuredTaskScope类,我们可以很灵活地定义自己想要的业务逻辑,并且可以将任务编排与业务逻辑分离开。</td><p></p>
<td>Loom</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/412">JEP 412: Foreign Function & Memory API (Incubator)</a></td>
<td>17</td>
<td>Panama对于JDK的影响其实比Loom可能更大(对我们上层开发者的影响不及Loom),感兴趣的可以看看相关的,Panama/JNI/JNA/JNR <a href="https://glavo.site/blog/2023/03/25/java-ffi-benchmark/">https://glavo.site/blog/2023/03/25/java-ffi-benchmark/</a></td>
<td>Panama</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/448">JEP 448: Vector API (Sixth Incubator)</a></td>
<td>21</td>
<td>支持SIMD向量化加速计算,至于什么是SIMD可以去百度一下,基本思想是通过 元素合并实现 在一个CPU周期内实现多个元素计算的并行<br>相关的还有个很火的项目 <a href="https://github.com/simdjson/simdjson">https://github.com/simdjson/simdjson </a>,使用SIMD加速JSON解析<br>也有java版本的 <a href="https://github.com/simdjson/simdjson-java">https://github.com/simdjson/simdjson-java</a></td>
<td>Panama</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/358">JEP 358: Helpful NullPointerExceptions</a></td>
<td>14</td>
<td>能够更准确地提供空指针异常信息</td>
<td></td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/321">JEP 321: HTTP Client API</a></td>
<td>11</td>
<td>jdk提供了一套标准的HTTPClient的api</td>
<td></td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/349">JEP 349: JFR Event Streaming</a></td>
<td>14</td>
<td></td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/259">JEP 259: Stack-Walking API</a></td>
<td>9</td>
<td>在stackWalker之前只能通过 getStackTrace获取完整的线程堆栈,stackWalker允许通过StackFrame的流来执行想要的操作,这允许我们获取一部分堆栈,甚至能拿到堆栈上持有锁(monitor)的情况、局部变量、以及当前帧是编译帧还是解释帧等非常详细的信息。是个很实用的api。</td>
<td></td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/250">JEP 250: Store Interned Strings in CDS Archives</a><br><a href="https://openjdk.org/jeps/310">JEP 310: Application Class-Data Sharing</a><br><a href="https://openjdk.org/jeps/341">JEP 341: Default CDS Archives</a><br><a href="https://openjdk.org/jeps/350">JEP 350: Dynamic CDS Archives</a></td>
<td>9<br>10<br>12<br>13</td>
<td>类数据共享存档,主要是需要提前构建好存档有点麻烦,使用可以参考 <a href="https://www.morling.dev/blog/building-class-data-sharing-archives-with-apache-maven/">https://www.morling.dev/blog/building-class-data-sharing-archives-with-apache-maven/</a></td>
<td></td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/193">JEP 193: Variable Handles</a></td>
<td>9</td>
<td>通过VarHanlde可以实现更细粒度的内存屏障控制,例如Caffeine中就通过VarHandle来维护时间戳。</td>
<td>Code Tools</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/254">JEP 254: Compact Strings</a></td>
<td>9</td>
<td>String的底层从char变成了byte,粒度更细了,占用内存可以变少一些。但是特定场景下可能会增加cpu开销。 <a href="https://ionutbalosin.com/2018/06/compact-strings-feature-might-slow-down-predominant-utf-16-strings-applications/">https://ionutbalosin.com/2018/06/compact-strings-feature-might-slow-down-predominant-utf-16-strings-applications/</a></td>
<td></td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/285">JEP 285: Spin-Wait Hints</a></td>
<td>9</td>
<td>自旋的场景使用 Thread.onSpinWait()可以节省CPU资源,例如<pre><code class="language-java"> volatile boolean eventNotificationNotReceived;
void waitForEventAndHandleIt() {
while ( eventNotificationNotReceived ) {
Thread.onSpinWait();
}
readAndProcessEvent();
}
</code></pre>onSpinWait可以适当降低自己的优先级,降低对CPU资源的消耗<br>但值得注意的是, onSpinWait在命中C2之前都是空实现的逻辑,等于啥也没干,直到C2优化后才能发挥特性<br><a href="https://bugs.openjdk.org/browse/JDK-8147844">https://bugs.openjdk.org/browse/JDK-8147844</a></td>
<td></td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/416">JEP 416: Reimplement Core Reflection with Method Handles</a></td>
<td>18</td>
<td>使用MethodHandles重新实现了反射(部分场景变快,部分变慢,具体见JEP描述,主要目的是规范化)。APIMethodHandle在JDK7中引入,在使用方法上比反射复杂一些,限制也比反射多一点,但是正确的使用可以使性能接近非反射直接调用方法。PR: <a href="https://github.com/openjdk/jdk/pull/5027">https://github.com/openjdk/jdk/pull/5027</a><br>顺带一提,Java表现出来了明显的倾向,想要尽量消除或者屏蔽C代码,想要尽可能替换成Java的实现。</td>
<td>Loom</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<h3 id="废弃-amp-删除"><a href="#废弃-amp-删除" class="headerlink" title="废弃&删除"></a>废弃&删除</h3><table><colgroup><col width="344"><col width="100"><col width="908"></colgroup>
<thead>
<tr>
<th>Reference</th>
<th>JDK Version</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://openjdk.org/jeps/374">JEP 374: Deprecate and Disable Biased Locking</a></td>
<td>15</td>
<td>废弃了偏向锁,这部分代码非常复杂,维护起来很麻烦,并且大部分情况下对性能并没有好处</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/363">JEP 363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector</a></td>
<td>14</td>
<td>移除了CMS收集器</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/366">JEP 366: Deprecate the ParallelScavenge + SerialOld GC Combination</a></td>
<td>14</td>
<td>废弃了ParallelScavenge + SerialOld GC的组合</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/410">JEP 410: Remove the Experimental AOT and JIT Compiler</a></td>
<td>17</td>
<td>移除了实验性质的AOT(jaotc tool)和 JIT编译器,这部分工作和GraalVM一样使用Graal项目,但是没什么人用,还要单独去维护它。<br>因此直接移除了,并且建议需要使用 Graal 编译器用于 AOT 或 JIT的直接去用GraalVM即可。</td>
</tr>
<tr>
<td><a href="https://openjdk.org/jeps/214">JEP 214: Remove GC Combinations Deprecated in JDK 8</a></td>
<td>9</td>
<td>删除了以下GC组合<ul>
<li>DefNew + CMS</li>
<li>ParNew + SerialOld</li>
<li>Incremental CMS</li>
</ul></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<h3 id="其他分类"><a href="#其他分类" class="headerlink" title="其他分类"></a>其他分类</h3><h4 id="jdk版本控制"><a href="#jdk版本控制" class="headerlink" title="jdk版本控制"></a>jdk版本控制</h4><p><a href="https://openjdk.org/jeps/322">JEP 322: Time-Based Release Versioning</a></p>
<p>重新规范了JDK的发版规则,更多jdk发版有关的信息可以参考 <a href="https://whichjdk.com/">https://whichjdk.com/</a></p>
<p>发布模型的变更可以看这两篇</p>
<p><a href="https://blogs.oracle.com/javamagazine/post/java-long-term-support-lts">https://blogs.oracle.com/javamagazine/post/java-long-term-support-lts</a></p>
<p><a href="https://blogs.oracle.com/java/post/moving-the-jdk-to-a-two-year-lts-cadence">https://blogs.oracle.com/java/post/moving-the-jdk-to-a-two-year-lts-cadence</a></p>
<h4 id="Project-Jigsaw-项目-模块化"><a href="#Project-Jigsaw-项目-模块化" class="headerlink" title="Project Jigsaw 项目-模块化"></a><a href="https://openjdk.org/projects/jigsaw/">Project Jigsaw </a>项目-模块化</h4><ul>
<li><p><a href="https://openjdk.org/jeps/200">200: The Modular JDK 200:模块化 JDK</a></p>
</li>
<li><p><a href="https://openjdk.org/jeps/201">201: Modular Source Code 201:模块化源代码</a></p>
</li>
<li><p><a href="https://openjdk.org/jeps/220">220: Modular Run-Time Images 模块化 Run-Time 映像 –illegal-access</a></p>
</li>
<li><p><a href="https://openjdk.org/jeps/260">260: Encapsulate Most Internal APIs 封装大多数内部 API</a></p>
</li>
<li><p><a href="https://openjdk.org/jeps/261">261: Module System 261:模块系统</a></p>
</li>
<li><p><a href="https://openjdk.org/jeps/403">JEP 403: Strongly Encapsulate JDK Internals 强封装</a></p>
</li>
<li><p><a href="https://openjdk.org/jeps/282">282: jlink: The Java Linker</a></p>
</li>
</ul>
<p>这里简单说下,一般升级时需要关注export和opens</p>
<p>export是模块内部会向外export一些包,而不被模块主动export的则认为是随时可变的,不做任何保证。如果使用了模块没有向外export的API,则会在编译期报错,需要在编译和启动参数手动增加export参数。</p>
<p>opens是运行期的检查,对应反射访问模块API的情况,根据运行期间的报错加一下就好,这个需要加在启动参数上。</p>
<p>值得注意的是,在jdk9开始做模块化以后,通过 –illegal-access来做了运行时的模块化控制,也就是opens的统一控制</p>
<ul>
<li><p><code>--illegal-access=permit</code> : JDK9以后默认模式,只在第一次非法反射访问时 warning一下</p>
</li>
<li><p><code>--illegal-access=warn</code> :与 <code>permit类似,不过每次非法反射都会 warning</code></p>
</li>
<li><p><code>--illegal-access=debug</code> :在warn的基础上,加上堆栈输出</p>
</li>
<li><p><code>--illegal-access=deny</code> : 拒绝所有非法反射访问内部api,JDK16下的默认模式</p>
</li>
</ul>
<p>但是在JDK17中,通过 <a href="https://openjdk.org/jeps/403">JEP 403: Strongly Encapsulate JDK Internals 强封装 </a>, <code>--illegal-access这个参数被无效化了,相当于只能deny,我们只能通过 opens来控制运行时反射的访问。</code></p>
<h4 id="适配-amp-兼容"><a href="#适配-amp-兼容" class="headerlink" title="适配&兼容"></a>适配&兼容</h4><p><a href="https://openjdk.org/jeps/247">JEP 247: Compile for Older Platform Versions</a></p>
<p>编译时可以通过source和target参数灵活控制产物jdk版本,对兼容老版本还是有好处的。</p>
<p><a href="https://openjdk.org/jeps/238">JEP 238: Multi-Release JAR Files</a></p>
<p>可以指定不同jdk版本的环境中使用不同的api。例如我维护了一个jar包,其中一个接口在jdk8下希望使用某个api,在jdk21下使用另一个api,就可以通过 <a href="https://openjdk.org/jeps/238">Multi-Release JAR </a>实现,不过这个东西实际比较鸡肋。</p>
<h3 id="不在JEP内的"><a href="#不在JEP内的" class="headerlink" title="不在JEP内的"></a>不在JEP内的</h3><p>Java 18, ZGC, SerialGC, and ParallelGC support string deduplication</p>
<p><a href="https://blogs.oracle.com/javamagazine/post/java-18-gems-hidden-subtle-changes-deprecations">https://blogs.oracle.com/javamagazine/post/java-18-gems-hidden-subtle-changes-deprecations</a></p>
<p>jakarta</p>
<p><a href="https://wiki.eclipse.org/Jakarta_EE_Maven_Coordinates">https://wiki.eclipse.org/Jakarta_EE_Maven_Coordinates</a></p>
<p>参考</p>
<p><a href="https://blogs.oracle.com/java/post/the-arrival-of-java-21">https://blogs.oracle.com/java/post/the-arrival-of-java-21</a></p>
<p><a href="https://cn.quarkus.io/blog/virtual-thread-1/">https://cn.quarkus.io/blog/virtual-thread-1/</a></p>
<p><a href="https://chriswhocodes.com/jepmap.html">https://chriswhocodes.com/jepmap.html</a></p>
]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>JDK21</tag>
<tag>JVM</tag>
<tag>JEP</tag>
</tags>
</entry>
<entry>
<title>Loom(虚拟线程)相关</title>
<url>/2024/09/05/2024-09-05-loom-virtual-threads/</url>
<content><![CDATA[<p>JDK21的 虚拟线程 目前存在许多严重bug,尽量不要在可靠性要求较高的服务中使用!</p>
<h1 id="简单介绍"><a href="#简单介绍" class="headerlink" title="简单介绍"></a>简单介绍</h1><p>虚拟线程使用一个专用的,FIFO模式下的ForkJoin线程池(parallelStream使用的是LIFO模式)。</p>
<span id="more"></span>
<p>它的并行度就是承载虚拟线程的平台线程数,默认与可用核心数相等,也可以使用参数 jdk.virtualThreadScheduler.parallelism 来调整。</p>
<h1 id="部分实现细节"><a href="#部分实现细节" class="headerlink" title="部分实现细节"></a>部分实现细节</h1><p><a href="https://www.bilibili.com/video/BV1Ub4y1M7L3/?share_source=copy_web&vd_source=d30c780580db9da56e2e4f9fa6acf1a5">[JVMLS][精翻+个人补充说明]虚拟线程Continuation实现原理 </a>- 原视频 <a href="https://inside.java/2023/08/26/continuations-under-the-covers/">https://inside.java/2023/08/26/continuations-under-the-covers/</a></p>
<h1 id="推荐资料"><a href="#推荐资料" class="headerlink" title="推荐资料"></a>推荐资料</h1><p>以下资料主要是对java虚拟线程的设计和思考,具体实现以jdk为准</p>
<p><a href="https://cr.openjdk.org/~rpressler/loom/Loom-Proposal.html">https://cr.openjdk.org/~rpressler/loom/Loom-Proposal.html</a></p>
<p><a href="https://cr.openjdk.org/~rpressler/loom/loom/sol1_part1.html">https://cr.openjdk.org/~rpressler/loom/loom/sol1_part1.html</a></p>
<p><a href="https://cr.openjdk.org/~rpressler/loom/loom/sol1_part2.html">https://cr.openjdk.org/~rpressler/loom/loom/sol1_part2.html</a></p>
<p><a href="https://inside.java/2021/05/10/networking-io-with-virtual-threads/">https://inside.java/2021/05/10/networking-io-with-virtual-threads/</a></p>
<p><a href="https://inside.java/2020/08/07/loom-performance/">https://inside.java/2020/08/07/loom-performance/</a></p>
<p><a href="https://blogs.oracle.com/javamagazine/post/going-inside-javas-project-loom-and-virtual-threads">https://blogs.oracle.com/javamagazine/post/going-inside-javas-project-loom-and-virtual-threads</a></p>
<p>如果对异步发展史感兴趣,可以观看 <a href="https://www.bilibili.com/video/BV1Fu4m1K7xb/?share_source=copy_web&vd_source=d30c780580db9da56e2e4f9fa6acf1a5">【[Devoxx2023]深入理解Java异步编程发展历史-从线程到响应式再到虚拟线程】 </a>- 原视频 <a href="https://youtu.be/1zSF1259s6w?si=tJGrNK-Uh6Hgw4B9">https://youtu.be/1zSF1259s6w?si=tJGrNK-Uh6Hgw4B9</a></p>
<h1 id="风险"><a href="#风险" class="headerlink" title="风险"></a>风险</h1><p>虚拟线程支持ThreadLocal和synchronized,但是存在一些缺陷,有一些问题需要我们额外注意</p>
<h2 id="1-绑定问题"><a href="#1-绑定问题" class="headerlink" title="1.绑定问题"></a>1.绑定问题</h2><ol>
<li><p>虚拟线程执行时 在 <code>synchronized</code> 块或方法中执行代码时,会 与平台线程绑定(同步块内,以及等待同步锁都会pin),因此需要使用 ReentrantLock替代(未来有望从底层解决-目前已经有相关JEP,会在JDK24上 <a href="https://openjdk.org/jeps/491">https://openjdk.org/jeps/491 </a>)</p>
</li>
<li><p>虚拟线程执行native代码或外来函数( <a href="https://openjdk.org/jeps/424">JEP424 </a>)时是无法被中止的(无法从底层解决,目前典型的是类初始化)。</p>
</li>
</ol>
<h3 id="故障实例"><a href="#故障实例" class="headerlink" title="故障实例"></a>故障实例</h3><h4 id="1-P1-死锁(这块风险在JEP中实际上被低估了)"><a href="#1-P1-死锁(这块风险在JEP中实际上被低估了)" class="headerlink" title="1[P1].死锁(这块风险在JEP中实际上被低估了)"></a>1[P1].死锁(这块风险在JEP中实际上被低估了)</h4><h5 id="1-最经典场景(大部分死锁本质都是这类问题)"><a href="#1-最经典场景(大部分死锁本质都是这类问题)" class="headerlink" title="1.最经典场景(大部分死锁本质都是这类问题)"></a>1.最经典场景(大部分死锁本质都是这类问题)</h5><p>这个问题触发概率高,而且影响大,目前已经有许多第三方开源组件都出现了这个问题</p>
<p><a href="https://www.reddit.com/r/java/comments/1512xuo/virtual_threads_interesting_deadlock/">https://www.reddit.com/r/java/comments/1512xuo/virtual_threads_interesting_deadlock/ </a>(这里的分析实际上有点问题,但是无伤大雅,下面会说到)</p>
<p><a href="https://mail.openjdk.org/pipermail/loom-dev/2023-July/005993.html">https://mail.openjdk.org/pipermail/loom-dev/2023-July/005993.html</a></p>
<p><strong>复现代码</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < Runtime.getRuntime().availableProcessors() + <span class="number">10</span>; i++) {</span><br><span class="line"> Thread.startVirtualThread(() -> {</span><br><span class="line"> System.out.println(Thread.currentThread() + <span class="string">": Before synchronized block"</span>);</span><br><span class="line"> <span class="keyword">synchronized</span> (Main.class) {</span><br><span class="line"> System.out.println(Thread.currentThread() + <span class="string">": Inside synchronized block"</span>);</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p><strong>原理分析</strong></p>
<p>大体原理可以参考上面的这个讨论内容</p>
<p><a href="https://www.reddit.com/r/java/comments/1512xuo/virtual_threads_interesting_deadlock/">https://www.reddit.com/r/java/comments/1512xuo/virtual_threads_interesting_deadlock/</a></p>
<p><strong>代码可以简化为</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i < Runtime.getRuntime().availableProcessors() + <span class="number">1</span>; i++) {</span><br><span class="line"> Thread.startVirtualThread(() -> {</span><br><span class="line"> doSthWithLock();</span><br><span class="line"> <span class="keyword">synchronized</span> (Main.class) {</span><br><span class="line"> doSthWithLock()</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">doSthWithLock</span><span class="params">()</span> {</span><br><span class="line"> lock.lock();</span><br><span class="line"> <span class="comment">// doSomeThing</span></span><br><span class="line"> lock.unlock();</span><br><span class="line"> } </span><br></pre></td></tr></table></figure>
<p>但是帖子里说到:</p>
<p>这里的分析略有偏差。如果 [#30]能得到锁的话,说明已经开始运行了,如果它没有yield让出平台线程的话应该是会执行到释放锁逻辑的,所以这个说法应该是不太准确的。</p>
<p>这里我们取这两个阻塞在System.out.println方法上的线程进行分析,即[#26](阻塞在同步代码块内部),[#30](阻塞在同步代码块外部)。</p>
<p>Lock内部实现的AQS使用 FIFO 队列来进行线程的排队进行资源抢占(对于已经在队列中的线程来说无论是公平锁还是非公平锁都是遵循FIFO的)。</p>
<p>这个死锁的场景实际上是FIFO队列中, [#30]排在[#26]前面,而当有一个线程执行完同步块外部的方法释放锁时,会将队列前面的Node,即[#30]进行unpark,然后自身马上停靠在synchronized同步块上被绑定</p>
<p>此时其他虚拟线程都在pinned状态,占用了所有平台线程(假设8核,即7个线程阻塞在同步块的锁上,1个线程阻塞在同步块内部的锁上)</p>
<p>因此这个unpark是无效的,[#30]此时没有平台线程可以拿来绑定,因此也就无法上锁,而[#26]又由于排在[#30]后面而无法得到unpark通知,因此死锁。</p>
<p>对于以上分析,我们可以通过debug来佐证</p>
<p>1.首先验证锁的情况,可以看到锁的确是并没有被抢占到的</p>
<p>2.当前AQS队列中#32排在#25前面,而#32阻塞在同步块外部的lock上,而#25阻塞在同步块内部的lock上,其他虚拟线程都在同步块上被绑定着</p>
<p>vt.json</p>
<p>如果摒弃ReentrantLock的影响,实际状态可以简化为类似以下代码情况</p>
<p>下面情况需要只有一个载体线程,即 -Djdk.virtualThreadScheduler.parallelism=1,另外需要模块化参数–add-opens java.base/jdk.internal.misc=ALL-UNNAMED</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> jdk.internal.misc.VirtualThreads;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestThreadDeadLock</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">vt1state</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> <span class="type">int</span> <span class="variable">vt2state</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> <span class="type">Thread</span> <span class="variable">vt1</span> <span class="operator">=</span> Thread.startVirtualThread(() -> {</span><br><span class="line"> VirtualThreads.park();</span><br><span class="line"> vt1state++;</span><br><span class="line"> });</span><br><span class="line"> <span class="comment">// 等待t1进入park</span></span><br><span class="line"> <span class="keyword">while</span> (!Thread.State.WAITING.equals(vt1.getState())) {</span><br><span class="line"> Thread.onSpinWait();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">Thread</span> <span class="variable">vt2</span> <span class="operator">=</span> Thread.startVirtualThread(() -> {</span><br><span class="line"> <span class="comment">// 唤醒t1</span></span><br><span class="line"> VirtualThreads.unpark(vt1);</span><br><span class="line"> <span class="comment">// 唤醒t1后立即pin住当前唯一的载体线程</span></span><br><span class="line"> syncPark();</span><br><span class="line"> vt2state++;</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> (<span class="literal">true</span>) {</span><br><span class="line"> Thread.sleep(<span class="number">1000</span>);</span><br><span class="line"> System.out.println(<span class="string">"vt1state:"</span> + vt1state + <span class="string">" vt2state: "</span> + vt2state);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">syncPark</span><span class="params">()</span> {</span><br><span class="line"> VirtualThreads.park();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>运行后输出应该如下,vt1被unpark也无法执行到vt1state++的代码</p>
<p><strong>相关问题</strong></p>
<p><a href="https://bugs.openjdk.org/browse/JDK-8320211">https://bugs.openjdk.org/browse/JDK-8320211</a></p>
<p><a href="https://www.javaspecialists.eu/archive/Issue302-Virtual-Thread-Deadlocks.html">https://www.javaspecialists.eu/archive/Issue302-Virtual-Thread-Deadlocks.html</a></p>
<p><a href="https://www.reddit.com/r/java/comments/17xkkoc/java_virtual_threads_pitfalls_to_look_out_for/">https://www.reddit.com/r/java/comments/17xkkoc/java_virtual_threads_pitfalls_to_look_out_for/</a></p>
<p><strong>conf相关问题(conf上面这几个问题实际上也类似上面提到的)</strong></p>
<h5 id="2-单线程的死锁(更简单)"><a href="#2-单线程的死锁(更简单)" class="headerlink" title="2.单线程的死锁(更简单)"></a>2.单线程的死锁(更简单)</h5><p>一个简化场景是单线程调度器 vt1拿到了lock A然后遇到io让出 vt2拿到sync然后去拿lock A pin住</p>
<h5 id="3-Native或FFI导致的死锁"><a href="#3-Native或FFI导致的死锁" class="headerlink" title="3.Native或FFI导致的死锁"></a>3.Native或FFI导致的死锁</h5><p>这个可能比较难发生,但是与同步块的死锁是同理的</p>
<p>例如在类加载内与类加载外存在ReentrantLock,或者其他资源的抢占,就可能发生。</p>
<h4 id="2-P2-平台线程资源耗尽"><a href="#2-P2-平台线程资源耗尽" class="headerlink" title="2[P2].平台线程资源耗尽"></a>2[P2].平台线程资源耗尽</h4><p><a href="https://blog.ydb.tech/how-we-switched-to-java-21-virtual-threads-and-got-deadlock-in-tpc-c-for-postgresql-cca2fe08d70b">How we switched to Java 21 virtual threads and got a deadlock in TPC-C for PostgreSQL</a></p>
<p><a href="https://news.ycombinator.com/item?id=39008026">https://news.ycombinator.com/item?id=39008026</a></p>
<p>这个问题源于线程的pinning,需要注意。</p>
<h2 id="2-ThreadLocal"><a href="#2-ThreadLocal" class="headerlink" title="2.ThreadLocal"></a>2.ThreadLocal</h2><p>ThreadLocal的问题主要在于线程模型的变化</p>
<p>没有虚拟线程前我们可以假定线程数不会太多,但是虚拟线程打破了这个限制,一个系统可能同时存在非常多虚拟线程</p>
<p>因此ThreadLocal的数据量就可能非常非常多,这也就是ThreadLocal的风险所在了。用的时候注意一下就好。</p>
<p>这里cue一下ScopedValue,有些人可能期望使用ScopedValue完全替代ThreadLocal,其实这是不现实的。</p>
<p>最典型的就是ThreadLocal用于资源池化的场景,例如jackson中和log4j2中都有这种用法。例如 log4j2的ReusableMessage,现在老实换成普通资源池了 <a href="https://github.com/apache/logging-log4j2/pull/1401">https://github.com/apache/logging-log4j2/pull/1401 </a>。</p>
<h3 id="故障实例-1"><a href="#故障实例-1" class="headerlink" title="故障实例"></a>故障实例</h3><h4 id="1-P2-线程变量使用不当导致OOM"><a href="#1-P2-线程变量使用不当导致OOM" class="headerlink" title="1.[P2].线程变量使用不当导致OOM"></a>1.[P2].线程变量使用不当导致OOM</h4><p>但是这个其实跟 ThreadLocal关系不大。。。。</p>
<h2 id="监控"><a href="#监控" class="headerlink" title="监控"></a>监控</h2><p>可以使用JFR来观察虚拟线程的 在绑定时的挂起情况</p>
<p><code>jdk.VirtualThreadStart</code> 和 <code>jdk.VirtualThreadEnd</code></p>
<p><code>记录了虚拟线程的启动和结束,默认关闭</code></p>
<p>jdk.VirtualThreadPinned</p>
<p> 表示虚拟线程绑定在平台线程上时,如果阻塞那么会产生这个事件(即没有释放其平台线程),阈值为20ms,默认开启</p>
<p>jdk.VirtualThreadSubmitFailed</p>
<p>启动或取消unparking虚拟线程失败,默认开启</p>
<p><a href="https://openjdk.org/jeps/425#JDK-Flight-Recorder-JFR">https://openjdk.org/jeps/425#JDK-Flight-Recorder-JFR</a></p>
<h3 id="2-【推荐】JFR-EventStream"><a href="#2-【推荐】JFR-EventStream" class="headerlink" title="2.【推荐】JFR EventStream"></a><strong>2.【推荐】JFR EventStream</strong></h3><p>由于 <code>Djdk.tracePinnedThreads本身会导致pin线程(下面会讲到),因此JFR是我们监控pin线程的首选方案</code></p>
<p><code>但是如果使用通过命令行的方式使用JFR会存在以下问题</code></p>
<p><code>1. dump文件较大,获取监控数据不方便,难以一次获取长期分析数据</code></p>
<p><code>2. dump文件需要上机器控制台dump文件,比较麻烦</code></p>
<p><code>3. 一次只能查看单机数据</code></p>
<p><code>而如果我们自己去解析JFR文件实际上也能做到持续监控,但是会很麻烦</code></p>
<p>因此使用JFR event stream来解决这个问题</p>
<p>JFR的事件流是jdk14引入的特性 <a href="https://openjdk.org/jeps/349">JEP 349: JFR Event Streaming</a></p>
<p>这边用的是封装过的监控埋点组件,替换成自己的就好</p>
<p><strong>JFR event stream</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> com.google.common.collect.Maps;</span><br><span class="line"><span class="keyword">import</span> jdk.jfr.consumer.RecordedEvent;</span><br><span class="line"><span class="keyword">import</span> jdk.jfr.consumer.RecordingStream;</span><br><span class="line"><span class="keyword">import</span> jdk.jfr.internal.tool.PrettyWriter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.PrintWriter;</span><br><span class="line"><span class="keyword">import</span> java.io.StringWriter;</span><br><span class="line"><span class="keyword">import</span> java.time.Duration;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.Map;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * JFR 事件流监控配置</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2024/04/20</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JFRMonitorConfiguration</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">LogFacade</span> <span class="variable">LOGGER</span> <span class="operator">=</span> LogFacadeFactory.getLogFacade(JFRMonitorConfiguration.class);</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">TITLE</span> <span class="operator">=</span> <span class="string">"JFR_LOG"</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">VIRTUAL_THREAD_PINNED</span> <span class="operator">=</span> <span class="string">"jdk.VirtualThreadPinned"</span>;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">JAVA_MONITOR_ENTER</span> <span class="operator">=</span> <span class="string">"jdk.JavaMonitorEnter"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> {</span><br><span class="line"> Thread.ofVirtual().name(<span class="string">"JFRMonitorThread"</span>).start(JFRMonitorConfiguration::startJFREventStream);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">startJFREventStream</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">try</span> (<span class="type">var</span> <span class="variable">rs</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RecordingStream</span>()) {</span><br><span class="line"> rs.enable(VIRTUAL_THREAD_PINNED).withoutThreshold();</span><br><span class="line"> rs.enable(JAVA_MONITOR_ENTER).withThreshold(Duration.ofMillis(<span class="number">5</span>));</span><br><span class="line"> List.of(VIRTUAL_THREAD_PINNED, JAVA_MONITOR_ENTER)</span><br><span class="line"> .forEach(name -> rs.onEvent(name, event -> doLog(event, name)));</span><br><span class="line"> rs.start();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">doLog</span><span class="params">(RecordedEvent event, String eventName)</span> {</span><br><span class="line"> Map<String, String> tags = Maps.newHashMapWithExpectedSize(<span class="number">2</span>);</span><br><span class="line"> tags.put(<span class="string">"eventName"</span>, eventName);</span><br><span class="line"> tags.put(<span class="string">"isVirtual"</span>, String.valueOf(event.getThread().isVirtual()));</span><br><span class="line"> MetricClient.withTags(tags).recordOne(<span class="string">"JFRDuration"</span>, event.getDuration().toMillis());</span><br><span class="line"> LOGGER.build(TITLE, eventToString(event))</span><br><span class="line"> .tag(<span class="string">"eventName"</span>, eventName)</span><br><span class="line"> .tag(<span class="string">"threadId"</span>, event.getThread().getJavaThreadId())</span><br><span class="line"> .tag(<span class="string">"isVirtual"</span>, event.getThread().isVirtual())</span><br><span class="line"> .warn();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> String <span class="title function_">eventToString</span><span class="params">(RecordedEvent event)</span> {</span><br><span class="line"> <span class="type">StringWriter</span> <span class="variable">s</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringWriter</span>();</span><br><span class="line"> <span class="type">PrettyWriter</span> <span class="variable">p</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PrettyWriter</span>(<span class="keyword">new</span> <span class="title class_">PrintWriter</span>(s));</span><br><span class="line"> p.setStackDepth(<span class="number">64</span>);</span><br><span class="line"> p.print(event);</span><br><span class="line"> p.flush(<span class="literal">true</span>);</span><br><span class="line"> <span class="keyword">return</span> s.toString();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>需要加上以下参数</p>
<p>1.maven-surefire-plugin 中加上</p>
<p>--add-opens jdk.jfr/jdk.jfr.internal.tool=ALL-UNNAMED</p>
<p>2.maven-compiler-plugin 中加上</p>
<p><compilerArgs> <arg>–add-exports</arg> <arg>jdk.jfr/jdk.jfr.internal.tool=ALL-UNNAMED</arg> </compilerArgs></p>
<p>--add-exports jdk.jfr/jdk.jfr.internal.tool=ALL-UNNAMED</p>
<p>这个事件的触发条件是:线程在pinned情况下出现park操作的耗时超过duration(默认20ms)</p>
<p>当然,这样依然会有问题存在<br>1.在JFR启动之前,可能有一些其他的pin事件发生了,例如一些类加载,会被忽略掉<br>2.JFR的启动在premain之后,也就是说对于启动期间静态agent premain操作中的pin事件我们也是不知道的<br>对于问题1,我们可以使用命令行启动JFR的方式解决,随后去机器上dump启动期间的jfr记录就好。或者我们可以自定义一个agent,确保jfr监控在其他类加载之前启动。<br>问题2目前是无解的,这是在JVM里写死的<br>当然,如果启动期间有严重问题会直接导致机器启动不起来,并不会造成太大的影响。</p>
<h3 id="3-jdk-tracePinnedThreads-只能在非生产使用!"><a href="#3-jdk-tracePinnedThreads-只能在非生产使用!" class="headerlink" title="3. jdk.tracePinnedThreads ( 只能在非生产使用! )"></a><strong>3. <code>jdk.tracePinnedThreads </code></strong><code>( </code><strong><code>只能在非生产使用! </code></strong><code>)</code></h3><p>当线程固定在平台线程上阻塞时</p>
<p><code>-Djdk.tracePinnedThreads=full</code> 打印完整的堆栈跟踪,并突出显示本机帧和持有锁的帧。</p>
<p><code>-Djdk.tracePinnedThreads=short</code> 输出限制为仅有问题的帧。</p>
<p><code>目前存在以下两个严重bug</code></p>
<h4 id="bug1(deadlock)-https-bugs-openjdk-org-browse-JDK-8322846"><a href="#bug1(deadlock)-https-bugs-openjdk-org-browse-JDK-8322846" class="headerlink" title="bug1(deadlock): https://bugs.openjdk.org/browse/JDK-8322846"></a><code>bug1(deadlock):</code> <a href="https://bugs.openjdk.org/browse/JDK-8322846"><code>https://bugs.openjdk.org/browse/JDK-8322846</code></a></h4><p><code>这个问题实质上就是lock和sync的嵌套导致的死锁问题, </code><strong><code>bug1已经在jdk21.0.4修复</code></strong></p>
<ul>
<li><code>解决</code></li>
</ul>
<p><code>可以尝试使用- </code><a href="http://djdk.io/"><code>Djdk.io </code></a><code>.useMonitors=true来将内部的ReentrantLock替换成使用synchronized,但是这样有饮鸩止渴的意味,不推荐</code></p>
<h4 id="bug2(假死问题,hang住虚拟机)-https-bugs-openjdk-org-browse-JDK-8325521"><a href="#bug2(假死问题,hang住虚拟机)-https-bugs-openjdk-org-browse-JDK-8325521" class="headerlink" title="bug2(假死问题,hang住虚拟机): https://bugs.openjdk.org/browse/JDK-8325521"></a><code>bug2(假死问题,hang住虚拟机): </code><a href="https://bugs.openjdk.org/browse/JDK-8325521"><code>https://bugs.openjdk.org/browse/JDK-8325521</code></a></h4><p><code>这里对bug2展开说说</code></p>
<ul>
<li>根因</li>
</ul>
<p>JVM里的JVMTI存在bug,jdk20时就有相关pr( <a href="https://github.com/openjdk/jdk/pull/10321%EF%BC%89">https://github.com/openjdk/jdk/pull/10321 </a>) ,应该是漏了部分逻辑,将会在23以后完成修复,原因如下</p>
<ul>
<li><code>触发条件</code></li>
</ul>
<p>1.开启了参数-Djdk.tracePinnedThreads;<br>2.使用某个内部 agent增强了Runnable;<br>3.在某个内部 agent attach之前没有触发过java.lang.PinnedThreadPrinter类里lambda表达式的加载</p>
<ul>
<li>解决(如果不想关闭 -Djdk.tracePinnedThreads )</li>
</ul>
<p>1.关闭JVM里对JVMTI事件的处理 -XX:-DoJVMTIVirtualThreadTransitions</p>
<p>2.在某个内部 agent attach之前自己手动触发一次pin</p>
<ul>
<li>复现代码(参数 -Djdk.attach.allowAttachSelf=true -Djdk.tracePinnedThreads=full )</li>
</ul>
<p><strong>hang JVM复现代码</strong></p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.example;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.LockSupport;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TestAgentJVMHang</span> {</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException, ClassNotFoundException {</span><br><span class="line"> <span class="comment">// 1. 启动Agent</span></span><br><span class="line"> System.out.println(<span class="string">"Agent loaded"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2. 启动VirtualThread park</span></span><br><span class="line"> Thread.ofVirtual().start(TestAgentJVMHang::testPinnedPrinter);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 3. 进行类加载 - hang</span></span><br><span class="line"> Thread.sleep(<span class="number">1000</span>);</span><br><span class="line"> System.out.println(<span class="string">"finished"</span>);</span><br><span class="line"> Class<?> aClass = Class.forName(<span class="string">"org.example.TestAgentJVMHang$TestClass"</span>);</span><br><span class="line"> System.out.println(aClass);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">testPinnedPrinter</span><span class="params">()</span> {</span><br><span class="line"> LockSupport.parkNanos(<span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">TestClass</span> {</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h2 id="解决措施"><a href="#解决措施" class="headerlink" title="解决措施"></a>解决措施</h2><ul>
<li><p>最好的解决方案是官方提供synchronized对虚拟线程的支持(在jdk24中已经实现了适配 <a href="https://openjdk.org/jeps/491">JEP 491 </a>)</p>
</li>
<li><p>尽量从synchronized迁移到ReentrantLock</p>
</li>
</ul>
<h3 id="自动档"><a href="#自动档" class="headerlink" title="自动档"></a>自动档</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><dependency></span><br><span class="line"> <groupId>mysql</groupId></span><br><span class="line"> <artifactId>mysql-connector-java</artifactId></span><br><span class="line"> <version><span class="number">8.3</span><span class="number">.0</span>-vt<span class="number">.1</span></version></span><br><span class="line"></dependency></span><br><span class="line"></span><br><span class="line"><dependency></span><br><span class="line"> <groupId>org.apache.httpcomponents.client5</groupId></span><br><span class="line"> <artifactId>httpclient5</artifactId></span><br><span class="line"> <version><span class="number">5.4</span>-alpha2</version></span><br><span class="line"></dependency></span><br><span class="line"><dependency></span><br><span class="line"> <groupId>org.apache.httpcomponents.core5</groupId></span><br><span class="line"> <artifactId>httpcore5</artifactId></span><br><span class="line"> <version><span class="number">5.3</span>-alpha2</version></span><br><span class="line"></dependency></span><br><span class="line"><dependency></span><br><span class="line"> <groupId>org.apache.httpcomponents.core5</groupId></span><br><span class="line"> <artifactId>httpcore5-h2</artifactId></span><br><span class="line"> <version><span class="number">5.3</span>-alpha2</version></span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure>
<p>以及以下依赖:</p>
<h3 id="具体如何更好地适配"><a href="#具体如何更好地适配" class="headerlink" title="具体如何更好地适配"></a>具体如何更好地适配</h3><p>1.可以参考 <a href="https://dzone.com/articles/relearning-java-thread-primitives">https://dzone.com/articles/relearning-java-thread-primitives </a>,更优雅地使用Lock进行替换<br>2.对于ConcurrentHashMap这类使用ReentrantLock替换需要付出代价(需要在Node上加lock属性,会导致内存膨胀)的场景,可以考虑使用ConcurrentSkipListMap替换(性能略差,但是是无锁的线程安全HashMap实现)<br>3.由于JFR的event并不包含持有Monitor的帧( <a href="https://bugs.openjdk.org/browse/JDK-8314591">https://bugs.openjdk.org/browse/JDK-8314591 </a>),因此上线前的排查阶段使用 -Djdk.tracePinnedThreads排查比较方便</p>
<h3 id="排查时需要关注的场景"><a href="#排查时需要关注的场景" class="headerlink" title="排查时需要关注的场景"></a>排查时需要关注的场景</h3><p>1.sync块内外出现了同一资源的竞争,典型的例如上面的sync块内外出现了对同一lock的抢占,这种可能造成死锁</p>
<p>2.sync里面有park但是无内外资源竞争的,这种只会影响性能</p>
<p>3.sync里面没有park操作,这种情况无竞争的情况下无影响,有竞争的情况下会导致性能变差(通过JFR事件jdk.JavaMonitorEnter来观察)</p>
<p>4.其他绑定问题(Native或FFI代码,例如类加载场景)</p>
<h1 id="TIPS"><a href="#TIPS" class="headerlink" title="TIPS"></a>TIPS</h1><p><a href="https://www.javaperformancetuning.com/news/newtips274.shtml">https://www.javaperformancetuning.com/news/newtips274.shtml</a></p>
<p>虚拟线程不再需要池化,虚拟线程是非常轻量的,随用随建就行。</p>
<p>但是线程池 <strong>部分思想依然有效</strong>,例如使用线程池的思想控 <strong>制访问资源的并发量</strong>。</p>
<p>这这种场景线程池内部可能用Semaphore简单地控制并发量,而不需要有复杂的线程管理逻辑</p>
<p>也就是说虚拟线程池的目的将不再包含资源的复用,而应该只包含一些更轻量的附加控制</p>
<h1 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h1><h2 id="线程转储"><a href="#线程转储" class="headerlink" title="线程转储"></a>线程转储</h2><p>在jdk21中使用jstack -l [pid] > [file]是拿不到虚拟线程的</p>
<p>使用idea中的线程转储也获取不到虚拟线程</p>
<p>原因:虚拟线程数量非常多,原本扁平化的数据结构无法很好地组织线程之间的结构关系,引入结构化并发后更是如此</p>
<p>方案:</p>
<p>提供json结构来输出包含虚拟线程的堆栈转储,而不会在原来的线程转储输出中包含虚拟线程</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">jcmd <pid> Thread.dump_to_file -format=json <file></span><br><span class="line"></span><br><span class="line">新版idea也对此提供了一定支持</span><br></pre></td></tr></table></figure>
<p>参考</p>
<p><a href="https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-8AEDDBE6-F783-4D77-8786-AC5A79F517C0">Virtual Threads: An Adoption Guide</a></p>
<p><a href="https://blog.rockthejvm.com/ultimate-guide-to-java-virtual-threads/">The Ultimate Guide to Java Virtual Threads</a></p>
]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>JDK21</tag>
<tag>JVM</tag>
<tag>Virtual Threads</tag>
<tag>Loom</tag>
<tag>JFR</tag>
</tags>
</entry>
<entry>
<title>JFR实战教程</title>
<url>/2024/09/22/2024-09-22-jfr-practice/</url>
<content><![CDATA[<p><strong>本文基于JDK11,对于jdk14的 <a href="https://openjdk.java.net/jeps/349">JFR Event Streaming </a>以及JDK16引入的 <a href="https://bugs.openjdk.org/browse/JDK-8257602">jdk.ObjectAllocationSample </a>事件等不作过多讨论</strong></p>
<h1 id="什么是JFR"><a href="#什么是JFR" class="headerlink" title="什么是JFR"></a>什么是JFR</h1><p>JFR是 Java Flight Record(前身是JRocket Flight Record),是 JVM 内置的基于事件的JDK监控记录框架。这个起名就是参考了黑匣子对于飞机的作用,将Java进程比喻成飞机飞行。顾名思义,这个记录主要用于问题定位和持续监控。</p>
<span id="more"></span>
<p>官方描述 <a href="https://openjdk.org/jeps/328">https://openjdk.org/jeps/328</a></p>
<p>JFR基于事件思想的起源 <a href="https://openjdk.org/jeps/167">https://openjdk.org/jeps/167</a></p>
<p><a href="https://bestsolution-at.github.io/jfr-doc/openjdk-11.html">JFR事件大全</a></p>
<p>JFR是一个开销不超过1%(对比profiler官方声称对系统影响不超过2%,实测远远不止,因此不推荐在线上系统长期跑profiler),目标是用于线上持续监控的特性,在jdk11以前是一个商业特性,在2018年 Java Flight Recorder 开源并作为 OpenJDK 11 的一部分发布。</p>
<p>jdk8u262后jfr被反向移植</p>
<p><a href="https://mail.openjdk.org/pipermail/jdk8u-dev/2020-July/012143.html">https://mail.openjdk.org/pipermail/jdk8u-dev/2020-July/012143.html</a></p>
<p><a href="https://bugs.openjdk.org/browse/JDK-8223147">https://bugs.openjdk.org/browse/JDK-8223147</a></p>
<p><a href="https://hg.openjdk.org/jdk8u/jdk8u-jfr-incubator/">https://hg.openjdk.org/jdk8u/jdk8u-jfr-incubator/</a></p>
<p>这意味着在jdk8u262以后的版本也可以自由使用jfr了。</p>
<p>当然,在某些供应商的open jdk8中可能更早被免费开放,比如Azul的openjdk。</p>
<p>JFR,具有以下关键的特性: - 低开销(在配置正确的情况下),可在生产环境核心业务进程中始终在线运行。当然,也可以随时开启与关闭。</p>
<p>- 可以查看出问题时间段内进行分析,可以分析 Java 应用程序,JVM 内部以及当前Java进程运行环境等多因素。</p>
<p>- JFR基于事件采集,可以分析非常底层的信息,例如对象分配,方法采样与热点方法定位与调用堆栈,安全点分析与锁占用时长与堆栈分析,GC 相关分析以及 JIT 编译器相关分析(例如 CodeCache )</p>
<p>- 完善的 API 定义,用户可以自定义事件生产与消费。</p>
<h2 id="为什么要使用JFR"><a href="#为什么要使用JFR" class="headerlink" title="为什么要使用JFR"></a>为什么要使用JFR</h2><p><strong>存在的问题</strong></p>
<ol>
<li><p>目前通过 <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jmx/index.html">JMX </a>对jvm的监控粒度比较粗,线上出现非业务逻辑问题后一般只能通过dump堆快照结合日志来进行分析,对jvm的详细把控只能通过dump来获得瞬时的状态进行分析(且dump操作需要拉出后再进行,可能dump到的数据并不准确)。</p>
</li>
<li><p>从业务层面看,目前业务线的代码业务逻辑比较复杂,且部分老代码存在可读性低的情况,常常难以直观感知到性能瓶颈。</p>
</li>
</ol>
<ol start="3">
<li><p>从组件框架层面看,公司内部以及业务线的组件封装层次比较深,并且日常工作忙于需求开发,因此日常接触的层次主要集中在业务代码层面,导致难以定位到框架或组件代码中可能存在的性能瓶颈。</p>
</li>
<li><p>就业务研发团队与组件研发团队结合而言,组件开发人员感知集中在组件代码层面,容易忽视一些业务场景下的调用特征,可能导致一些业务场景独有的瓶颈。而业务开发人员的感知集中在业务层面,且由于业务复杂,框架封装层次深,难以直观感知到组件层面导致的瓶颈。导致二者难以形成整体的感知,只能各自为政。</p>
</li>
</ol>
<ol start="5">
<li>目前对应用的优化以代码重构与架构重构为主,但重构代价大,周期长,往往难以推进。因此有时候通过消除代码层面的瓶颈来提升应用性能也是一个不错的选择。</li>
</ol>
<p><strong>使用JFR的好处</strong></p>
<ol>
<li><p>定位问题直达本质。jfr的思想是万物皆为event,这些event可以是jvm的事件,例如进入safepoint,stw,锁争用,内存分配,线程上下文切换等,也可以是自定义事件。这些event直接反映了应用在jvm和操作系统等层面的根本开销。并且jfr对各类事件进行聚合,通过火焰图等方式让开发者能够从jvm和操作系统层面反向定位到代码层面,极大程度上消除了代码水平逻辑复杂,调用深度复杂导致性能与开销瓶颈定位困难的问题,在推动jdk升级的背景下,对JVM有更全面的监控是十分必要的。</p>
</li>
<li><p>JFR监控开销很低。根据官方描述可以做到将开销控制在1%,是完全可以接受的。一些常用的监控手段,比如jstack和jmap,需要让JVM进入safepoint状态才能获取线程堆栈和内存使用情况等信息。而这样的操作会导致JVM暂停应用程序的执行,从而降低线上应用的性能。 对于JMX等外部监控来说存在网络开销的问题。JFR通过在JVM内部进行数据采集,可以实现非常低的性能开销。</p>
</li>
</ol>
<ol start="3">
<li>使用JFR有助于形成对JVM更全面的认知。在日常开发中,JVM的情况往往是一个只暴露少数指标的黑盒,难以做到具体的感知。使用JFR有助于使用者形成对JVM更全面的认知。</li>
</ol>
<p><strong>JFR的使用场景</strong></p>
<ol>
<li><p>分析应用瓶颈。本地分析应用瓶颈可以通过jmeter模拟生产流量,或者在mirror中回放生产流量的方式来进行监控,这种方式下可以采用比较激进的jfc配置(下面会讲到),对应用进行更全面的事件采集。</p>
</li>
<li><p>线上性能监控、辅助问题定位。线上可以使用default.jfc或者自定义配置来进行低开销的事件采集。通过dump一段时间内的jfr记录来分析应用瓶颈和问题所在。</p>
</li>
</ol>
<h1 id="JFR历史"><a href="#JFR历史" class="headerlink" title="JFR历史"></a>JFR历史</h1><p>JFR 0.9 版本对应 JDK 7 和JDK 8:JDK 7u40 之后,实现了和 JRockit Flight Recorder 一样的功能,并添加了各项数据配置,用来打开或者关闭一些统计数据功能。而且,在 JDK 8u40 之后,可以在运行时灵活地打开关闭 JFR。<br>JFR 1.0 版本对应 JDK 9 和 JDK 10: 在这一版本之后,增加了 JFR 事件接口,用户可以生产或者消费某种事件。<br>JFR 2.0 版本对应 JDK 11,这一版本就是本文主要基于的版本。<br>JDK 14 推出了 <a href="https://openjdk.java.net/jeps/349">JFR Event Streaming </a>,让用户处理 JFR 事件更加灵活方便 。</p>
<h1 id="JDK相关"><a href="#JDK相关" class="headerlink" title="JDK相关"></a>JDK相关</h1><h3 id="JDK下载"><a href="#JDK下载" class="headerlink" title="JDK下载"></a>JDK下载</h3><p><a href="https://jdk.java.net/archive/">https://jdk.java.net/archive/</a></p>
<h3 id="JDK源码下载"><a href="#JDK源码下载" class="headerlink" title="JDK源码下载"></a>JDK源码下载</h3><p><a href="https://mirrors.omnios.org/openjdk/">https://mirrors.omnios.org/openjdk/</a></p>
<p><a href="https://github.com/openjdk/jdk/tags">https://github.com/openjdk/jdk/tags</a></p>
<h3 id="JFR源码"><a href="#JFR源码" class="headerlink" title="JFR源码"></a>JFR源码</h3><p><a href="https://github.com/AdoptOpenJDK/openjdk-jdk11/tree/master/src/jdk.jfr/share/classes/jdk/jfr">https://github.com/AdoptOpenJDK/openjdk-jdk11/tree/master/src/jdk.jfr/share/classes/jdk/jfr</a></p>
<h1 id="Tools"><a href="#Tools" class="headerlink" title="Tools"></a>Tools</h1><h2 id="JMC安装"><a href="#JMC安装" class="headerlink" title="JMC安装"></a>JMC安装</h2><p>JMC是用于分析JFR事件的开源工具,可以帮助我们更好地分析各类事件,JFR页面如下所示。</p>
<p><a href="https://github.com/openjdk/jmc">https://github.com/openjdk/jmc</a></p>
<p><img src="JFR%E5%AE%9E%E6%88%98%E6%95%99%E7%A8%8B-image2023-8-13_18-59-55.png"></p>
<h2 id="JMC源码"><a href="#JMC源码" class="headerlink" title="JMC源码"></a>JMC源码</h2><p><a href="https://github.com/openjdk/jmc">https://github.com/openjdk/jmc</a></p>
<h2 id="JMC-JFR-WIN"><a href="#JMC-JFR-WIN" class="headerlink" title="JMC-JFR(WIN)"></a>JMC-JFR(WIN)</h2><h3 id="idea使用JFR(不推荐)注意要点"><a href="#idea使用JFR(不推荐)注意要点" class="headerlink" title="idea使用JFR(不推荐)注意要点"></a>idea使用JFR(不推荐)注意要点</h3><p>idea自带的jfr可能存在各种问题,包括事件采集不全面,功能较少等, <strong>推荐使用JMC进行采集和分析</strong></p>
<p>并且JFR在低版本的idea中并未得到支持,并且在开始支持的较低版本可能存在各种问题(比如使用idea自带的JFR看不到内存分析等,虽然可能是由于jfc配置导致的),可以在此下载2023.1版本的idea。</p>
<p><a href="https://www.cnblogs.com/xxcbdhxx/archive/2023/03/30/17271151.html">https://www.cnblogs.com/xxcbdhxx/archive/2023/03/30/17271151.html</a></p>
<p>idea自带的JFR似乎无法分析由应用容器直接启动的应用(未做进一步确定,可以自己尝试),建议使用SpringBoot启动。</p>
<p>idea中c对jfr的cpu火焰图似乎还不错。</p>
<h2 id="JMC-JFR-MAC"><a href="#JMC-JFR-MAC" class="headerlink" title="JMC-JFR(MAC)"></a>JMC-JFR(MAC)</h2><h3 id="JMC安装到使用过程的问题"><a href="#JMC安装到使用过程的问题" class="headerlink" title="JMC安装到使用过程的问题"></a>JMC安装到使用过程的问题</h3><p>1.</p>
<p><img src="JFR%E5%AE%9E%E6%88%98%E6%95%99%E7%A8%8B-image2023-8-5_14-33-16.png"></p>
<p><strong>解决:</strong></p>
<p>电脑环境是Mac m2 ,macOS版本是Ventura 13.2.1。</p>
<p>mac默认使用/Library/JavaJavaVirtualMachines目录下的最新版本JDK,而系统原本最新JDK版本是11。</p>
<p>JDK在15以前只有Mac Linux Windows三种环境的版本,在15以后才有支持aarch64的版本,m系列芯片是aarch64(arm64)架构,因此报错。</p>
<p>2.</p>
<p><img src="JFR%E5%AE%9E%E6%88%98%E6%95%99%E7%A8%8B-image2023-8-5_14-35-55.png"></p>
<p><strong>解决:</strong></p>
<p>使用SpringBoot启动,使用JMC连接虚拟机时需要设置变量:</p>
<p>System . setProperty ( "java.rmi.server.hostname" , "Ip or DNS of the server" );</p>
<h2 id="JFR具体使用"><a href="#JFR具体使用" class="headerlink" title="JFR具体使用"></a>JFR具体使用</h2><h3 id="方式1-使用JVM参数启动"><a href="#方式1-使用JVM参数启动" class="headerlink" title="方式1 使用JVM参数启动"></a>方式1 使用JVM参数启动</h3><p>例如</p>
<p>-XX:StartFlightRecording=disk=true,dumponexit=true,name=profile_online,filename=/tmp/recording.jfr,maxsize=4096m,maxage=1d,settings=/path/to/custom.jfc,path-to-gc-roots=false</p>
<table>
<thead>
<tr>
<th>key</th>
<th>默认值</th>
<th>含义</th>
</tr>
</thead>
<tbody><tr>
<td>delay</td>
<td>0</td>
<td>延迟多久后启动 JFR 记录,支持带单位配置, 例如 delay=60s(秒), delay=20m(分钟), delay=1h(小时), delay=1d(天),不带单位就是秒, 0就是没有延迟直接开始记录。一般为了避免框架初始化等影响,我们会延迟 1 分钟开始记录(例如Spring cloud应用,可以看下日志中应用启动耗时,来决定下这个时间)。</td>
</tr>
<tr>
<td>disk</td>
<td>true</td>
<td>是否写入磁盘,这个就是上文提到的, global buffer 满了之后,是直接丢弃还是写入磁盘文件。</td>
</tr>
<tr>
<td>dumponexit</td>
<td>false</td>
<td>程序退出时,是否要dump出 .jfr文件</td>
</tr>
<tr>
<td>duration</td>
<td>0</td>
<td>JFR 记录持续时间,同样支持单位配置,不带单位就是秒,0代表不限制持续时间,一直记录。</td>
</tr>
<tr>
<td>filename</td>
<td></td>
<td>启动目录/hotspot-pid-26732-id-1-2020_03_12_10_07_22.jfr,pid 后面就是 pid, id 后面是第几个 JFR 记录,可以启动多个 JFR 记录。最后就是时间。 | dump的输出文件</td>
</tr>
<tr>
<td>name</td>
<td>无</td>
<td>记录名称,由于可以启动多个 JFR 记录,这个名称用于区分,否则只能看到一个记录 id,不好区分。</td>
</tr>
<tr>
<td>maxage</td>
<td>0</td>
<td>这个参数只有在 disk 为 true 的情况下才有效。最大文件记录保存时间,就是 global buffer 满了需要刷入本地临时目录下保存,这些文件最多保留多久的。也可以通过单位配置,没有单位就是秒,默认是0,就是不限制</td>
</tr>
<tr>
<td>maxsize</td>
<td>250MB</td>
<td>这个参数只有在 disk 为 true 的情况下才有效。最大文件大小,支持单位配置, 不带单位是字节,m或者M代表MB,g或者G代表GB。设置为0代表不限制大小**。虽然官网说默认就是0,但是实际用的时候,不设置会有提示**: No limit specified, using maxsize=250MB as default. 注意,这个配置不能小于后面将会提到的 maxchunksize 这个参数。</td>
</tr>
<tr>
<td>path-to-gc-roots</td>
<td>false</td>
<td>是否记录GC根节点到活动对象的路径,一般不打开这个,首先这个在我个人定位问题的时候,很难用到,只要你的编程习惯好。还有就是打开这个,性能损耗比较大,会导致FullGC一般是在怀疑有内存泄漏的时候热启动这种采集,并且通过产生对象堆栈无法定位的时候,动态打开即可。一般通过产生这个对象的堆栈就能定位,如果定位不到,怀疑有其他引用,例如 ThreadLocal 没有释放这样的,可以在 dump 的时候采集 gc roots</td>
</tr>
<tr>
<td>settings</td>
<td>default.jfc</td>
<td>这个位于 `$JAVA_HOME/lib/jfr/default.jfc`| 采集 Event 的详细配置,采集的每个 Event 都有自己的详细配置。另一个 JDK 自带的配置是 profile.jfc,位于 `$JAVA_HOME/lib/jfr/profile.jfc`。</td>
</tr>
</tbody></table>
<p><strong>其他配置</strong></p>
<p><strong>-XX:FlightRecorderOptions</strong></p>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>allow_threadbuffers_to_disk</td>
<td>false</td>
<td>是否允许 在 thread buffer 线程阻塞的时候,直接将 thread buffer 的内容写入文件。默认不启用,一般没必要开启这个参数,只要你设置的参数让 global buffer 大小合理不至于刷盘很慢,就行了</td>
</tr>
<tr>
<td>globalbuffersize</td>
<td>如果不设置,根据设置的 memorysize 自动计算得出</td>
<td>单个 global buffer 的大小,一般通过 memorysize 设置,不建议自己设置</td>
</tr>
<tr>
<td>maxchunksize</td>
<td>12M</td>
<td>存入磁盘的每个临时文件的大小。默认为12MB,不能小于1M。可以用单位配置,不带单位是字节,m或者M代表MB,g或者G代表GB。注意这个大小最好不要比 memorySize 小,更不能比 globalbuffersize 小,否则会导致性能下降</td>
</tr>
<tr>
<td>memorysize</td>
<td>10M</td>
<td>JFR的 global buffer 占用的整体内存大小,一般通过设置这个参数,numglobalbuffers 还有 globalbuffersize 会被自动计算出。可以用单位配置,不带单位是字节,m或者M代表MB,g或者G代表GB。</td>
</tr>
<tr>
<td>numglobalbuffers</td>
<td>如果不设置,根据设置的 memorysize 自动计算得出</td>
<td>global buffer的个数,一般通过 memorysize 设置,不建议自己设置</td>
</tr>
<tr>
<td>old-object-queue-size</td>
<td>256</td>
<td>对于Profiling中的 Old Object Sample 事件,记录多少个 Old Object,这个配置并不是越大越好。一般应用256就够,时间跨度大的,例如 maxage 保存了一周以上的,可以翻倍</td>
</tr>
<tr>
<td>repository</td>
<td>等同于 - <a href="http://djava.io/">Djava.io </a>.tmpdir 指定的目录</td>
<td>JFR 保存到磁盘的临时记录的位置</td>
</tr>
<tr>
<td>retransform</td>
<td>true</td>
<td>是否通过 JVMTI 转换 JFR 相关 Event 类,如果设置为 false,则只在 Event 类加载的时候添加相应的 Java Instrumentation,这个一般不用改,这点内存 metaspace 还是足够的</td>
</tr>
<tr>
<td>samplethreads</td>
<td>true</td>
<td>这个是是否开启线程采集的状态位配置,只有这个配置为 true,并且在 Event 配置中开启线程相关的采集(这个后面会提到),才会采集这些事件。</td>
</tr>
<tr>
<td>stackdepth</td>
<td>64</td>
<td>采集事件堆栈深度,有些 Event 会采集堆栈,这个堆栈采集的深度,统一由这个配置指定。注意这个值不能设置过大,如果你采集的 Event种类很多,堆栈深度大很影响性能。比如你用的是 default.jfc 配置的采集,堆栈深度64基本上就是不影响性能的极限了。你可以自定义采集某些事件,增加堆栈深度。</td>
</tr>
<tr>
<td>threadbuffersize</td>
<td>8KB</td>
<td>threadBuffer 大小,最好不要修改这个,如果增大,那么随着你的线程数增多,内存占用会增大。过小的话,刷入 global buffer 的次数就会变多。8KB 就是经验中最合适的。</td>
</tr>
</tbody></table>
<h3 id="方式2-使用jcmd"><a href="#方式2-使用jcmd" class="headerlink" title="方式2 使用jcmd"></a>方式2 使用jcmd</h3><p><strong>1.启动 JFR 记录</strong></p>
<p>jcmd <pid> JFR.start</p>
<p><strong>例如</strong>jcmd 85 JFR.start name=profile_online filename=/tmp/recording.jfr maxage=1d maxsize=2g settings=opt/app/WEB-INF/tars/fat/custom.jfc</p>
<p>参数 与上面通过JVM参数启动的方式一致,如果使用JVM参数开启了记录的话dump指定时间的记录就行</p>
<p><strong>2.dumpJFR记录</strong></p>
<p>jcmd <pid> JFR.dump</p>
<p><strong>例如</strong>jcmd 83 JFR.dump begin=16:06 end=16:08 name=profile_online filename=/tmp/profile- `hostname`-`date +%Y_%m_%d_%H_%M_%S`.jfr</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>默认值</th>
<th>描述</th>
</tr>
</thead>
<tbody><tr>
<td>name</td>
<td>无</td>
<td>指定要查看的 JFR 记录名称</td>
</tr>
<tr>
<td>filename</td>
<td>无</td>
<td>指定文件输出位置</td>
</tr>
<tr>
<td>maxage</td>
<td>0</td>
<td>dump最多的时间范围的文件,可以通过单位配置,没有单位就是秒,默认是0,就是不限制</td>
</tr>
<tr>
<td>maxsize</td>
<td>0</td>
<td>dump最大文件大小,支持单位配置, 不带单位是字节,m或者M代表MB,g或者G代表GB。设置为0代表不限制大小</td>
</tr>
<tr>
<td>begin</td>
<td>无</td>
<td>dump开始位置, 可以这么配置:09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h, or -1d</td>
</tr>
<tr>
<td>end</td>
<td>无</td>
<td>dump结束位置,可以这么配置: 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h, or -1d (STRING, no default value)</td>
</tr>
<tr>
<td>path-to-gc-roots</td>
<td>false</td>
<td>是否记录GC根节点到活动对象的路径,一般不记录,dump 的时候打开这个肯定会触发一次 fullGC,对线上应用有影响。最好参考之前对于 JFR 启动记录参数的这个参数的描述,考虑是否有必要</td>
</tr>
</tbody></table>
<p><strong>下载dump文件</strong></p>
<p><strong>【推荐】方式1:</strong></p>
<p>jcmd 72 JFR.dump begin=19:59 end=20:01 name=profile_online filename=/tmp/profile_ `hostname`-`date +%Y_%m_%d_%H_%M_%S`.jfr</p>
<p><strong>方式2:</strong></p>
<p>在跳板机切到sftp,下载指定jfr文件即可(dump文件建议dump到tmp目录,这样可以在sftp页面直接看到对应文件)</p>
<p><strong>3.停止JFR记录</strong></p>
<p>jcmd <pid> JFR.stop</p>
<p><strong>4.查看正在执行的JFR记录</strong></p>
<p>jcmd <pid> JFR.check</p>
<p><strong>5.查看或修改JFR配置参数</strong></p>
<p>jcmd <pid> JFR.configure</p>
<p>如果不传入参数,则是查看当前配置,传入参数就是修改配置。</p>
<p>更多jcmd使用可以查看 <a href="https://docs.oracle.com/javacomponents/jmc-5-5/jfr-runtime-guide/run.htm#JFRRT172">https://docs.oracle.com/javacomponents/jmc-5-5/jfr-runtime-guide/run.htm#JFRRT172</a></p>
<p>对于参数的介绍也可以查看redHat的文档 <a href="https://access.redhat.com/documentation/zh-cn/openjdk/11/html/using_jdk_flight_recorder_with_openjdk/index">https://access.redhat.com/documentation/zh-cn/openjdk/11/html/using_jdk_flight_recorder_with_openjdk/index</a></p>
<h3 id="方式3-使用JMC直接连接"><a href="#方式3-使用JMC直接连接" class="headerlink" title="方式3 使用JMC直接连接"></a>方式3 使用JMC直接连接</h3><p>本地分析时非常推荐使用这种方式,操作比较简单,就不展开了</p>
<h3 id="JFR自定义配置文件"><a href="#JFR自定义配置文件" class="headerlink" title="JFR自定义配置文件"></a>JFR自定义配置文件</h3><p><strong>建议关闭 OldObjectSample事件采集,它可能导致内存泄漏(如果需要开启的话建议在mirror上开启后接入流量观察几天,查看RSS使用率是否正常)</strong></p>
<p>JDK中自带了两份jfc配置文件</p>
<ul>
<li><p><code>default.jfc</code> : <em>default</em>配置文件是一种低开销配置,可以安全地在生产环境中连续使用。开销通常小于 1%。</p>
</li>
<li><p><code>profile.jfc</code> : <em>profile</em>配置文件是一种低开销配置,非常适合用于分析瓶颈。开销通常低于 2%(官方宣称)。</p>
</li>
</ul>
<p>对于profile.jfc,经过实测,性能开销不止官方宣称的2%, <strong>不适合用于生产环境长期开启</strong>。</p>
<p>JFR中有一些指标监控已经在JMX中存在,对于一些异常和ERROR的记录也能通过现有的log进行分析,因此不必重复记录。</p>
<p>另外对于一些事件的记录开销比较大,例如ObjectAllocationInNewTLAB和ObjectAllocationOutsideTLAB,也一些事件对应用问题的排查参考意义不大,因此需要调整或关闭这部分事件的采集,在需要采集时再动态开启。</p>
<p>此外有一部分事件的采集频率需要根据应用情况适当调整。</p>
<p>进行上述调整后得到了自定义配置文件(V1.0),在启动JFR监控时使用这份jfc配置用于线上长期采集。</p>
<p>这份配置文件采取了 <strong>比较保守</strong>的配置,可以根据自己的应用需求具体调整。</p>
<p><a href="https://jeyzer.org/resources/jfr/jeyzer.jfc">Jeyzer 的 jfc 配置参考</a></p>
<h3 id="JFR文件拆分"><a href="#JFR文件拆分" class="headerlink" title="JFR文件拆分"></a>JFR文件拆分</h3><p>我们可能一次性dump了比较多的记录,或者想要一次性dump一段比较长时间的记录进行分析,但是jmc对jfr记录的分析十分消耗cpu和内存,所以对于比较大的jfr记录我们需要对其进行拆分</p>
<p>我们可以使用jdk自带的命令来完成这个需求</p>
<p>官方文档 <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/jfr.html">The jfr Command</a></p>
<p>用法例如</p>
<p>/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/bin/jfr disassemble –max-chunks 1 –output ./ ./profile_data.jfr</p>
<p><img src="JFR%E5%AE%9E%E6%88%98%E6%95%99%E7%A8%8B-image2023-9-4_13-57-52.png"></p>
<h3 id="开启JFR对性能的影响测试-todo"><a href="#开启JFR对性能的影响测试-todo" class="headerlink" title="开启JFR对性能的影响测试(todo)"></a>开启JFR对性能的影响测试(todo)</h3><p>本测试的条件是在使用JVM参数开启JFR记录,已经平稳运行的应用上测试dump文件的影响、停止JFR的影响、以及开启JFR的影响。</p>
<p>主要通过查看执行JFR指令时cpu load和response time的抖动情况来评估</p>
<h4 id="一个缓存型服务"><a href="#一个缓存型服务" class="headerlink" title="一个缓存型服务"></a>一个缓存型服务</h4><p>一个缓存型服务是一个缓存应用,接口响应时间快、性能高,能承受比较大的并发。如果JFR会导致比较大的开销,比较容易能在响应时间上看出来。</p>
<p>对一个缓存型服务设置每分钟24k左右的单机流量请求,远高于线上的请求量,一方面JFR会记录到更多事件,放大JFR的开销,另一方面可以测试出在系统压力较大情况下JFR对系统的影响</p>
<p>测试期间的监控指标如下</p>
<p>1.dump测试</p>
<p>在23:25的时候dump了5分钟的数据,从监控可以看出来并没有导致系统出现明显的抖动</p>
<p>从原理角度也很好解释,因为JFR是持续记录的,我们dump文件时JFR只需要拉取一段时间内已有的记录给我们,而不需要在dump时再进行太多分析。</p>
<p>2.stop测试</p>
<p>在23:36时stop JFR,并未观察到明显抖动</p>
<p>3.start 测试</p>
<p>在23:55时开启了JFR,开启时也没有观察到系统明显的抖动,或是开启后性能的降低</p>
<h3 id="待完成部分"><a href="#待完成部分" class="headerlink" title="待完成部分"></a>待完成部分</h3><p><strong>Cryostat:容器化的JFR</strong></p>
<p><a href="https://cryostat.io/">Cryostat官网</a></p>
<p><a href="https://developers.redhat.com/articles/2021/11/09/automating-jdk-flight-recorder-containers">Automating JDK Flight Recorder in containers</a></p>
<p><a href="https://developers.redhat.com/articles/2021/10/18/announcing-cryostat-20-jdk-flight-recorder-containers">Announcing Cryostat 2.0: JDK Flight Recorder for containers</a></p>
<h3 id="推荐阅读"><a href="#推荐阅读" class="headerlink" title="推荐阅读"></a>推荐阅读</h3><p><a href="https://bestsolution-at.github.io/jfr-doc/openjdk-11.html">JFR事件大全</a></p>
<p><a href="https://access.redhat.com/documentation/en-us/red_hat_build_of_openjdk/11/html/using_jdk_flight_recorder_with_red_hat_build_of_openjdk/index">JFR教程-redhat-jdk11</a></p>
<p><a href="https://www.zhihu.com/column/c_1264859821121355776">JFR专题-hashcon</a></p>
<p><a href="https://www.bilibili.com/video/BV148411K742/?share_source=copy_web&vd_source=d30c780580db9da56e2e4f9fa6acf1a5">JFR编程与使用入门-video</a></p>
<p><a href="https://www.bilibili.com/video/BV13m4y1i7Nv/?share_source=copy_web&vd_source=d30c780580db9da56e2e4f9fa6acf1a5">JFR-JVM进程最全面低消耗持续线上监控-video</a></p>
<p><a href="https://stackoverflow.com/questions/64024144/how-to-configure-the-interval-of-a-custom-jfr-event">自定义事件</a></p>
<h3 id="拓展阅读"><a href="#拓展阅读" class="headerlink" title="拓展阅读"></a>拓展阅读</h3><p><a href="https://jeyzer.org/">Jeyzer:the incident analysis solution for Java</a></p>
<p><a href="https://jeyzer.org/resources/jfr/jeyzer.jfc">Jeyzer 的 jfc 配置参考</a></p>
<p><a href="https://jeyzer.org/jeyzer-and-jfr/">jeyzer and jfr</a></p>
<h3 id="其他阅读"><a href="#其他阅读" class="headerlink" title="其他阅读"></a>其他阅读</h3><p><a href="https://github.com/thegreystone/jmc-tutorial/tree/master/docs">JMC教程(有点过时)</a></p>
<p><a href="https://confluence.atlassian.com/enterprise/diagnosing-runtime-issues-using-the-java-flight-recorder-1127256408.html">参考</a></p>
<p><a href="https://www.morling.dev/blog/rest-api-monitoring-with-custom-jdk-flight-recorder-events/">自定义JFR事件</a></p>
<p><a href="https://www.reddit.com/r/java/comments/13n10ac/how_widely_is_jfr_java_flight_recorder_used/?onetap_auto=true">JFR讨论</a></p>
<p><a href="https://www.w3computing.com/articles/java-flight-recorder-advanced-profiling-diagnostics-techniques/">JFR使用</a></p>
<p><a href="https://foojay.io/today/continuous-production-profiling-and-diagnostics/">重点看看</a></p>
<p><a href="http://missioncontrol.mcnz.com/">JFR-JMC教程</a></p>
<p><a href="https://docs.newrelic.com/docs/apm/agents/java-agent/features/real-time-profiling-java-using-jfr-metrics/">new Relic集成JFR监控</a></p>
<p><a href="https://docs.datadoghq.com/profiler/enabling/java/?tab=datadogprofiler"><strong>data dog</strong></a></p>
<p><a href="https://github.com/cryostatio/jfr-datasource">grafana集成JFR</a></p>
<p><a href="https://access.redhat.com/documentation/en-us/red_hat_build_of_cryostat/2/html/using_cryostat_to_manage_a_jfr_recording/assembly_integrated-applications_assembly_event-templates">grafana集成JFR-redhat</a></p>
<p><a href="https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/toc.html">JVM监控指南(包括JFR)</a></p>
]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
<tag>JVM</tag>
<tag>JFR</tag>
<tag>JMC</tag>
<tag>Performance</tag>
</tags>
</entry>
<entry>
<title>JDK21-25重要特性解读</title>
<url>/2024/10/05/2024-10-05-jdk21-25-features/</url>
<content><![CDATA[<h1 id="Loom(虚拟线程)"><a href="#Loom(虚拟线程)" class="headerlink" title="Loom(虚拟线程)"></a>Loom(虚拟线程)</h1><table>
<thead>
<tr>
<th>Release</th>