Java虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为新生代
、老年代
和永久代
(对HotSpot虚拟机而言),这就是JVM的内存分代策略
一、jvm分代
jdk7
分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。
试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,因为可能进行了很多次遍历,但是他们依旧存在。因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。
虚拟机中的共划分为三个代:新生代(Young)
、老年代(Old)
和持久代(Permanent)
。
新生代
所有新生成的对象首先都是放在新生代(Young)
的。新生代(Young)
的目标就是尽可能快速的收集掉那些生命周期短的对象。
新生代分三个区。一个Eden
区,两个Survivor
区(一般而言)。大部分对象在Eden
区中生成。当Eden
区满时,还存活的对象将被复制到Survivor(From)
区,当Survivor(From)
区满时,此区的存活对象将被复制到另外一个Survivor(To)
区,当这个Survivor(To)
去也满了的时候,从Survivor(From)
区复制过来的并且此时还存活的对象,将被复制老年代(Old)
。
需要注意,Survivor(From)
和Survivor(To)
的两个区是对称的,没先后关系。
老年代
在新生代(Young)
中经历了N次垃圾回收后仍然存活的对象,就会被放到老年代(Old)
中。因此,可以认为老年代(Old)
中存放的都是一些生命周期较长的对象。
持久代
用于存放静态文件,如今Java类、方法等。持久代(Permanent)
对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。
jdk8永久代的废弃
随着JDK8的到来,JVM不再有PermGen
。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做Metaspace
的本地内存(Native memory)中。
类的元数据信息转移到Metaspace
的原因是PermGen
很难调整。PermGen
中类的元数据信息在每次FullGC的时候可能会被收集,但成绩很难令人满意。而且应该为PermGen
分配多大的空间很难确定,因为PermSize
的大小依赖于很多因素,比如JVM加载的class的总数,常量池的大小,方法的大小等。元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
常用配置参数
MetaspaceSize
初始化的Metaspace大小,控制元空间发生GC的阈值。GC后,动态增加或降低MetaspaceSize
。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。使用Java -XX:+PrintFlagsInitial
命令查看本机的初始化参数
MaxMetaspaceSize
限制Metaspace
增长的上限,防止因为某些情况导致Metaspace
无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。
MinMetaspaceFreeRatio
当进行过Metaspace GC
之后,会计算当前Metaspace
的空闲空间比,如果空闲比小于这个参数(即实际非空闲占比过大,内存不够用),那么虚拟机将增长Metaspace
的大小。默认值为40,也就是40%。设置该参数可以控制Metaspace
的增长的速度,太小的值会导致Metaspace
增长的缓慢,Metaspace
的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace
增长的过快,浪费内存。
MaxMetasaceFreeRatio
当进行过Metaspace GC
之后,会计算当前Metaspace
的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace
的部分空间。默认值为70,也就是70%。
MaxMetaspaceExpansion
Metaspace
增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB)。
MinMetaspaceExpansion
Metaspace
增长时的最小幅度。在本机上该参数的默认值为340784B(大约330KB为)。
二、GC类型
Minor GC:指发生在新生代的GC,因为新生代的Java对象大多都是朝生夕死,所以Minor GC
非常频繁,一般回收速度也比较快。当Eden
空间不足以为对象分配内存时,会触发Minor GC
。
Full GC:对整个堆进行整理,包括新生代、老年代和持久带。Full GC
因为需要对整个对进行回收,所以比Minor GC
慢10倍以上,因此应该尽可能减少Full GC
的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC
的调节。有如下原因可能导致Full GC
:
- 老年代被写满
- 持久代(Perm)被写满
- System.gc()被显示调用
- 上一次GC之后Heap的各域分配策略动态变化
三、Java中垃圾回收器
垃圾回收的基本步骤
- 查找内存中不再使用的对象
- 释放这些对象占用的内存
串行回收器(Serial Garbage Collector)
串行回收器是最简单的一个,你都不会考虑使用它,因为它主要是面向单线程环境的(比如说32位的或者Windows)以及比较小的堆。这个回收器工作的时候会将所有应用线程全部冻结
,就这一点而言就使得它完全不可能会被服务端应用所采用。
开启方式
-XX:+UseSerialGC
并行回收器(Parallel Garbage Collector)
并行回收器( Parallel collector)。这是JVM的默认回收器。正如它的名字所说的那样,它的最大的优点就是它使用多个线程来扫描及压缩堆。与串行垃圾回收器不同,它使用多线程进行垃圾回收。相似的是,它也会冻结
所有的应用程序线程,并行回收器最适合那些可以容许暂停的应用,它试图减少由回收器所引起的CPU开销。
开启方式
-XX:+UseParallelGC -XX:+UseParallelOldGC
CMS(Concurrent Mark Sweep)
并发标记垃圾回收(CMS)使用多线程扫描堆内存,标记需要清理的实例并且清理被标记过的实例。并发标记垃圾回收器只会在下面两种情况持有应用程序所有线程。
- 当标记的引用对象在tenured区域;
- 在进行垃圾回收的时候,堆内存的数据被并发的改变。
并发标记扫描垃圾回收器特别适用于对系统响应时间要求较高的系统中,如页面请求/web服务器,适用于前段业务系统,最小化系统停顿时间,用于老年代的GC。
这个算法的另一个缺点就是和并行回收器相比,它使用的CPU资源会更多,它使用了多个线程来执行扫描和回收,这样才能让应用持续提供更高级别的吞吐量。对于大多数长期运行的程序而言,应用的暂停对它们是很不利的,这个时候可以考虑使用CMS回收器。尽管如此,这个算法也不是默认开启的。你得指定XX:+UseConcMarkSweepGC
来启用它。假设你的堆小于4G,而你又希望分配更多的CPU资源以避免应用暂停,那么这就是你要选择的回收器。然而,如果堆大于4G的话,你可能更希望使用最后的这个——G1回收器。
开启方式
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
G1(并发)回收器
G1收集器(或者垃圾优先收集器)的设计初衷是为了尽量缩短处理超大堆(大于4GB)时产生的停顿。相对于CMS的优势而言是内存碎片的产生率大大降低。
G1( Garbage first)回收器在JDK 7update 4中首次引入,它的设计目标是能更好地支持大于4GB的堆。G1回收器将堆分为多个区域,大小从1MB到32MB不等,并使用多个后台线程来扫描它们。G1回收器会优先扫描那些包含垃圾最多的区域,这正是它的名字的由来(Garbage first)。
这一策略减少了后台线程还未扫描完无用对象前堆就已经用光的可能性,而那种情况回收器就必须得暂停应用。G1的另一个好处就是它总是会进行堆的压缩,而CMS回收器只有在full GC
的时候才会干这事。
开启方式
-XX:+UseG1GC
开发人员仅仅需要声明以下参数即可
-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200
其中-XX:+UseG1GC
为开启G1垃圾收集器,-Xmx32g
设计堆内存的最大内存为32G,-XX:MaxGCPauseMillis=200
设置GC的最大暂停时间为200ms。如果我们需要调优,在内存大小一定的情况下,我们只需要修改最大暂停时间即可。
四、常见JVM参数配置汇总
参数 | 描述 |
---|---|
-Xms | 初始堆大小。如:-Xms2g |
-Xmx | 最大堆大小。如:-Xmx2g |
-Xmn | 新生代大小。通常为 Xmx 的 1/3 或 1/4。 |
-Xss | 每个线程所占内存大小,一般来说如果栈不是很深的话, 1M 是绝对够用了的 |
-XX:NewRatio | 新生代与老年代的比例,如 –XX:NewRatio=2,则新生代:老年代=1:2,即新生代占整个堆空间的1/3,老年代占2/3 |
-XX:SurvivorRatio | 新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10 |
-XX:MetaspaceSize | 设置元空间的初始大小 |
-XX:MaxMetaspaceSize | 设置元空间的最大值,默认是没有上限的,也就是说你的系统内存上限是多少它就是多少 |
-XX:+PrintGCDetails | 打印 GC 信息 |
-XX:+HeapDumpOnOutOfMemoryError | 让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析用,文件目录设置-XX:HeapDumpPath=${目录} |
-XX:-UseBiasedLocking | 取消偏向锁 |
-XX:+TieredCompilation | 启用分层编译,java8默认开启 |
-XX:ErrorFile | 当不可恢复的错误发生时,错误信息记录到哪个文件。默认是在当前目录的一个叫做hs_err_pid pid.log的文件。如果指定的目录没有写权限,这时候文件会创建到/tmp目录下 ,例如:/path/to/hs_err_pid.log |
-XX:+UseSerialGC | 使用串行收集器 |
-XX:+UseParallelGC | 使用并行收集器 |
-XX:ParallelGCThreads | 配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等 |
-XX:+UseParallelOldGC | 采用对于老年代并发收集的策略,可以提高收集效率 |
-XX:+UseAdaptiveSizePolicy | 设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开 |
-XX:+UseConcMarkSweepGC | 使用CMS收集器,为了稳健,还是8G以下的堆还是CMS好了,G1的细节实现起来难度太大 |
-XX:+UseParNewGC | 设置年轻代为多线程收集,CMS默认开启 |
-XX:+DisableExplicitGC | 禁止 java 程序中的 full gc, 如 System.gc() 的调用。 最好加上防止程序在代码里误用了,对性能造成冲击 |
-XX:CMSInitiatingOccupancyFraction | CMSInitiatingOccupancyFraction = 75, 意味着第一次CMS垃圾收集会在老年代被占用75%时被触发,与 -XX:+UseCMSInitiatingOccupancyOnly:两个参数需要配合使用,否则第一个参数的75只是一个参考值 |
-XX:MaxTenuringThreshold | 用于控制对象能经历多少次Minor GC才晋升到旧生代,默认值是15 |
-XX:+UseG1GC | 启用G1收集器 |