JVM(和Spark)性能优化:使用Java Mission Control (7)

网友投稿 713 2022-05-30

Java垃圾回收器是一种“自适应的、分代的、停止-复制、标记-清扫”式的垃圾回收器。在基于分代的内存回收策略中,堆空间通常都被划分为3个代,年轻代,年老代(或者tenured代-终身代),永生代。在年轻代中又被划分为三个小的区域,分别为:Eden(伊甸)区,S0区(survivor 0),S1区(survivor 1),如下图所示:

其中,新的对象总被分配到年轻代中,当年轻代空间被填满时,这时需要执行一次垃圾回收,即执行 minor GC,回收不再被引用的对象,并同时提升幸存的对象其年龄,年轻代中的幸存对象都有年龄标识字段,一旦其达到一定的阈值,则仍然幸存的对象将被提升到年老代空间中。

年老代的空间用于存放长时间幸存的对象,即生命周期较长的对象,一旦年轻代空间的幸存对象达到一定的年龄阈值后,将被自动提升到年老代,当年老代空间被对象填满(达到限额)时,这时执行一次Major GC。相较于minor GC, Major GC的执行次数要比minor GC要少很多,同时,Major Gc 执行的时间较Minor Gc要长。因为其涉及到更多的对象扫描。这种分代的思想,也是基于在实践中,对于新分配的对象具有更短的生命周期,年老的对象具有更长的生命周期所作出的较佳的选择。

与此同时,Minor Gc 和 Major Gc 在执行垃圾收集时,采取的是stop the world (STW) ,即终止正在运行的线程,等GC执行完毕在恢复所有的线程。

JVM在Old区申请不到内存,会进行Full GC。对于永生代的内存,主要是用来存放元数据的相关信息,类及其方法的信息。当一个类不再使用时将会被回收,当执行Full GC时,将会扫描永生代内存,对其进行垃圾回收。

采用复制收集器(Copying)。

一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

采用标记-整理收集器(Mark-Compact)。

对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:

年老代(Tenured)被写满(达到限额)

永生代(Perm)被写满

System.gc()被显示调用

上一次GC之后,Heap的各域分配策略有变更

注:Minor GC=Young GC= Scavenge GC,Major GC≈Full GC,参见《Java性能优化权威指南》第4章和这个博客

Java G1 GC 是分区的(regionalized)、分代的,它把堆heap切分为多个大小相等的区块regions,这个区块的大小可以从1 MB到32 MB,但总数不会超过2048个区块。eden, survivor,和 old generations 是这些区块的逻辑集合logical sets且不是连续的。

看起来像这种样子:

下图是HotSpot的几种收集器(不含G1):

性能优化的三个指标:吞吐量、延迟、内存占用。

JVM垃圾收集三个基本原则:

Ø Minor GC最多原则

Ø GC内存最大化原则

Ø GC调优的3选2原则

Oracle官方建议,heap大于16GB,就用G1垃圾收集器!替换掉Parallel Collector(默认的)和CMS Collector的长时暂停“Stop The World”。【注:基本上确定了在Java 9中G1将是默认的GC了。】G1不要设置年轻代的大小-Xmn。

The Garbage First Garbage Collector (G1 GC) is the low-pause, server-style generational garbage collector

• G1 GC uses concurrent and parallel phases to achieve its target:pause time and to maintain good throughput

• When G1 GC determines that a garbage collection is necessary, it collects the regions with the least live data first (garbage first)

JVM的长时暂停应用程序,可能会导致网络ack超时或executor lost。可以尝试下如下的优化设置选项:

-XX:+UseG1GC

-XX:MaxGCPauseMillis=150~500

-XX:GCPauseIntervalMillis=200

-XX:ParallelGCThreads=8 + ((N - 8) * 5 / 8)

-XX:ConcGCThreads= {ParallelGCThreads} / 4

-XX:+ParallelRefProcEnabled

-XX:-ResizePLAB

-XX:+UnlockExperimentalVMOptions -XX:G1MaxNewSizePercent=75 -XX:G1NewSizePercent=3 //如果heap>100GB,则设为1

更多的G1细节参看官方文档:http://www.oracle.com/technetwork/articles/java/g1gc-1984535.html

注意,在Java 8中永生代PermGen已经被删除了,取而代之的是Metaspace用来保存类的元数据,它位于本地(native)内存,而不是堆上。PermSize 和 MaxPermSize选项相应也从JDK中删除了。但可以通过如下参数控制non-heap内存的最大值:

-XX:ReservedCodeCacheSize=100m

-XX:MaxMetaspaceSize=128m

-XX:CompressedClassSpaceSize=128m

Java 8中的G1,引入了一个强大的优化,字符串去重,因为String和它内部的char[]常常占用非常大的堆空间。G1就会识别出相同的字符串,并把指针指向同一个内部char[],从而避免相同字符串的多个副本在堆上。需要加上:

-XX:+UseStringDeduplication

Oracle官方也建议,当heap小于等于32GB时,也可以试试CMS垃圾收集器的优化配置:

-XX:+UseParNewGC

-XX:+UseConcMarkSweepGC

-XX:+UseIncrementalMode

-XX:+UseIncrementalPacing

-XX:CMSInitiatingOccupancyFraction=70 //如果程序中维持了一个很大的长命对象的缓存,则可以加大此值为90

-XX:+CMSParallelRemarkEnabled

-XX:+UseParallelGC

-XX:+UseParallelOldGC

-XX:ParallelGCThreads=20

这些参数选项需要根据具体的业务不断的试验并测量,直到找到最合适的数值。观察GC中的年轻代、年老代的大小随GC的变化,适当调整试验不同代的大小。有人说,无测量无改进。有了JMC/JFR,就变得容易多了。

GC日志样例1 ,Minor GC :

GC日志样例2,Full GC:

当前Java 8的JVM有700多个Final参数(另外还有大量实验性的参数)。性能优化是多轮迭代的过程,需要人工反复收集大量的数据一轮一轮地进行。最好每次只优化一个方面。通过以下推荐的GC日志数据,可以得到许多的信息:

Java堆大小的通用计算法则:

Java 7和8中的JVM变化较大,功能越来越强。未来的JVM(可能在Java 9中)将在模块化上有突破性进展,模块化有几年曲折历史了,相关的JEP都有很多个。

【2015.07.29增加】最值得期待的是下一代Hotspot JVM,叫Graal(http://openjdk.java.net/projects/graal/ ),是动态的编译器,增加了一种高度可扩展的中间表示IR,可以做更多高级和低级优化:

基于此,Oracle还开发了个多语言的解释器框架Truffle(https://wiki.openjdk.java.net/display/Graal/Truffle+FAQ+and+Guidelines ),许多语言基于此速度将会更快,例如JRuby,Jython,FastR,Clojure等。基于Truffle实现新的语言将会更加容易。

还有个引人注目的是新一代GC,Shenandoah(http://openjdk.java.net/jeps/189 ): An Ultra-Low-Pause-Time Garbage Collector,超低暂停时间的GC。

当然,老牌项目Jigsaw将会在Java 9 中实现(http://openjdk.java.net/projects/jigsaw/),模块化带来的好处之一是性能优化。因为模块的导入导出包是明确的,JVM可以做很多Whole-Program Optimization Techniques(https://www.voxxed.com/blog/2015/07/the-features-project-jigsaw-brings-to-java-9/ )。

另外,还值得期待的有:

Value Objects 值对象,这样能方便地支持元组、记录、基本类型集合了。

http://openjdk.java.net/jeps/169

Project Sumatra: Java on GPU 在JVM级别支持GPGPU并行计算!

http://openjdk.java.net/projects/sumatra/

Project Panama: Native Interconnect for Java (JNI 2.0)

http://mail.openjdk.java.net/pipermail/discuss/2014-March/003306.html

JVM(和Spark)性能优化:使用Java Mission Control (7)

JEP 197: Segmented Code Cache,把代码缓存根据不同的代码类型分为三个段,提高性能

http://openjdk.java.net/jeps/197

转载请注明出处:华为云博客 https://portal.hwclouds.com/blogs

Java spark JVM

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:《大数据技术丛书Flink原理、实战与性能优化》
下一篇:五光十色——理解曝光、光圈、快门速度、ISO
相关文章