[TOC]html
垃圾收集 (Garbage Collection) 机制是Java语言的一大优点特性, 为充分榨取JVM性能, 避免系统因垃圾收集不及时致使的OOM (OutOfMemory, 内存溢出)问题, 或内存饱和出现没法响应用户请求的状况, 就须要根据服务器配置及应用复杂度对GC策略进行优化, 以确保系统正常运行.java
JVM根据运行于其中的对象的生存时间, 将它们分为3种, 并分别存放在JVM的不一样内存区域中. 这种对象存放空间的管理方式叫作Generation管理方式.算法
(1) Young Generation (新生代, 又称年轻代): 用于存放"早逝"对象(即瞬时对象), 通常的Java应用中, 80%的对象都是"朝生息灭"的, 好比在建立对象或调用方法时使用的临时对象或局部变量.shell
(2) Tenured Generation (老年代): 用于存放"驻留"对象(即被引用较长时间的对象). 每每体现为一个大型程序中的全局对象或长时间被使用的对象.服务器
(3) Perm Generation (永久代): 用于存放"永久"对象. 这些对象管理着运行于JVM中的类和方法.并发
又叫Minor GC(次收集), Young GC常常发生, 且其每次消耗的时间较短.app
—— 它只对Young Generation中的对象进行垃圾收集.jvm
触发条件:分布式
在Young Generation(新生代)的Eden区的空间不足以容纳新生成的对象时执行, 同时会将 Eden 区与 From Survivor 区中尚且存活的对象移动至空闲的 To Survivor 区中.性能
—— 程序运行过程当中, 始终有一个 Survivor 区是彻底处于空闲状态的, 若是不是, 说明应用程序出现故障了.
又叫Major GC(主收集), 是对整个Java Heap中的对象(不包括永久代/元空间)进行垃圾收集.
Full GC操做耗时久, 对系统的性能影响较大, 所以在 JVM 的调优中, 不少工做是针对 Full GC 的调优 —— 要尽量减小Full GC的频率.
Full GC 是一种"昂贵"的垃圾收集方式, 它要对整个Heap进行垃圾收集, 并作必定的空间整理, 这会使Stop-The-World的时间变长.
Full GC的触发条件:
(1) 年老代(Tenured)空间不足:
- 经过Minor GC后进入老年代的对象的体积大于老年代的可用空间;
- 由Eden块、From Space块向To Space复制存活对象时, 它们的体积大于To Space的大小, 系统就会把这些对象转存到老年代, 而老年代的可用空间小于这些对象的体积.
(2)
System.gc()
方法被显式调用, 系统建议执行Full GC, 但并不会当即执行 —— 很是影响程序性能, 建议禁止使用;(3) 上一次GC以后Heap中各个区域空间的动态变化.
(1) JVM会试图为相关Java对象在年轻代的Eden区中初始化一块内存区域;
(2) 当Eden区空间足够时, 内存申请结束. 不然执行下一步;
(3) JVM 试图释放在Eden区中全部不活跃的对象(即 出发Young GC), 释放后若Eden空间仍然不足以放入新对象时, JVM会试图将部分Eden区中活跃的对象迁移至Survivor区;
(4) Survivor区被用来做为Eden区及老年代的中间交换区域, 当老年代空间足够时, Survivor区中存活了必定次数的对象会被迁移到老年代;
(5) 当年老代空间不够时, JVM会在老年代进行彻底的垃圾回收(Full GC);
(6) Full GC后, 若Survivor区及老年代仍然没法存放从Eden区复制过来的对象, 则会致使JVM没法在 Eden区为新生成的对象申请内存, 即出现 "Out of Memory".
OOM(Out of Memory)异常通常主要有以下2种缘由:
(1) 老年代溢出, 表现为:
java.lang.OutOfMemoryError:Javaheapspace
, 这是最多见的状况, 产生的缘由多是: 设置的内存参数-Xmx太小或程序的内存泄露及使用不当问题.(2) 持久代溢出,表现为:
java.lang.OutOfMemoryError:PermGenspace
, 一般因为持久代设置太小, 动态加载了大量 Java 类而致使溢出, 解决办法惟有将参数-XX:MaxPermSize
调大(通常256MB能知足绝大多数应用程序需求).
只有一条GC线程, 运行时须要暂停用户程序(Stop-The-World).
实现: Serial(用于新生代, 采用复制算法)、serial old(用于老年代, 采用标记-整理算法).
有多条GC线程, 运行时也须要暂停用户程序(Stop-The-World).
实现: ParNew(用于新生代, 采用复制算法)、Parallel Scavenge(用于新生代, 采用复制算法)、Parallel Old(用于老年代, 采用标记-整理算法).
有一条或多条GC线程, 且须要在部分阶段暂停用户程序(Stop-The-World), 部分阶段与用户程序并发执行.
实现: Concurrent Mark Sweep(CMS, 用于老年代, 采用标记-清除算法).
并发(concurrent)与并行(parallel)的比较:
(1) 并发就是两个任务(A和B)须要独立运行, 在任务A结束以前, 任务B开始执行 —— 即表面上多个任务同时执行. (2) 并行, 类比串行, 是微观概念, 即在每个时刻都有多个任务在同时执行, 形象点理解为多管齐下, 串行可理解为单列队列, 同一时刻只能执行一个任务. (3) 事实上, 并行是并发的一种实现方式, 还有一种并发的实现方式, 即咱们熟悉的时间片切换 —— 任务A执行一段时间, CPU再切换到任务B执行一段时间, 如此交替执行. 时间片切换在微观上仍然是串行 —— 某一具体时刻只有一个任务在执行, 而在宏观上, 即一段时间内, 有多个任务获得了执行. (4) 总结: 并行必须在多核多处理器或分布式系统(本质仍是多核多处理器)中才能发生, 而单核处理器上只能发生时间片切换.
G1垃圾回收器在 Oracle JDK 7 开始提供完整支持, 它是 server 型的 GC, 主要针对多核处理器和大内存的服务器, 可以以很高的几率 知足开发人员对停顿时间的要求, 同时还能保证高吞吐量.
(1) 与CMS收集器相比, G1收集器的优点:
① 基于标记-整理算法, 不会产生大量的内存碎片; ② 能够更加精确地控制停顿时间, 在不牺牲吞吐量前提下, 实现低停顿垃圾回收.
(2) G1收集器的实现原理:
G1收集器可以避免全区域的垃圾收集, 它把堆内存划分为大小固定的几个独立区域, 并跟踪这些区域的垃圾收集进度, 同时在后台维护一个优先级列表, 每次根据所容许的收集时间, 优先回收垃圾最多的区域. —— **区域划分和优先级区域回收机制, 确保G1收集器能够在有限的时间内得到最高的垃圾收集效率. **
G1的长期目标是取代CMS (Concurrent Mark-Sweep Collector) 并发标记-清除收集器.
(1) 为了更大程度地榨取机器性能, 新生代的收集器都使用了复制算法, 老年代的收集器都使用 标记-清除 或 标记-整理 算法. 关于各算法详情, 请参阅: JVM内存管理———垃圾搜集器参数精解.
(2) 在实际应用中, 须要对JVM的新生代、老年代分别选择合适的垃圾收集器.
(3) 这里新生代和老年代都分别有三种实现, 但因为收集器的实现方式不一样, 部分组合没法一块儿配合工做, 通过验证, 这六种垃圾收集器只有六种可用组合.
全部 JVM 都必须支持这些参数的功能, 并且向后兼容, 如:
-client : 设置 JVM 使用 client 模式, 特色是启动速度比较快, 但运行时性能和内存管理效率不高. 一般用于客户端应用程序或开发调试; 在32位环境下直接运行 Java 程序默认启用该模式.
-server : 设置 JVM 使 server 模式, 特色是启动速度比较慢, 但运行时性能和内存管理效率很高, 适用于生产环境; 在具备64位能力的JDK环境下默认启用该模式.
各 JVM 厂商应该所有实现这些参数的功能, 但并不能保证这些厂商提供的 JVM 中都实现了这些功能, 且不保证向后兼容;
此类参数在各个 JVM 的实现中会有所不一样, 未来也可能不被支持, 要慎重使用.
注意: 在"-XX:"后的参数若不须要赋值, 即只是用来配置开启或关闭相应选项, 则须要有 **"+" (开启) 或 "-" (禁止) **, 不然应用程序将在日志文件 (如 Tomcat 的日志文件 catalina.out ) 中抛出以下错误:
Missing +/- setting for VM option 'UseConcMarkSweepGC'. Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit.
能够看出, 这里缺乏了 "+/-" 符号, 致使虚拟机启动异常, 修改后便可正常启动.
-XX:+UseSerialGC # 使用 Serial & Serial Old 串行收集器(JDK 5之前的主要收集方式), 是client模式的默认设置.
-XX:+UseParNewGC # 使用ParNew & Serial Old收集器, 即对新生代使用并行收集, 提升收集效率(缩短Young GC的时间), 不推荐. -XX:+UseParallelGC # 使用Parallel Scavenge & Parallel Old并行收集器, 吞吐量优先, 会消耗更多内存, 是server模式的默认设置. -XX:+UseParallelOldGC # 对老年代使用Parallel Old并行收集器(JDK 6开始支持). -XX:ParallelGCThreads=20 # 配置并行收集器的线程数, 即并行执行垃圾收集任务的线程个数. # 此值最好与CPU处理器的个数相同(默认即相同). -XX:GCTimeRatio=49 # 设置系统用做GC的时间比例, 如49, 则GC时间比为 1/(1+49) = 2%, 即Java用2%的时间来作垃圾收集. # 若是此值设置过大, 即GC时间太少致使GC没法完成, JVM会压缩新生代的大小以适应此配置. -XX:MaxGCPauseMillis=100 # 设置每次新生代垃圾收集的最长毫秒值, 若是时间久而新生代的大小不足以支撑到此时间, # JVM会自动调整新生代的大小以知足此值. 若仍然没法知足, 则会调整GCTimeRatio. -XX:+UseAdaptiveSizePolicy # 使并行收集器自动设定Eden区的大小与相应的Survivor区的比例, # 以达到目标系统规定的最低响应时间或收集频率等. 建议在使用并行收集器时始终开启此选项.
-XX:+UseConcMarkSweepGC # JDK 5开始提供支持, 以响应时间优先--即缩短Full GC的时间. # JVM会根据系统配置自行设置使用ParNewGC & CMS(Serial Old做为替补)收集器. # 建议在Heap Size较大且Full GC时间较长, 对响应时间的需求大于对吞吐量的需求, 可以承受垃圾回收线程和应用线程共享CPU资源等状况下使用. -XX:+UseCMSCompactAtFullCollection # CMS是不会移动内存的, 此参数设置在每次Full GC后对老年代空间进行压缩整理, 会影响性能, 可是能减小内存碎片. -XX:CMSInitiatingOccupancyFraction=70 # 触发CMS收集器的内存占用阈值, 默认为90%: 当老年代内存空间使用率达到90%时, 就开始对老年代进行CMS并发垃圾收集. # 这个参数设置不当, 将发生promotion failed(晋升失败). -XX:CMSFullGCsBeforeCompaction=10 # 因为并发收集器不对内存空间进行压缩整理, 因此运行一段时间后会产生"碎片", 使得运行效率下降. 此配置项用来设置在几回GC后触发一次内存整理. # 此配置项即将被移除(JDK 8已不建议使用).
-XX:+UseG1GC # 使用G1收集器 -XX:MaxGCPauseMillis=200 # 设置回收器的最大停顿毫秒值, 这是一个几率目标, JVM将尽最大努力去实现它. -XX:G1ReservePercent=15 # 设置堆的临时上限, 以防止因堆扩大失败而致使的异常. 默认值是10. -XX:G1HeapRegionSize=16 # 使用 G1 的 Java 堆细分为大小相等的区域(Region), 此选项是设置单个区域的大小, # 默认值是基于堆大小的一种人体工效划分, 最小值是1Mb, 最大值是32Mb. # 人体工效: 根据平台相关的默认选择和根据需求动态垃圾回收的行为统称为人体工效, # 人体工效的做用就是能够经过少许的命令行选项就可让JVM提供最合适的性能.
-Xnoclassgc # 禁用类垃圾收集, 能提升性能. -XX:MaxHeapFreeRatio=70 # GC事后堆的最大空闲空间比例, 避免过渡压缩 -XX:MinHeapFreeRatio=40 # GC事后堆的最小空闲空间比例, 避免过分膨胀 -XX:MaxTenuringThreshold=5 # 晋升老年代的最大年龄, 默认为15: 新生代对象在15次Minor GC后将被转移至老年代. -- 必须在0-15之间. # 若是设置为0, 等同于去掉了新生代的空间, 新生代对象将会越过Survivor区直接进入老年代, 很快就会占满老年代发生Full GC. # 同时, 这在老年代对象较多的应用中却能够提升效率. # 若是将此值调大, 则新生代对象会在Survivor区进行屡次复制, 即增长了对象在新生代的存活时间. -XX:PretenureSizeThreshold=10 # 晋升老年代的对象的大小, 默认为0. 好比设为10M, 则超过10M的对象将越过Eden区, 直接存入老年代. -XX:+UseThreadPriorities # 启用本地线程优先级API, 使java.lang.Thread.setPriority()生效, 不启用则无效 -XX:+DisableExplicitGC # 禁用写在程序中的System.gc(), 即禁止开发人员调用gc()方法影响性能. -XX:+ExplicitGCInvokesConcurrent # 配置System.gc()能够和应用程序一块儿并发执行. # System.gc()用来回收不用的内存, 此方法只是"建议"JVM回收内存, 不能强制回收, 具体回收时机由JVM决定. -XX:TargetSurvivorRatio=90 # 容许90%的Survivor区被占用(JVM默认为50%), 提升对于Survivor区的使用率 -XX:SoftRefLRUPolicyMSPerMB=1 # Soft Reference(弱引用)在虚拟机中比在客户机中存活的时间更长, 其清除频率可用此命令来控制. # 此命令用来指定每MB堆空间中Soft Reference存活的秒数, 默认值为1000毫秒: 对象的最后一个强引用被收集以后, 存活1秒钟. # 这是个近似值: Soft Reference只会在垃圾收集时才会被清除, 而垃圾收集并不老是发生. 可改成0, 客户机中不使用就当即清除.
(1) 在内存调优中, 内存设置越大, 处理请求的效率也就越高, 但同时垃圾收集所须要的时间也就越长, 在垃圾收集期间的处理效率确定会受影响, 所以须要做出相应的权衡.
(2) 关于 CMS 收集器的 Promotion Failed 和 Concurrent Mode Failure :
Promotion Failed
, 晋升失败: 发生Young GC时, Eden区存活的对象 和 Eden区的From块中存活的对象, 二者的体积超过了Eden区中To块的大小, Young GC的悲观策略将使这些存活的对象都迁移到老年代, 而此时老年代的大小不足以容纳这些对象, 从而发生promotion failed , 程序将暂停很长时间.
Concurrent Mode Failure
, 并发修改失败: 在执行GC的过程当中, 刚好有对象要晋升至老年代, 而此时老年代的空间不足, 从而形成Concurrent Mode Failure
.
这两种状况均可能触发Full GC. 要注意不要一次性占用太多的内存, 或对JVM的配置进行必定的优化.
CMSInitiatingOccupancyFraction
的设置技巧: 参考: CMSInitiatingOccupancyFraction计算公式
(Xmx-Xmn) * (1 - CMSInitiatingOccupancyFraction/100) >= (Xmn - Xmn/(SurvivorRatior+2))
进而推断出:
CMSInitiatingOccupancyFraction <= ( (Xmx-Xmn) - (Xmn - Xmn/(SurvivorRatior+2) ) ) / (Xmx-Xmn) * 100
参考资料
版权声明
做者: 马瘦风
出处: 博客园 马瘦风的博客
您的支持是对博主的极大鼓励, 感谢您的阅读.
本文版权归博主全部, 欢迎转载, 但请保留此段声明, 并在文章页面明显位置给出原文连接, 不然博主保留追究相关人员法律责任的权利.