介绍
我不得不承认我很震惊。 确实,当我意识到这个出现的日历帖子将涉及垃圾收集时,我感到非常震惊。 GC的主题引起了Java倡导者和那些认为内存管理应该是手动的人的热情。 撰写了许多文章,内容涉及看起来奇怪的命令行参数中的细微变化,这些变化对Java应用程序的性能影响很小。 我该如何增加这项庞大的身体工作?
我希望这篇文章不会增加GC的热度,而是让您呼吸新鲜空气。 让我们不要看垃圾回收器消耗的CPU时间或暂停时间; 如何看待内存管理的一个隐藏但潜在的关键方面,尤其是垃圾回收:数据缓存是现代计算机软件设计的主要挑战之一(其他是指令缓存和多核工作分配)。
现代CPU的运行速度如此之快,以至于主内存无法跟上。 可以减轻这种灾难性性能损失中的一部分的方式。 内存被并行拉入高速缓存,然后CPU访问该缓存。 如果我们很幸运,并且代码使CPU几次读取和写入相同的内存(例如在循环内),则CPU可以愉快地访问高速缓存,并且可以免于等待加载和存储到主内存或从主内存中存储。
人们可能会问: “垃圾回收如何影响缓存的性能 ?” 有很多方法,其中有些非常微妙,但是,这里有一些重要的方法:
垃圾收集器遍历内存中的引用。 这导致高速缓存行(高速缓存中的内存块)包含引用周围的内存,因此不再保存程序正在使用的其他数据。
虽然我们将其称为垃圾收集器,但实际上它是分配器,移动器和收集器。 当我们考虑数据缓存时,这确实很重要:
- 分配:根据硬件规则,内存地址与高速缓存行匹配。 如果内存共享一条高速缓存行,但实际上是从不同的线程访问的,我们会得到一种称为假共享的效果。 但是,如果少量数据散布但从同一线程访问,则缓存利用率很低。
- 移动:对象在整个生命周期中都不会留在一处。 垃圾收集器通过移动对象来避免内存碎片。 这具有有趣的效果,可以保证与对象关联的高速缓存行在移动后不再与之关联。
- 收集:有趣的是,收集是一件容易的事。 它可以很简单,只需将内存标记为可重复使用即可。 对象图(多个根)的遍历是为了找出可以收集的内容,这将导致数据高速缓存行负载,从而从用户代码读取或写入的数据中删除高速缓存行。
因此,我们现在可以看到垃圾收集器的设计对于数据缓存的运行至关重要。 交换我们使用的收集器不仅会影响GC暂停和其他明显的问题,而且还会从根本上影响所有用户代码。
一个例子
我不会就此概念发表详尽的科学论文。 这篇文章的目的是展示一种进行JVM调优的替代方法。 因此,我在个人合成器程序Sonic-Field中运行了一个简单,简短,多线程的补丁程序。 该补丁使用反馈,共鸣和其他一些概念来合成弦乐器,然后进行卷积以将声音放置在声学环境中。
选择音场的原因不仅是因为它具有合理的复杂性,高度线程化并使用Spring,还因为我最近发现我可以使用CMS垃圾收集器从中获得更好的性能。 与Sonic-Field的延迟无关紧要,因为它是批处理处理器。 但是,当内存不足时,标准Java 7垃圾收集器与Sonic Field将交换文件写出到磁盘的方式交互不良。 我之所以尝试CMS,是因为它使内存一直处于关闭状态(理论上,不要让我失望),因为它一直在尝试在用户线程旁边进行小的垃圾回收。
如果将所有这些放在一起,我们很可能会提出一个合理的理论
“ CMS垃圾收集器可能会减少暂停时间,也许能够减少内存使用量,但这样做几乎肯定会导致更多数据高速缓存未命中” 。 不断遍历内存中的参考图以尝试收集死对象将导致高速缓存加载,而这些加载将导致从高速缓存中清除其他数据(其大小有限)。 因此,当用户线程再次读取时,它们将导致更多的高速缓存未命中,依此类推。
有关系吗? 答案将完全取决于应用程序,硬件以及应用程序的负载。 我不是,我不再重复,提倡一个垃圾收集器而不是另一个! 尽管如此,这是我想回答的一个问题,所以让我们为我的小测试补丁回答它。
垃圾收集器的这些数据缓存效果在普通的VM分析工具中是不可见的。 这意味着它们在JVM社区中没有得到太多讨论,并且在JVM调优中得到的考虑甚至更少。 但是,有一个工具(实际上是几个,但我将讨论最简单的使用方法)可以使您对该主题有所了解。 我说的是英特尔的PCM(性能计数器监视器)。 它也可以用于代码调整,但我认为今天谈论GC会更有趣。
一个可行的例子
pcm只是命令行工具。 我们通过命令行将引号引起来运行Java并对其进行测量。 通过其他工具,性能计数器可用于获取有关应用程序的各种其他详细信息。 pcm命令行工具的优点在于它的简单性,并且不会干扰整个应用程序的运行。 缺点是它将衡量JVM和应用程序预热阶段。 但是,对于服务器样式的应用程序或批处理处理器(例如Sonic Field),与实际应用程序运行相比,这些开销通常是微不足道的。
我在具有16Gig RAM的个人Macbook Pro Retina(2012)上运行了补丁。 JVM是:
java version "1.8.0-ea"
Java(TM) SE Runtime Environment (build 1.8.0-ea-b61)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b05, mixed mode)
当应用程序退出时,从pcm读取的数据将被简单地写入标准输出。 我将运行时没有设置垃圾收集器的设置(因此是默认设置)与当前首选的调整进行了比较。 老实说,我不确定这些调整是否最佳; 我从一系列在线文章中解脱了他们……
这是Java的启动脚本:
/Users/alexanderturner/x/IntelPerformanceCounterMonitorV2.5.1\ 2/pcm.x "java \
-Xmx12G -Xms12G -DsonicFieldTemp=/Users/alexanderturner/temp -DsonicFieldThreads=12 -DsonicFieldSwapLimit=4.0 -XX:+UseConcMarkSweepGC -XX:+UseCompressedOops -XX:ParallelGCThreads=8 -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=60 -XX:+UseCMSInitiatingOccupancyOnly \
-classpath \
bin:\
ing-framework-3.1.2.RELEASE/dist/org.springframework.asm-3.1.2.RELEASE.jar:\
spring/sp
spring/sp
rring-framework-3.1.2.RELEASE/dist/org.springframework.beans-3.1.2.RELEASE.jar:\
ing-framework-3.1.2.RELEASE/dist/org.springframework.core-3.1.2.RELEASE.jar:\
spring/sprin
spring/spring-framework-3.1.2.RELEASE/dist/org.springframework.context-3.1.2.RELEASE.jar:\
spring/spring-framework-3.1.2.RELEASE/dist/org.springframework.context-support-3.1.2.RELEASE.jar:\
spring/sp
rg-framework-3.1.2.RELEASE/dist/org.springframework.expression-3.1.2.RELEASE.jar:\
spring/spring-framework-3.1.2.RELEASE/dist/org.springframework.test-3.1.2.RELEASE.jar:\
spring/otherJars/commons-logging-1.1.1.jar \
com.nerdscentral.sfpl.RenderRunner $1
"
希望可以清楚地知道,在IntelPerformanceCounterMonitorV2下运行Java到底有多简单。 因此,这是输出:
标准GC
Core (SKT) | EXEC | IPC | FREQ | AFREQ | L3MISS | L2MISS | L3HIT | L2HIT | L3CLK | L2CLK | READ | WRITE | TEMP
0 0 0.53 0.75 0.70 1.31 422 M 621 M 0.32 0.32 0.14 0.01 N/A N/A 32
2 0 0.56 0.77 0.73 1.31 346 M 466 M 0.26 0.31 0.11 0.01 N/A N/A 28
1 0 0.22 0.69 0.32 1.31 144 M 192 M 0.25 0.28 0.11 0.01 N/A N/A 32
3 0 0.21 0.68 0.31 1.31 135 M 171 M 0.21 0.28 0.10 0.01 N/A N/A 28
4 0 0.55 0.77 0.71 1.31 332 M 410 M 0.19 0.38 0.11 0.01 N/A N/A 22
7 0 0.18 0.68 0.26 1.30 124 M 134 M 0.08 0.30 0.11 0.00 N/A N/A 27
5 0 0.19 0.68 0.29 1.31 133 M 155 M 0.14 0.30 0.11 0.00 N/A N/A 22
6 0 0.61 0.79 0.78 1.32 343 M 382 M 0.10 0.35 0.10 0.00 N/A N/A 27
-------------------------------------------------------------------------------------------------------------------
SKT 0 0.38 0.75 0.51 1.31 1982 M 2533 M 0.22 0.33 0.11 0.01 N/A N/A 22
Instructions retired: 2366 G ; Active cycles: 3166 G ; Time (TSC): 773 Gticks ; C0 (active,non-halted) core resi
-------------------------------------------------------------------------------------------------------------------
TOTAL * 0.38 0.75 0.51 1.31 1982 M 2533 M 0.22 0.33 0.11 0.01 N/A N/A N/A
dency: 39.04 %
C : 1.49 => corresponds to 37.36 % utilization for cores in active state
Instructions per no
C1 core residency: 23.92 %; C3 core residency: 0.01 %; C6 core residency: 0.00 %; C7 core residency: 37.02 %
C2 package residency: 0.00 %; C3 package residency: 0.00 %; C6 package residency: 0.00 %; C7 package residency: 0.00 %
PHYSICAL CORE I
Pminal CPU cycle: 0.76 => corresponds to 19.12 % core utilization over time interval
并发标记扫描
而不是
-XX:+UseConcMarkSweepGC
-XX:+UseCompressedOops
-XX:+CMSParallelRemark
-XX:ParallelGCThreads=
8Enabled
-XX:CMSInitiatingOccupancyFraction=60
-XX:+UseCMSInitiatingOccupancyOnly
Core (SKT) | EXEC | IPC | FREQ | AFREQ | L3MISS | L2MISS | L3HIT | L2HIT | L3CLK | L2CLK | READ | WRITE | TEMP
0 0 0.53 0.69 0.76 1.31 511 M 781 M 0.35 0.35 0.17 0.02 N/A N/A 26
2 0 0.54 0.71 0.75 1.31 418 M 586 M 0.29 0.40 0.14 0.01 N/A N/A 29
1 0 0.31 0.66 0.47 1.30 207 M 285 M 0.27 0.26 0.11 0.01 N/A N/A 26
3 0 0.30 0.66 0.46 1.30 198 M 258 M 0.23 0.27 0.11 0.01 N/A N/A 29
4 0 0.59 0.73 0.81 1.31 397 M 504 M 0.21 0.46 0.12 0.01 N/A N/A 29
7 0 0.30 0.66 0.45 1.30 186 M 204 M 0.09 0.29 0.11 0.00 N/A N/A 30
5 0 0.30 0.66 0.45 1.30 188 M 225 M 0.16 0.28 0.11 0.01 N/A N/A 29
6 0 0.58 0.73 0.79 1.31 414 M 466 M 0.11 0.49 0.13 0.00 N/A N/A 30
-------------------------------------------------------------------------------------------------------------------
SKT 0 0.43 0.70 0.62 1.31 2523 M 3313 M 0.24 0.38 0.13 0.01 N/A N/A 25
Instructions retired: 2438 G ; Active cycles: 3501 G ; Time (TSC): 708 Gticks ; C0 (active,non-halted) core resi
-------------------------------------------------------------------------------------------------------------------
TOTAL * 0.43 0.70 0.62 1.31 2523 M 3313 M 0.24 0.38 0.13 0.01 N/A N/A N/A
dency: 47.22 %
C : 1.39 => corresponds to 34.83 % utilization for cores in active state
Instructions per no
C1 core residency: 17.84 %; C3 core residency: 0.01 %; C6 core residency: 0.01 %; C7 core residency: 34.92 %
C2 package residency: 0.00 %; C3 package residency: 0.00 %; C6 package residency: 0.00 %; C7 package residency: 0.00 %
PHYSICAL CORE I
Pminal CPU cycle: 0.86 => corresponds to 21.51 % core utilization over time interval
这里提供的所有信息都很有趣,但是我想做的最好的事情就是减少案例并测试我对CMS收集器的主张。 为此,我们只需要看两行即可得出每次运行的输出:
Default:
SKT 0 0.38 0.75 0.51 1.31 1982 M 2533 M 0.22 0.33 0.11 0.01 N/A N/A 22
Instructions retired: 2366 G ; Active cycles: 3166 G ; Time (TSC): 773 Gticks ; C0 (active,non-halted) core residency: 39.04 %
CMS:
0 0.43 0.70 0.62 1.31 2523 M 3313 M 0.24 0.38 0.13 0.01 N/A N/A 25
SKT
Instructions retired: 2438 G ; Active cycles: 3501 G ; Time (TSC): 708 Gticks ; C0 (active,non-halted) core residency: 47.22 %
讨论区
我们可以看到,在CMS收集器下,缓存未命中率大大提高。 与默认收集器相比,L2遗漏增加了30%,L2遗漏了27%。 但是,以十亿分s为单位的总时间(708CMS / 773默认)表明,所有这些额外的数据丢失并没有对所有性能产生负面影响。 我想这意味着在就此应用程序的正确方法得出任何结论之前,可以而且应该做更多的研究!
如果您以我未充分讨论该主题为由而离开,则您是正确的。 我的目的是使读者对Java性能的这一方面感兴趣并为新方法打开大门。