Ref:

Java 内存运行时区域的程序计数器、虚拟机栈、本地方法栈 3 个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由即时编译器进行一些优化,但在基于概念模型的讨论里,大体上可以认为是编译期可知的),因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑如何回收的问题,当方法结束或者线程结束时,内存自然就跟随着回收了。

image.png

A best practice is to tune the time spent doing garbage collection to within 5% of execution time. Ref: https://docs.oracle.com/cd/E21764_01/web.1111/e13814/jvm_tuning.htm#PERFM156

1.原理

JVM 堆内存分为 2 块:Permanent Space 和 Heap Space。

  • Permanent 即 持久代(Permanent Generation),主要存放的是 Java 类定义信息,与垃圾收集器要收集的 Java 对象关系不大。
  • Heap = { Old + NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年轻代(Young Generation)。老年代和年轻代的划分对垃圾收集影响比较大。

    1.1 年轻代(Young)

    所有新生成的对象首先都是放在年轻代。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代一般分 3 个区,1 个 Eden 区,2个 Survivor 区(from 和 to)。

大部分对象在 Eden 区中生成。当 Eden 区满时,还存活的对象将被复制到 Survivor 区(两个中的一个),当一个 Survivor 区满时,此区的存活对象将被复制到另外一个 Survivor 区,当另一个 Survivor 区也满了的时候,从前一个 Survivor 区复制过来的并且此时还存活的对象,将可能被复制到年老代。

2 个 Survivor 区是对称的,没有先后关系,所以同一个 Survivor 区中可能同时存在从 Eden 区复制过来对象,和从另一个 Survivor 区复制过来的对象;而复制到年老区的只有从另一个 Survivor 区过来的对象。而且,因为需要交换的原因,Survivor区至少有一个是空的。特殊的情况下,根据程序需要,Survivor 区是可以配置为多个的(多于2个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。
针对年轻代的垃圾回收即 Young GC(MinorGC)

1.2 年老代(Old / Tenured)

在年轻代中经历了N次(可配置)垃圾回收后仍然存活的对象,就会被复制到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
针对年老代的垃圾回收即 Full GC(MajorGC)

1.3 持久代(Permanent)

用于存放静态类型数据,如 Java Class, Method 等。持久代对垃圾回收没有显著影响。但是有些应用可能动态生成或调用一些 Class,例如 Hibernate CGLib 等,在这种时候往往需要设置一个比较大的持久代空间来存放这些运行过程中动态增加的类型。

1.4 内存申请

所以,当一组对象生成时,内存申请过程如下:

  • JVM 会试图为相关Java对象在年轻代的 Eden 区中初始化一块内存区域。
  • 当 Eden 区空间足够时,内存申请结束。否则执行下一步。
  • JVM 试图释放在 Eden区 中所有不活跃的对象(Young GC)。
    释放后若Eden空间仍然不足以放入新对象,JVM 则试图将部分 Eden 区中活跃对象放入 Survivor 区。
  • Survivor 区被用来作为 Eden 区及年老代的中间交换区域。当年老代空间足够时,Survivor 区中存活了一定次数的对象会被移到年老代。
  • 当年老代空间不够时,JVM会在年老代进行完全的垃圾回收(Full GC)。
  • Full GC 后,若 Survivor 区及年老代仍然无法存放从 Eden 区复制过来的对象,则会导致 JVM 无法在 Eden 区为新生成的对象申请内存,即出现Out of Memory

    OOM 异常

    一般主要有如下 2 种原因:
  1. 年老代溢出(Tenured),表现为:java.lang.OutOfMemoryError:Javaheapspace
    这是最常见的情况,产生的原因可能是:设置的内存参数 -Xmx 过小或程序的内存泄露及使用不当问题。
    例如,循环上万次的字符串处理、创建上千万个对象、在一段代码内申请上百 M 甚至上 G 的内存;还有的时候虽然不会报内存溢出,却会使系统不间断的垃圾回收,也无法处理其它请求。这种情况下除了检查程序、打印堆内存等方法排查,还可以借助一些内存分析工具,比如 MAT 就很不错。
  2. 持久代溢出(Permanent),表现为:java.lang.OutOfMemoryError:PermGenspace
    通常由于持久代设置过小,动态加载了大量 Java 类而导致溢出,解决办法唯有将参数 -XX:MaxPermSize 调大(一般 256 m 能满足绝大多数应用程序需求)。将部分 Java 类放到容器共享区(例如 Tomcat share lib)去加载的办法也是一个思路,但前提是容器里部署了多个应用,且这些应用有大量的共享类库。

调优主要的目的是减少 GC 的频率和 Full GC 的次数。Full GC 因为需要对整个堆进行回收,所以比较慢,因此应该尽可能减少 Full GC 的次数。
为避免 FullGC 频繁:

  • 不要创建过大的对象及数组避免直接在旧生代创建对象。
  • 增大 Perm Gen 空间,避免太多静态对象,
  • 控制好新生代和旧生代的比例。

    2.调优参数

    JVM 中最大堆大小有三方面限制:

  • 相关操作系统的数据模型(32-bit 还是 64-bit)限制;

  • 系统的可用虚拟内存限制;
  • 系统的可用物理内存限制。

32 位系统下,一般限制在 1.5G~2G;64 位操作系统对内存无限制。我在 Windows Server 2003 系统,3.5G 物理内存,JDK5.0 下测试,最大可设置为 1478m。

2.1 参数列表

堆内存参数典型设置:

  1. java -Xmx3550m -Xms3550m -Xmn2g -Xss128k \
  2. -XX:NewSize=1024m -XX:MaxNewSize=1024m \
  3. -XX:NewRatio=4 -XX:SurvivorRatio=4 \
  4. -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
  • -Xmx3550m最大可用堆内存为 3550M;
  • -Xms3550m初始堆内存为 3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后 JVM 重新分配内存;
  • -Xmn2g年轻代大小为 2G。
    - JDK1.7:整个堆大小=年轻代大小 + 年老代大小 + 持久代大小
    - JDK8:整个堆大小=年轻代大小 + 年老代大小
    持久代一般固定大小为 64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun 官方推荐配置为整个堆的 3/8;
  • -Xss128k设置每个线程的堆栈大小。JDK5.0 以后每个线程堆栈大小为 1M,以前每个线程堆栈大小为 256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000 左右;
  • -XX:NewSize=1024m设置年轻代初始值为 1024M;
  • -XX:MaxNewSize=1024m设置年轻代最大值为 1024M;
  • -XX:NewRatio=4设置年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去持久代)。设置为 4,则年轻代与年老代所占比值为 1:4,年轻代占整个堆的 1/5;
  • -XX:SurvivorRatio=4设置年轻代中 Eden 区与 Survivor 区的大小比值。设置为 4,则两个 Survivor 区与一个 Eden 区的比值为 2:4,一个 Survivor 区占整个年轻代的 1/6;
  • -XX:MaxPermSize=16m设置持久代大小为 16m;
  • -XX:MaxTenuringThreshold=0设置垃圾最大年龄。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代 。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象再年轻代的存活时间 ,增加在年轻代即被回收的概率;

Ref: https://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html

2.2 参数选取

-Xmn,-XX:NewSize / -XX:MaxNewSize,-XX:NewRatio 3 组参数都可以影响年轻代的大小,混合使用的情况下,优先级是什么?如下:

  • 高优先级:-XX:NewSize / -XX:MaxNewSize
  • 中优先级:-Xmn(默认等效 -Xmn=-XX:NewSize=-XX:MaxNewSize=?)
  • 低优先级:-XX:NewRatio

推荐使用 -Xmn 参数,原因是这个参数简洁,相当于一次设定 NewSize / MaxNewSIze,而且两者相等,适用于生产环境。-Xmn 配合 -Xms/-Xmx,即可完成堆内存的布局。
-Xmn 参数是在 JDK 1.4 开始支持。

2.3 -Xms -Xmx -Xss

  • -Xms: jvm 启动时分配的内存,-Xms200m 表示分配 200M. 一般来讲,大点程序会启动的快一点。
  • -Xmx: jvm 运行过程中分配的最大内存,-Xms500m 表示 jvm 进程最多只能够占用 500M 内存。
  • -Xss: jvm 启动的每个线程分配的内存大小,默认 JDK1.4: 256K,JDK1.5+: 1M。

以上三个参数的设置都是默认以 Byte 为单位的,也可以在数字后面添加 [k/K] 或者 [m/M] 来表示 KB 或者 MB。超过机器本身的内存大小也是不可以的,否则就等着机器变慢而不是程序变慢了。

Total Memory -Xms -Xmx -Xss Spare Memory JDK Thread Count
1024M 256M 256M 256K 768M 1.4 3072
1024M 256M 256M 256K 768M 1.5 768

上表只是大致的估计了在特定内存条件下可以在java中创建的最大线程数。随着 -Xmx 的加大,空闲的内存数就更少,那么可以创建的线程也就更少,同时在 JDK1.4 和 1.5 版本不同下,可创建的线程数也会根据每个线程的内存大小不同而不同。

随着 -Xmx 的加大,Heap 内存增大,JVM 空闲的内存数就更少,那么可以创建的线程也就更少:

  • 增大堆内存(-Xms,-Xmx)会减少可创建的线程数量;
  • 增大线程栈内存(-Xss,32 位系统中此参数值最小为 60K)也会减少可创建的线程数量;

线程最大数由 JVM 的堆内存大小Thread 的 Stack 内存大小系统最大可创建的线程数量三个方面影响,系统:

  • /proc/sys/kernel/pid_max
  • /proc/sys/kernel/thread-max
  • max_user_process(ulimit -u)
  • /proc/sys/vm/max_map_count

    2.4 调优事项

  1. 生成 dump 文件:通过 JMX 的 MBean 生成当前的 Heap 信息,大小为一个3G(整个堆的大小)的 hprof 文件,如果没有启动 JMX 可以通过 Java 的 jmap 命令来生成该文件。
  2. 分析 dump 文件:打开这个3G的堆信息文件,显然一般的 Window 系统没有这么大的内存,必须借助高配置的 Linux,几种工具打开该文件:
    - Visual VM
    - IBM HeapAnalyzer
    - JDK 自带的 Hprof 工具
    - Mat(Eclipse专门的静态内存分析工具)推荐使用

备注:文件太大,建议使用 Eclipse 专门的静态内存分析工具 Mat 打开分析。

如果分析结果满足下面的指标,则一般不需要进行GC:

  • Minor GC 执行时间不到 50ms;
  • Minor GC 执行不频繁,约 10 秒一次;
  • Full GC 执行时间不到 1s;
  • Full GC 执行频率不算频繁,不低于 10 分钟 1 次;

    2.5 打印辅助信息

    JVM 提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:

  • -XX:+PrintGC
    输出形式:
    [GC 118250K->113543K(130112K), 0.0094143 secs]
    [Full GC 121376K->10414K(130112K), 0.0650971 secs]

  • -XX:+PrintGCDetails
    输出形式:
    [GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]

  • -XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps 可与上面两个混合使用
    输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]

  • -XX:+PrintGCApplicationConcurrentTime打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用
    输出形式:Application time: 0.5291524 seconds

  • -XX:+PrintGCApplicationStoppedTime打印垃圾回收期间程序暂停的时间。可与上面混合使用
    输出形式:Total time for which application threads were stopped: 0.0468229 seconds

  • -XX:PrintHeapAtGC打印GC前后的详细堆栈信息
    输出形式:
    34.702: [GC {Heap before gc invocations=7:
    def new generation total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
    eden space 49152K, 99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)
    from space 6144K, 55% used [….]

  • -Xloggc:filename与上面几个配合使用,把相关日志信息记录到文件以便分析。

    2.6 常见配置

  1. 堆设置
    • -Xms :初始堆大小
    • -Xmx :最大堆大小
    • -XX:NewSize=n :设置年轻代大小
    • -XX:NewRatio=n: 设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
    • -XX:SurvivorRatio=n :年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
    • -XX:MaxPermSize=n :设置持久代大小
  2. 收集器设置
    • -XX:+UseSerialGC :设置串行收集器
    • -XX:+UseParallelGC :设置并行收集器
    • -XX:+UseParalledlOldGC :设置并行年老代收集器
    • -XX:+UseConcMarkSweepGC :设置并发收集器
  3. 垃圾回收统计信息
    • -XX:+PrintGC
    • -XX:+PrintGCDetails
    • -XX:+PrintGCTimeStamps
    • -Xloggc:filename
  4. 并行收集器设置
    • -XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。
    • -XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间
    • -XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
  5. 并发收集器设置
    • -XX:+CMSIncrementalMode :设置为增量模式。适用于单CPU情况。
    • -XX:ParallelGCThreads=n :设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

      2.7 一点说明

      java.lang.Runtime 类中的 freeMemory(), totalMemory(), maxMemory() 这几个方法反映的都是 java 这个进程的内存情况,跟操作系统的内存根本没有关系。
  • maxMemory():返回的是 java虚拟机(这个进程)能构从操作系统那里挖到的最大的内存,以字节为单位,如果在运行 java 程序的时候,没有添加 -Xmx 参数,那么就是 64 M,也就是说 maxMemory() 返回的大约是 64_1024_1024 字节,这是 java 虚拟机默认情况下能从操作系统那里挖到的最大的内存。如果添加了 -Xmx 参数,将以这个参数后面的值为准,例如java -cp you_classpath -Xmx512m your_class,那么最大内存就是 512_1024_1024 字节。
  • totalMemory():返回的是 java 虚拟机现在已经从操作系统那里挖过来的内存大小,也就是 java 虚拟机这个进程当时所占用的所有内存。如果在运行 java 的时候没有添加 -Xms 参数,那么,在 java 程序运行的过程的,内存总是慢慢的从操作系统那里挖的,基本上是用多少挖多少,直到挖到 maxMemory() 为止,所以totalMemory() 是慢慢增大的。如果用了 -Xms 参数,程序在启动的时候就会无条件的从操作系统中挖 -Xms后面定义的内存数,然后在这些内存用的差不多的时候,再去挖。
  • freeMemory():刚才讲到如果在运行 java 的时候没有添加 -Xms 参数,那么,在 java 程序运行的过程的,内存总是慢慢的从操作系统那里挖的,基本上是用多少挖多少,但是 java 虚拟机100%的情况下是会稍微多挖一点的,这些挖过来而又没有用上的内存,实际上就是 freeMemory(),所以 freeMemory() 的值一般情况下都是很小的,但是如果你在运行 java 程序的时候使用了 -Xms,这个时候因为程序在启动的时候就会无条件的从操作系统中挖 -Xms 后面定义的内存数,这个时候,挖过来的内存可能大部分没用上,所以这个时候 freeMemory() 可能会有些大。

    3. 调优策略

    3.1 内存设置策略 ♥♥

    新上线一个 java 服务,或者是 RPC 或者是 Web 站点, 内存的设置该怎么设置呢?设置成多大比较合适,既不浪费内存,又不影响性能呢?

分析:依据的原则是根据 Java Performance 里面的推荐公式来进行设置。
JVM 调优基础 - 图2
具体来讲:

  • Java 整个堆大小设置,Xmx 和 Xms 设置为老年代存活对象的 3-4 倍,即 FullGC 之后的老年代内存占用的 3-4 倍。
  • 持久代 PermSize 和 MaxPermSize 设置为老年代存活对象的 1.2-1.5倍。
  • 年轻代 Xmn 的设置为老年代存活对象的 1-1.5 倍。
  • 老年代的内存大小设置为老年代存活对象的 2-3 倍。

BTW:
1、Sun 官方建议年轻代的大小为整个堆的 3/8 左右,所以按照上述设置的方式,基本符合 Sun 的建议。
2、堆大小=年轻代大小+年老代大小, 即 xmx = xmn + 老年代大小 。 Permsize 不影响堆大小

3.2 确认老年代存活对象大小

  1. GC日志:推荐/比较稳妥

JVM 参数中添加 GC 日志,GC 日志中会记录每次 FullGC 之后各代的内存大小,观察老年代 GC 之后的空间大小。可观察一段时间内(比如2天)的 FullGC 之后的内存情况,根据多次的 FullGC 之后的老年代的空间大小数据来预估 FullGC 之后老年代的存活对象大小(可根据多次 FullGC 之后的内存大小取平均值)。

  1. 强制触发 FullGC:会影响线上服务慎用

方式 1 的方式比较可行,但需要更改JVM参数,并分析日志。同时,在使用 CMS 回收器的时候,有可能不能触发 FullGC(只发生CMS GC),所以日志中并没有记录 FullGC 的日志。在分析的时候就比较难处理。

BTW:使用 jstat -gcutil 工具来看 FullGC 的时候, CMS GC 是会造成 2 次的 FullGC 次数增加(具体可参见之前写的一篇关于 jstat 使用的文章)。所以,有时候需要强制触发一次 FullGC,来观察 FullGC 之后的老年代存活对象大小。

注:强制触发 FullGC,会造成线上服务停顿(STW),要谨慎。建议的操作方式为,在强制 FullGC 前先把服务节点摘除,FullGC 之后再将服务挂回可用节点,对外提供服务。

在不同时间段触发 FullGC,根据多次 FullGC 之后的老年代内存情况来预估 FullGC 之后的老年代存活对象大小。

3.3 如何触发 FullGC

使用 jmap 工具可触发 FullGC:

  1. # 将当前的存活对象dump到文件,此时会触发FullGC
  2. jmap -dump:live,format=b,file=heap.bin <pid>
  3. # 打印每个class的实例数目,内存占用,类全名信息.live子参数加上后,只统计活的对象数量.
  4. # 此时会触发FullGC
  5. jmap -histo:live <pid>

3.4 操作实例

以我司的一个RPC服务为例。
BTW:刚上线的新服务,不知道该设置多大的内存的时候,可以先多设置一点内存,然后根据GC之后的情况来进行分析。初始JVM内存参数设置为: Xmx=2G Xms=2G xmn=1G

使用 jstat 查看当前的 GC 情况,如下图:
JVM 调优基础 - 图3

YGC 平均耗时:173.825s/15799=11ms
FGC 平均耗时:0.817s/41=19.9ms
平均大约 10-20s 会产生一次 YGC。

看起来似乎不错,YGC 触发的频率不高,FGC 的耗时也不高,但这样的内存设置是不是有些浪费呢?
为了快速看数据,我们使用了方式 2,产生了几次 FullGC,FullGC 之后,使用的 jmap -heap 来看的当前的堆内存情况(也可以根据 GC 日志来看)。命令 jmap -heap <pid>,heap 情况如下图:
JVM 调优基础 - 图4

上图中的 concurrent mark-sweep generation 即为老年代的内存描述。
老年代的内存占用为 100M 左右。 按照整个堆大小是老年代(FullGC)之后的 3-4 倍计算的话,设置各代的内存情况如下:

  1. # 老年代的大小为 (512-128=384m)为老年代存活对象大小的3倍左右
  2. Xmx=512m Xms=512m Xmn=128m PermSize=128m

调整之后的,heap 情况:
JVM 调优基础 - 图5
GC 情况如下:
JVM 调优基础 - 图6
YGC 差不多在10s左右触发一次。每次 YGC 平均耗时大约 9.41 ms,可接受。
FGC 平均耗时:0.016s/2=8ms。
整体的 GC 时减少,但 GC 频率比之前的 2G 时的要多了一些。

总结

在内存相对紧张的情况下,可以按照上述的方式来进行内存的调优, 找到一个在 GC 频率和 GC 耗时上都可接受的一个内存设置,可以用较小的内存满足当前的服务需要。

但当内存相对宽裕的时候,可以相对给服务多增加一点内存,可以减少 GC 的频率,GC 的耗时相应会增加一些。 一般要求低延时的可以考虑多设置一点内存, 对延时要求不高的,可以按照上述方式设置较小内存。
持久代(方法区)并不在堆内,所以之前有看过一篇文章中描述的 整个堆大小=年轻代+年老代+持久代 的描述是不正确的

企业级生产环境参数 ♥♥

  1. java -server -Xms2048m -Xmx2048m -Xmn768m -Xss256k \
  2. -XX:SurvivorRatio=8 \
  3. -XX:MetaspaceSize=512m \
  4. -XX:MaxMetaspaceSize=1024m \
  5. -XX:-UseAdaptiveSizePolicy \
  6. -XX:+PrintPromotionFailure \
  7. -XX:+HeapDumpOnOutOfMemoryError \
  8. -XX:HeapDumpPath=/data/logs/dist-quiz/oom.hprof \
  9. -XX:+UseConcMarkSweepGC \
  10. -XX:+CMSParallelRemarkEnabled \
  11. -XX:CMSInitiatingOccupancyFraction=70 \
  12. -XX:+UseCMSInitiatingOccupancyOnly \
  13. -XX:+UseFastAccessorMethods \
  14. -XX:+PrintGCDetails \
  15. -XX:+PrintGCDateStamps \
  16. -XX:GCLogFileSize=100M \
  17. -Xloggc:/data/logs/dist-quiz/gc.log \
  18. -Dcom.sun.management.jmxremote \
  19. -Dcom.sun.management.jmxremote.port=7070 \
  20. -Dcom.sun.management.jmxremote.authenticate=false \
  21. -Dcom.sun.management.jmxremote.ssl=false \
  22. -Djava.awt.headless=true \
  23. -Djava.net.preferIPv4Stack=true \
  24. -Dfile.encoding=UTF-8 \
  25. -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8010 \
  26. -Xdebug \
  27. -Djava.compiler=NONE \
  28. -Dspring.profiles.active=prod \
  29. -jar /data/project/dist-quiz/dist-quiz-SNAPSHOT.jar

4.调优总结

4.1 基本原则

  • 为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把 -Xms -Xmx 设置为相同的值;
  • 在配置较好的机器上(多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC;
  • 对大多数应用而言,每个线程默认会开启的 1M 堆栈(用于存放栈帧、调用参数、局部变量等)太大了,一般256K就足用;

    4.2 年轻代大小选择

  • 响应时间优先的应用尽可能设大,直到接近系统的最低响应时间限制 (根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。

  • 吞吐量优先的应用 :尽可能的设置大,可能到达 Gbit 的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合 8CPU 以上的应用。

    4.3 年老代大小选择

  1. 响应时间优先的应用

年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。
如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。

最优化的方案,一般需要参考以下数据获得:

  • 并发垃圾收集信息
  • 持久代并发收集次数
  • 传统GC信息
  • 花在年轻代和年老代回收上的时间比例

减少年轻代和年老代花费的时间,一般会提高应用的效率

  1. 吞吐量优先的应用

一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。

4.4 较小堆引起的碎片问题

因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置: