写在前面

JDK 1.8,参照 1.8 相关文档收集整理;

这是我的移动小本本,持续记录中…



应用程序启动时

这些参数应该根据需要,在程序启动时指定:

  • 启用收集器
  • -XX:+UseSerialGC 明确启用串行收集器;
  • -XX:+UseParallelGC 明确启用并行收集器(默认启用并行压缩);
  • -XX:+UseParallelOldGC 启用并行收集器(关闭并行压缩);
  • -XX:+UseConcMarkSweepGC 启用 CMS 收集器;

此收集器用于希望更短的垃圾收集暂停并能够与垃圾收集共享处理器资源的应用程序。

  • -XX:+UseG1GC 启用 G1 收集器;

用于具有大内存的多处理计算机,在实现高吞吐量的同时,极有可能满足垃圾收集暂停时间目标。

使用并发收集器减少暂停时间(以处理器资源为代价),使用并行收集器增加多处理器硬件上的总体吞吐量,性能取决于堆的大小,应用程序维护的实时数据量以及可用处理器的数量和速度。

  • 在选择了并行收集器时,可调整的参数:
  • -XX:ParallelGCThreads=<N> 垃圾收集器线程的数量;
  • -XX:MaxGCPauseMillis=<N> 最大垃圾收集暂停时间;

默认情况下,没有最大暂停时间目标,如果指定了暂停时间目标,则会调整堆大小和其他与垃圾收集相关的参数,以使垃圾收集暂停时间短于指定的值。这些调整可能会导致垃圾收集器降低应用程序的总体吞吐量(GC 执行线程的 CPU 使用率影响系统吞吐量),并且所需的暂停时间目标不能总是得到满足。

  • -XX:GCTimeRatio=<N> 吞吐量,将垃圾收集时间与应用程序时间的比率设置为 1/(1+N)

通过花费在垃圾收集上的时间与花费在垃圾收集之外的时间来度量吞吐量目标。

  • -Xmx<N> 指定最大的堆内存占用

只有在满足了其他目标(最大垃圾收集暂停时间,吞吐量),才会最小化堆的大小。

  • -XX:YoungGenerationSizeIncrement=<Y> -XX:TenuredGenerationSizeIncrement=<T> 世代递增速率

在垃圾收集结束时,会更新统计信息,然后确定目标是否达到,并对世代大小进行必要的调整。在保留统计信息和调整世代大小方面,唯一的例外是会忽略显示垃圾回收( Ststem.gc()) ;默认情况下,世代以 20% 的增量递长,而以 5% 的增量减少;

  • -XX:AdaptiveSizeDecrementScaleFactor=<D> 世代缩小的百分比,如果增长增量为 X 百分比,则收缩的减少量为 X/D 百分比。

如果没有达到最大暂停时间目标,则一次只缩小一代的大小。如果两代的暂停时间都超过了目标,那么暂停时间较大的那代的大小将首先缩小。

如果没有达到吞吐量目标,那么这两代的大小都会增加。每一个都按其对总垃圾收集时间的贡献的比例增加。例如,如果年轻代的垃圾收集时间是总收集时间的25%,如果年轻代的全部增量是20%,那么年轻代将增加5%。

  • 在选择了G1收集器时,可调整的参数(更多的请参考后续的参考博文):
  • -XX:InitiatingHeapOccupancyPercent=<NN> 当整个 Java 堆的占用达到参数的值时,开始并发标记阶段,默认值是 45;
  • -XX:G1HeapRegionSize=n 设置 G1 区域的大小。该值为 2 的幂,范围为 1MB32MB,目标是基于最小的 Java 堆大小具有大约 2048 个区域;
  • -XX:MaxGCPauseMillis=200 为所需的最大暂停时间设置目标值,默认值为 200 毫秒;
  • -XX:ParallelGCThreads=n 设置 STW 工作线程的值。
  • -XX:ConcGCThreads=n 设置平行标记线程的数量,设置 n 为 ParallelGCThreads 的 1/4;
  • 功能禁用:
  • -XX:UseGCoverheadLimit 并行收集器或者CMS 收集器如果在垃圾回收上花费过多的时间,会抛出 OutOfMemoryError (如果在垃圾回收中花费了总时间的 98% 以上,却回收不到 2% 的堆,则抛出),该命令可以禁用此功能;
  • 控制打印信息:
  • -verbose:gc 使有关堆和垃圾收集的信息在每个收集处打印;
  • -XX:+PrintGCDetails 有关收集的其它信息被打印;
  • -XX:PrintGCTimeStamps 在每个收集开始时,添加一个时间戳。这对于查看垃圾收集频率很有用。
  • 分配堆空间:
  • -Xmx 指定保留空间的大小;
  • -Xms 初始时,需要提交给虚拟机的空间大小;

可以设置-Xms-Xmx相同的值。否则,JVM 将使用初始堆大小开始,然后将增大 Java 堆,直到找到堆使用和性能之间的平衡为止。

  • -XX:MinHeapFreeRatio=<minimum> 默认值 40, 用于某代可用空间百分比下降到 40 % 以下,则该代将拓展以维持 40% 的可用空间;
  • -XX:MaxHeapFreeRatio=<maximum> 默认值为 70 ,用于某代可用空间超过 70% ,则该代将收缩;
  • -XX:NewRatio=2 意味着年轻代和年老代的比率为 1 : 2;
  • -XX:NewSize server JVM 默认值 1310 M;
  • -XX:MaxNewSize 不限,该值代表年轻代的最大大小,将根据总堆的大小和 NewRation 参数值来计算;

在虚拟机初始化时,将保留堆的整个空间。-Xms 参数的值小于 -Xmx 的值,则 -Xmx 并非所有保留的空间都会立即提交给虚拟机,未提交的则称为虚拟空间。堆的不同部分(年轻代和年老代)可以根据需要增长到虚拟空间的极限。


面临优化时

GC 优化的核心思路在于:尽可能让对象在新生代中分配和回收,尽量避免过多对象进入老年代,导致对老年代频繁进行垃圾回收,同时给系统足够的内存减少新生代垃圾回收次数。<摘自推荐博文中的第一篇>



一些有趣的案例

【20?】: 频繁为大对象分配内存?
我曾在文件上传的一个接口(内部使用)中使用了 BufferedInputStream 来复用上传的文件流,具体使用详情请参考这篇文章( InputStream重用 ),当时还在为自己发现了这种方式而感到高兴,像发现新大陆般;这种方式其实是以底层的字节数组作为缓冲区的,毫无疑问在文件过大时,会导致分配较大的内存。所以,在上传文件大小超过 500 M 以后出现了内存溢出的错误,当时查看 jvm 分配的堆大小为 1个 G,年轻代为 200 M 左右,后来在增加堆大小为 2 个 G 后解决了这个问题。不过我仍然认为这样的代码是不正确的,所以后来把文件存储到本地,需要的时候再去读取,避免通过内存来直接复用。

总结: 第一次收获到内存溢出的错误,归结于当时的侥幸心理,下笔如有神,在编码中也仍然适用。不过呢,由于这次错误,也让我发现了推荐博文中的这篇文章,并且之前仅仅知道 GC ,现在实战了(完全没用上,哈哈哈…),具有里程碑一样的意义,总之,有进步。

【2020.7.1】:eden 太小?
最近使用 Intellij 感觉非常的不流畅,输入会有卡顿的感觉。我这里使用的是 2019.3 版本,起初的参数是这样的(我并不清楚之前是否有调整):
-Xms1024m -Xmx3375m -XX:ReservedCodeCacheSize=240m -XX:+UseConcMakeSweepGC -XX:SoftRefLRUPolicyMSPerMB=50

ReservedCodeCacheSize 用于设置Code Cache大小,JIT编译的代码都放在Code Cache中,若Code Cache空间不足则JIT无法继续编译,并且会去优化,比如编译执行改为解释执行,由此,性能会降低。
SoftRefLRUPolicyMSPerMB 是用于软引用回收判断的,软引用在 GC 时要不要回收,是根据公式:clock - timestamp <= freespace * SoftRefLRUPolicyMSPerMB 来计算的,“clock - timestamp”代表了一个软引用对象他有多久没被访问过了,freespace代表JVM中的空闲内存空间,SoftRefLRUPolicyMSPerMB 代表每一MB空闲内存空间可以允许SoftReference对象存活多久。

结果:使用 jvisualvm 监控 intellij 发现它的 eden区很快就被装满,基本上,我在编辑框输入几个字符就会直接装满。然后,我尝试调大了 -Xms c参数,效果好了很多。后来,我再切换为 G1 收集器时,感觉更舒服了。观察了内存使用情况,切换为 G1 后,最大内存可达 4 G。现在,Intellij 终于可以流畅了。eden 区很快被填满的现象一直存在,但后续两次的参数调整,让卡顿没那么明显,甚至消失了。