# JVM参数设置

> 转载：[JVM参数设置](https://sasaxie.github.io/java/2018/05/02/JVM%E5%8F%82%E6%95%B0%E8%AE%BE%E7%BD%AE/)

## 1. 基本概念

HotSpot JVM把新生代分为了三部分：1个Eden区和2个Survivor区（分别叫From和To）。默认比例为8:1:1。一般情况下，新创建的对象都会被分配到Eden区（一些大对象特殊处理），这些对象经过第一次Minor GC后，如果仍然存活，将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC，年龄就会增加1岁，当它的年龄增加到一定程度时，就会被移动到老年代中。

在GC开始的时候，对象只会存在于Eden区和名为“From”的Survivor区，Survivor区“To”是空的。紧接着进行GC，Eden区中所有存活的对象都会被复制到“To”，而在“From”区中，仍存活的对象会根据它们的年龄值来决定去向。年龄达到一定值（年龄阈值，可以通过`-XX:MaxTenuringThreshold`来设置）的对象会被移动到老年代中，没有达到阈值的对象会被复制到“To”区域。经过这次GC后，Eden区和“From”区已经被清空。这个时候，“From”和“To”会交换它们的角色，也就是新的“To”就是上次GC前的“From”，新的“From”就是上次GC前的“To”。不管怎样，都会保证名为“To”的Survivor区域是空的。Minor GC会一直重复这样的过程，直到“To”区被填满，“To”区被填满之后，会将所有对象移到到老年代中。

## 2. GC策略

新生代GC：Minor GC，新生代中的对象基本都是朝生夕死的（80%以上），触发频繁，当Eden区内存不够的时候触发，对新生代进行一次垃圾回收，复制算法：基本思想就是将内存分成两块，每次只用其中一块，当这一块内存用完，就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎块。

老年代GC：Major GC，又称Full GC，不会频繁触发，在进行Major GC前一般都先进行了一次Minor GC，使得有新生代的对象晋入老年代，导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时，也会提前触发一次Major GC进行垃圾回收腾出空间。标记-清除算法：首先扫描一次所有的老年代，标记出存活的对象，然后回收没有标记的对象。Major GC的耗时比较长，因为要扫描再回收。Major GC会产生内存碎片，为了减少内存损耗，我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满的装不下的时候，就好抛出OOM（Out of Memory）异常。

永久带：指内存的永久保存区域，主要存放Class和Meta（元数据）的信息，Class在被加载的时候被放入永久代。它和存放实例的区域不同，GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着Class的增多而膨胀，最终抛出OOM异常。在Java8中，永久代已经被移除，被一个称为“元数据区”的区域所取代。元空间的本质和永久代类似，都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于：元空间并不在虚拟机中，而是使用本地内存。因此，默认情况下，元空间的大小仅受本地内存限制。类的元数据放入Native Memory，字符串池和类的静态变量放入Java堆中。这样可以加载多少类的元数据就不再由MaxPermSize控制，而由系统的实际可用空间来控制。

## 3. GC方式

串行GC（SerialGC）：在整个扫描和复制过程采用单线程的方式来进行，适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上，是Client级别默认的GC方式，可以通过`-XX:+UseSerialGC`来强制指定

并行回收GC（ParallelScavenge）：在整个扫描和复制过程采用多线程的方式来进行，适用于多CPU、对暂停时间要求较短的应用上，是server级别默认采用的GC方式，可用`-XX:+UseParallelGC`来强制指定，用`-XX:ParallelGCThreads=4`来指定线程数

并行GC（ParNew）：与旧生代的并发GC配合使用

并发GC（CMS）：老年代GC

HotSpot? JRockit?

## 4. JVM参数设置

活跃数据

1. 应用程序Full GC后，老年代占用的Java堆大小
2. 应用程序Full GC后，永久代占用的Java堆大小

**老年代**

Java堆大小减新生代大小，建议设置为老年代活跃数据的2\~3倍，老年代大小=堆大小-新生代大小

**-XX:+PrintTenuringDistribution**

显示每次Minor GC时Survivor区中各个年龄段的对象的大小（格式：`-XX:+PrintTenuringDistribution`）

**-XX:+PrintGCDetails**

显示GC细节（格式：`-XX:+PrintGCDetails`）

**-XX:+PrintTenuringDistribution**

指定JVM在每次新生代GC时，输出幸存区中对象的年龄分布（格式：`-XX:+PrintTenuringDistribution`）

**-Xms** **-Xmx**

设置堆初始值和最大值大小（格式：`-Xms1024m -Xmx1024m`），两个值设为一样大，建议设置为老年代活跃数据大小的3\~4倍

**-XX:PermSize** **-XX:MaxPermSize**

设置永久代初始值和最大值大小（格式：`-XX:PermSize=64m -XX:MaxPermSize=128m`），两个值设为一样大，建议设置为永久代活跃数据的1.2\~1.5倍，Java8中采用元空间，仅由系统的实际可用空间控制

**-XX:NewSize** **-XX:MaxNewSize**

设置新生代的大小（格式：`-XX:NewSize=64m -XX:MaxNewSize=128m`），建议设置为整个堆大小的1/3或者1/4，两个值设为一样大，建议设置为老年代活跃数据的1\~1.5倍，最大值一般不超过堆内存大小的1/2

**-XX:TargetSurvivorRatio**

幸存区空间目标使用率（格式：`-XX:TargetSurvivorRatio=90`），如果幸存区使用率经常达到，有些年龄超过1的对象被移动到老年代中。这种情况，可以尝试调整幸存区大小或目标使用率

**-Xmn**

设置新生代的大小（格式：`-Xmn2g`），建议设置为老年代活跃数据的1\~1.5倍

**-XX:NewRatio**

设置新生代与老年代的比例（格式：`-XX:NewRatio=2`），如：-XX:NewRatio=2，则新生代占整个堆空间的1/3，老年代占2/3，即老年代是新生代的2倍，这个值不能<=1，也就是说老年代必须大于新生代

**-XX:SurvivorRatio**

设置Eden区和其中一个Survivor区的比值（格式：`-XX:SurvivorRatio=8`），默认值为：-XX:SurvivorRatio=8，即Eden区占新生代空间的8/10，另外两个Survivor区各占1/10，Eden区是To/From的8倍

**-XX:InitialTenuringThreshold** **-XX:MaxTenuringThreshold**

设置晋升到老年代的对象年龄的初始值和最大值（格式：`-XX:InitialTenuringThreshold=10 -XX:MaxTenuringThreshold=90`），每个对象在坚持过一次Minor GC后，年龄就加1

**-Xss**

设置每个线程堆栈大小（格式：`-Xss256k`），一般来说如果栈不是很深，1M是绝对够用

**-XX:+HeapDumpOnOutOfMemoryError**

让虚拟机在发生内存溢出时Dump出当前的内存堆转储快照（格式：`-XX:+HeapDumpOnOutOfMemoryError`），以便分析使用

**-XX:+UseSerialGC**

强制指定串行GC（格式：`-XX:+UseSerialGC`）

**-XX:+UseParallelGC** **-XX:ParallelGCThreads**

强制指定并行回收GC（格式：`-XX:+UseParallelGC -XX:ParallelGCThreads=4`）

**-XX:+NeverTenure** **-XX:+AlwaysTenure**

没有老年代和没有新生代，极端情况下的设置，一般不需要（格式：`-XX:+NeverTenure -XX:+AlwaysTenure`）
