JVM垃圾回收和参数说明

178 阅读9分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

本文介绍Java虚拟机运行时数据区分布图、JVM堆栈区域说明、JVM参数速查表、垃圾回收策略、垃圾回收器CMS(Concurrent Mark-Sweep)简单介绍

Java虚拟机运行时数据区分布图

image.png

  • JVM栈(Java Virtual Machine Stacks):Java中一个线程就会相应有一个线程栈与之对应,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈,因此栈存储的信息都是跟当前线程(或程序)相关信息的,包括局部变量、程序运行状态、方法返回值、方法出口等等。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 堆(Heap): 堆是所有线程共享的,主要是存放对象实例和数组。处于物理上不连续的内存空间,只要逻辑连续即可 方法区(Method Area): 属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 常量池(Runtime Constant Pool): 它是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。 本地方法栈(Native Method Stacks):就是使用非Java语言实现的方法,但是通常我们指的一般为C或者C++,因此这个栈也有着C栈这一称号。

JVM堆栈区域说明

Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象,如下图所示: image.png

在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、S0、S1。 这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。

新生成的对象首先放到年轻代Eden区,当Eden空间满了,触发Minor GC,存活下来的对象移动到Survivor0区,Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。经过多次Minor GC仍然存活的对象移动到老年代。

老年代存储长期存活的对象,占满时会触发Major GC=Full GC,GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。

Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:Minor GC、Full GC ( 或称为 Major GC )。 image.png

JVM参数速查表

参数说明
-Xms1024m程序启动时占用内存大小。默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制
-Xmx1024m程序运行期间最大可占用的内存大小。默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
-Xss256k每个线程分配的内存大小
-Xmn1g设置年轻代大小,一般设置为Xmx的2/8~3/8,等同于-XX:NewSize 和 -XX:MaxNewSize
-XX:NewSize=512m新生代初始内存的大小,应该小于-Xms的值
-XX:MaxNewSize=512m新生代可被分配的内存的最大上限;当然这个值应该小于-Xmx的值
-XX:SurvivorRatio=8Eden区与Survivor区的大小比值。两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
-XX:NewRatio=2新生代与老年代比值,参数值为2表示,新生代:老年代=1:2
-XX:PreternureSizeThreshold直接晋升老年代的对象大小,设置了这个参数后,大于这个参数的对象直接在老年代进行分配
-XX:MaxTenuringThreshold晋升老年代的对象年龄,对象在每一次Minor GC后年龄增加一岁,超过这个值后进入到老年代。默认值为15
-XX:MetaspaceSize=128m元数据空间,本质跟永久代类似,都是对JVM规范中方法区的实现。从JDK8开始取代永久代(PermGen)。区别是Metaspace使用的是本地内存,主要是控制metaspaceGC发生的初始阈值。Metaspace扩容时触发FullGC的初始化阈值
-XX:MaxMetaspaceSize默认基本是无穷大,建议大家设置这个参数,因为很可能会因为没有限制而导致metaspace被无止境使用(一般是内存泄漏)而被OS Kill。MaxMetaspaceSize并不会在jvm启动的时候分配一块这么大的内存出来
-XX:PermSize=64MJVM初始分配的永久代内存大小,默认64M
-XX:MaxPermSize=128MJVM最大允许分配的永久代内存大小
-XX:+ScavengeBeforeFullGC指导垃圾回收机制在进行Full GC和CMS remark phase之前收集年轻态。其结果是提升性能,因为在年轻态和老态之间不存在检查引用。
-XX:SoftRefLRUPolicyMSPerMB设置每兆堆空闲空间中SoftReference的存活时间,默认值是1s。动态生成的一些类会放入到Metaspace里面去,它们的Class对象都是软引用(SoftReference),软引用对象到底在GC的时候要不要回收,通过公式判断(clock - timestamp <= freespace * SoftRefLRUPolicyMSPerMB)。如:JVM中空闲的内存空间有3000MB,SoftRefLRUPolicyMSPerMB的默认值是1000毫秒,那么软引用的Class对象,可以存活3000 * 1000 = 3000秒,就是50分钟左右
-XX:+ExplicitGCInvokesConcurrent也是触发full gc,只不过在CMS在full gc效率比较高
-XX:+UseCompressedOopsOOPS是指“ordinary object pointers“。64bit的JVM出现后,OOPS的尺寸也变成了64bit。打开后,OOPS变成了32bit,最大能引用的空间是32GB。
-XX:+UseCompressedClassPointerjava8在UseCompressedOops之外,额外增加的一个新选项。class信息中的指针也用32bit的Compressed版本。而这些指针指向的空间被称作“Compressed Class Space”。默认大小是1G,但可以通过“CompressedClassSpaceSize”调整
-XX:+OmitStackTraceInFastThrow禁止大量的相同的异常输出将不打印堆栈。 当大量抛出同样的异常的后,后面的异常输出将不打印堆栈,打印堆栈的时候底层会调用到Throwable.getOurStackTrace()方法,而这个方法是synchronized的,对性能有比较明显对影响
-XX:+DisableExplicitGC关闭System.gc()
-XX:+PrintGC输出GC日志
-XX:+PrintGCDetails输出GC的详细日志
-XX:+PrintGCTimeStamps输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps输出GC的时间戳(以日期的形式,如 2018-06-19T21:53:59.230+0800)
-XX:+PrintHeapAtGC在进行GC的前后打印出堆的信息
Xloggc:…/logs/gc.log日志文件的输出路径
-XX:+HeapDumpOnOutOfMemoryError当JVM发生OOM时,自动生成DUMP文件
-XX:HeapDumpPath=生成DUMP文件的路径。默认为:java_<pid>_<date>_<time>_heapDump.hprof

垃圾回收策略

参数说明
-XX:+UseSerialGC它是一个最基本历史悠久的单线程收集器,一旦回收器开始运行时,整个系统都要停止。新生代和老年代都使用串行回收器,新生代使用复制算法,老年代使用标记-整理算法。Client模式下默认开启,其他模式默认关闭。
-XX:+UseParNewGCParNew收集器是Serial收集器的多线程版本。新生代进行并行回收,老年代仍旧使用串行回收。新生代S区仍然使用复制算法。
-XX:+UseParallelGC新生代使用Parallel收集器,老年代使用串行收集器。它的目的是达到一个可以控制的吞吐量。吞吐量(CPU消耗时间)=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。控制吞吐量的参数有:-XX:MaxGCPauseMillis、-XX:GCTimeRatio、-XX:UseAdaptiveSizePolicy
-XX:+UseParallelOldGC新生代和老年代都使用并行收集器
-XX:+UseConcMarkSweepGCConcurrent Mark Sweep并发标记清除。它和应用程序线程一起执行,减少JVM停顿,吞吐量会降低,确定是对CPU资源比较敏感。它是老年代的收集算法,新生代使用ParNew收集算法。

垃圾回收策略调优

参数说明
-XX:+UseCMSCompactAtFullCollectionFull GC后,进行一次碎片整理,整理过程是独占的,会引起停顿时间变长。仅在使用CMS收集器时生效
-XX:ParallelCMSThreads设置并行GC时进行内存回收的线程数量
–XX:CMSInitiatingOccupancyFraction为了更早的执行垃圾回收,指定老年代使用占比阈值,如果老年代增加不是很快,可以调高此值降低CMS次数,默认值为68
–XX:CMSInitiatingPermOccupancyFraction永久代的使用率达到阈值,默认值为92。前提是开启CMSClassUnloadingEnabled
-XX:+CMSParallelRemarkEnabled开启并行标记,减少第二次暂停的时间
-XX:+CMSScavengeBeforeRemark强制remark之前开始一次minor gc,减少remark的暂停时间,但是在remark之后也将立即开始又一次minor gc
-XX:+CMSPermGenSweepingEnabled这个参数表示是否会清理持久代。默认是不清理的,因此我们需要明确设置这个参数来调试持久代内存溢出问题。这个参数在Java6中被移除了,因此你需要使用 -XX:+CMSClassUnloadingEnabled 如果你是使用Java6或者后面更高的版本
-XX:+CMSClassUnloadingEnabled这个参数表示在使用CMS垃圾回收机制的时候是否启用类卸载功能(清理持久代,移除不再使用的classes),默认不开启。这个参数只有在UseConcMarkSweepGC也启用的情况下才有用
-XX:MaxGCPauseMillis控制垃圾回收最大停顿时间。-XX:+UseParallelGC开启时有效
-XX:GCTimeRatio控制垃圾回收时间占比,参数值是0-100,如果参数设置为19,代表最大GC时间占总时间的5%(1/(1+19))。-XX:+UseParallelGC开启时有效
-XX:UseAdaptiveSizePolicy自适应调节策略
-XX:+UseCMSInitiatingOccupancyOnly关闭CMS动态检查机制自动执行垃圾回收。指定HotSpot VM总是使用-XX:CMSInitiatingOccupancyFraction的值作为old的空间使用率限制来启动CMS垃圾回收
-XX:CMSFullGCsBeforeCompaction=9用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的

垃圾回收器

1. CMS(Concurrent Mark-Sweep)并发标记清除垃圾

CMS(Concurrent Mark-Sweep)并发标记清除垃圾回收器是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。CMS是用于对tenured generation的回收,也就是年老代的回收,目标是尽量减少应用的暂停时间,减少full gc发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代。

特点

  • CMS回收器采用的基础算法是Mark-Sweep。所有CMS不会整理、压缩堆空间而产生碎片。节约了停顿时间,而带来了堆空间浪费。
  • 需要更多的CPU资源。为了让应用程序不停顿,CMS线程和应用程序线程并发执行,这样就需要有更多的CPU。
  • CMS的另一个缺点是它需要更大的堆空间。为了保证在CMS回 收完堆之前还有空间分配给正在运行的应用程序,必须预留一部分空间。CMS不会在老年代满的时候才开始收集,它会尝试更早的开始收集。

过程

  • 初始标记(STW initial mark):STW(Stop The Word)
  • 并发标记(Concurrent marking)
  • 并发预清理(Concurrent precleaning)
  • 重新标记(STW remark):STW(Stop The Word)
  • 并发清理(Concurrent sweeping)
  • 并发重置(Concurrent reset)

参考