JVM--内存调优
什么是虚拟机参数配置
在虚拟机运行的过程,如果可以跟踪系统的运行状态,那么对于问题的故障排查会有一定的帮助,为此,虚拟机提供了一些跟踪系统状态的参数,使用给定的参数执行Java虚拟机,就可以在系统运行时打印相关的日志,用于分析实际的问题,我们进行虚拟机的参数的设置,其实就是围绕着堆,栈,方法区进行配置的。
常见的堆的参数配置
- -XX:+PrintGC
每次触发GC的时候打印相关的日志 - -XX:-UseSerialGC
串行回收(规定的垃圾收集器的策略) - -XX:+PrintGCDetails
更详细的GC日志 - -Xms
堆的初始值 - -Xmx
堆的最大的可用值 - -Xmn
新生代堆最大可用值 - -XX:SurvivorRatio
用来设置新生代中eden空间和from/to空间的比例
含以-XX:SurviorRatio=eden/(from+to)
总结:
在实际的工作种,我们可以直接将初始的堆大小与最大堆大小相等,这样的好处是可以减少程序运行时候的垃圾回收次数,从而提高效率
举例说明,下面是我们没有设置JVM相关参数的代码,以及输出后的结果
package com.xiyou.jvm;
import java.text.DecimalFormat;
public class Demo1 {
/**
* 该函数的作用是格式化处理内存大小,将kb转换成M
* @param maxMemory
* @return
*/
static private String toM(long maxMemory){
float num = (float) maxMemory / (1024 * 1024);
// 格式化小数
DecimalFormat df = new DecimalFormat("0.00");
// 返回String类型
String s = df.format(num);
return s;
}
public static void main(String[] args) {
byte[] bytes01 = new byte[1*1024*1024];
System.out.println("分配了1M内存");
jvmInfo();
byte[] bytes02 = new byte[4*1024*1024];
System.out.println("分配了4M内存");
jvmInfo();
}
public static void jvmInfo(){
// 最大内存配置信息
long maxMemory = Runtime.getRuntime().maxMemory();
System.out.println("maxMemory: " + maxMemory + ", " + toM(maxMemory) + "M");
// 当前空闲内存
long freeMemory = Runtime.getRuntime().freeMemory();
System.out.println("freeMemory: " + freeMemory + ", " + toM(freeMemory) + "M");
// 已经使用的内存
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("totalMemory: " + totalMemory + ", " + toM(totalMemory) + "M");
}
}
如上面的代码所示,我们在没有规定堆内存的时候,打印了最大内存,空闲内存,已经使用的内存
- 最大内存:
Runtime.getRuntime().maxMemory();
- 空闲内存:
Runtime.getRuntime().freeMemory();
- 已经使用的内存:
Runtime.getRuntime().totalMemory();
上面代码的打印结果如下:
Connected to the target VM, address: '127.0.0.1:12163', transport: 'socket'
分配了1M内存
maxMemory: 1881145344, 1794.00M
freeMemory: 123128344, 117.42M
totalMemory: 128974848, 123.00M
分配了4M内存
maxMemory: 1881145344, 1794.00M
freeMemory: 118934024, 113.42M
totalMemory: 128974848, 123.00M
Disconnected from the target VM, address: '127.0.0.1:12163', transport: 'socket'
Process finished with exit code 0
通过上面的结果可以看到
(1)不去配置堆内存的时候本机默认是1784M堆内存
(2)已经使用的内存totalMemory无论分配多少M结果都是123M,原因是打印的太快了,其实内存已经改变了,只不过没有记录出来。
下面是我们设置了虚拟机参数后的代码和输出结果
- 我们设置以下参数:
(1)-Xms5m:堆的初始内存大小是5M
(2)-Xmx20m:堆的最大可用值是20m
(3)-XX:PrintGCDetails:打印更详细的GC日志
(4)-XX:+UserSerialGC:串行回收
(5)-XX:+PrintCommandLineFlags:打印出已经被用户或者JVM设置过的详细的XX参数的名称和值 - 将这些参数进行设置
- 代码不做改变,和上面的代码一致
- 结果如下:
-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC
[GC (Allocation Failure) [DefNew: 1653K->192K(1856K), 0.0011658 secs] 1653K->619K(5952K), 0.0011940 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
分配了1M内存
[GC (Allocation Failure) [DefNew: 1856K->108K(1856K), 0.0015049 secs] 2283K->1745K(5952K), 0.0015222 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
maxMemory: 20316160, 19.38M
freeMemory: 3955424, 3.77M
totalMemory: 6094848, 5.81M
[GC (Allocation Failure) [DefNew: 452K->128K(1856K), 0.0004677 secs][Tenured: 1743K->1871K(4096K), 0.0015799 secs] 2089K->1871K(5952K), [Metaspace: 4003K->4003K(1056768K)], 0.0020839 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
分配了4M内存
maxMemory: 20316160, 19.38M
freeMemory: 4215384, 4.02M
totalMemory: 10358784, 9.88M
Heap
def new generation total 1920K, used 49K [0x00000000fec00000, 0x00000000fee10000, 0x00000000ff2a0000)
eden space 1728K, 2% used [0x00000000fec00000, 0x00000000fec0c670, 0x00000000fedb0000)
from space 192K, 0% used [0x00000000fedb0000, 0x00000000fedb0000, 0x00000000fede0000)
to space 192K, 0% used [0x00000000fede0000, 0x00000000fede0000, 0x00000000fee10000)
tenured generation total 8196K, used 5967K [0x00000000ff2a0000, 0x00000000ffaa1000, 0x0000000100000000)
the space 8196K, 72% used [0x00000000ff2a0000, 0x00000000ff873dc8, 0x00000000ff873e00, 0x00000000ffaa1000)
Metaspace used 4009K, capacity 4646K, committed 4864K, reserved 1056768K
class space used 449K, capacity 462K, committed 512K, reserved 1048576K
Process finished with exit code 0
通过上面我们可以看到GC进行了三次,当我们设置将Xmx和Xms相等的时候,就是最大的堆内存和初始的堆内存大小一样的时候
-XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC
分配了1M内存
maxMemory: 20316160, 19.38M
freeMemory: 16475232, 15.71M
totalMemory: 20316160, 19.38M
[GC (Allocation Failure) [DefNew: 3750K->640K(6144K), 0.0020843 secs] 3750K->1871K(19840K), 0.0025898 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
分配了4M内存
maxMemory: 20316160, 19.38M
freeMemory: 14101752, 13.45M
totalMemory: 20316160, 19.38M
Heap
def new generation total 6144K, used 4892K [0x00000000fec00000, 0x00000000ff2a0000, 0x00000000ff2a0000)
eden space 5504K, 77% used [0x00000000fec00000, 0x00000000ff0273b0, 0x00000000ff160000)
from space 640K, 100% used [0x00000000ff200000, 0x00000000ff2a0000, 0x00000000ff2a0000)
to space 640K, 0% used [0x00000000ff160000, 0x00000000ff160000, 0x00000000ff200000)
tenured generation total 13696K, used 1231K [0x00000000ff2a0000, 0x0000000100000000, 0x0000000100000000)
the space 13696K, 8% used [0x00000000ff2a0000, 0x00000000ff3d3cf8, 0x00000000ff3d3e00, 0x0000000100000000)
Metaspace used 4010K, capacity 4646K, committed 4864K, reserved 1056768K
class space used 449K, capacity 462K, committed 512K, reserved 1048576K
Process finished with exit code 0
可以看到只进行了一次GC操作。这就是验证了我们上面所说:
重要
- 当设置最大堆内存和初始化堆内存一致的时候,垃圾回收阶段最少。
- 堆的初始值越小,垃圾回收的次数越多。为了调优应该让Xmx和Xms的大小设置的一致。
设置新生代与老年代的回收比例进行调优
上面我们讲到了减少垃圾回收次数进行调优,这里我们再讲解一个调优策略------设置新生代与老年代的回收比例进行调优。使得垃圾回收机制经常去新生代进行回收
- 新生代与老年代的比例是1/3或者1/4,新生代较小的话,垃圾回收机制就会经常回收新生代,因为新生代内存不足,需要GC。
- 这里我们接收几个比较重要的参数:
(1)-XX:SurvivorRatio 用来设置新生代中eden和from+to空间的比例(可以理解为eden/(to+from))
(2)-Xmn:设置新生代的大小,一般设为整个堆的1/3或者1/4左右
(3)-XX:NewRatio:就是老年代和新生代的比例,该参数=老年代/新生代(尽量让垃圾回收发生在新生代而不是老年代,所以一般新生代都要比老年代小)
内存溢出
我们内存的溢出经常指的是栈内存溢出和堆内存溢出
- 栈内存溢出:
(1)栈内存溢出指的是无限递归调用
(2)注意一点,无限循环调用不是栈溢出。
(3)错误:java.lang.StackOverflowError
(4)解决方法:-Xss5m设置最大的调用深度
(5)栈内存例子:
package com.xiyou.jvm;
/**
* 栈内存溢出的例子
*/
public class Demo2 {
private static int count;
public static void count(){
try {
count++;
// 无限递归,没有终止条件
count();
} catch (Throwable e) {
System.out.println("最大深度:"+count);
e.printStackTrace();
}
}
public static void main(String[] args) {
count();
}
}
输出结果
最大深度:12298
java.lang.StackOverflowError
at sun.misc.Unsafe.compareAndSwapLong(Native Method)
at java.util.concurrent.ConcurrentHashMap.addCount(ConcurrentHashMap.java:2259)
at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1070)
at java.util.concurrent.ConcurrentHashMap.putIfAbsent(ConcurrentHashMap.java:1535)
at java.lang.ClassLoader.getClassLoadingLock(ClassLoader.java:463)
at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
at java.lang.ClassLoader.loadClass(ClassLoader.java:411)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
....
- 堆内存溢出:
(1)错误:java.lang.OutOfMemoryError: Java heap space
(2)解决方案:设置堆内存大小 -Xms1m -Xmx10m(设置初始的堆内存大小和最大的可用堆内存大小,这里只是举个例子,并不是一定要设置这么大)
(3)堆内存溢出例子:
package com.xiyou.jvm;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
public class Demo3
{
public static void main(String[] args) throws InterruptedException {
List<Object> list = new ArrayList<>();
Thread.sleep(3000);
jvmInfo();
for (int i = 0; i < 1000000000; i++) {
System.out.println("i:"+i);
Byte [] bytes= new Byte[1*1024*1024];
list.add(bytes);
jvmInfo();
}
System.out.println("添加成功...");
}
/**
* 该函数的作用是格式化处理内存大小,将kb转换成M
* @param maxMemory
* @return
*/
static private String toM(long maxMemory){
float num = (float) maxMemory / (1024 * 1024);
// 格式化小数
DecimalFormat df = new DecimalFormat("0.00");
// 返回String类型
String s = df.format(num);
return s;
}
public static void jvmInfo(){
// 最大内存配置信息
long maxMemory = Runtime.getRuntime().maxMemory();
System.out.println("maxMemory: " + maxMemory + ", " + toM(maxMemory) + "M");
// 当前空闲内存
long freeMemory = Runtime.getRuntime().freeMemory();
System.out.println("freeMemory: " + freeMemory + ", " + toM(freeMemory) + "M");
// 已经使用的内存
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("totalMemory: " + totalMemory + ", " + toM(totalMemory) + "M");
}
}
结果:
(输出堆未溢出的时候的内存)
.....
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.xiyou.jvm.Demo3.main(Demo3.java:15)
总结:
- 减少垃圾回收次数可以进行JVM调优
(1)设置最大堆内存大小(Xmx)和堆内存初始大小一致(Xms),Xms越小,垃圾回收次数越多。 - 尽量让GC发生在新生代
(1)-Xmn:设置新生代的大小,一般为整个堆的1/3 1/4
(2)-XX:SurvivorRatio=eden/(to+from)
(3)-XX:NewRatio = 老年代/新生代
总而言之我们希望通过设置达到以下要求:
(1)GC的时间足够小
(2)GC的次数足够少
(3)发生Full GC(新生代和老年代)的周期足够长
但是我们(2)和(1)是不能同时成立的。要想GC时间少,就必须有个更小的堆,要想GC次数足够少,就需要一个更大的堆,并且(3)我们无法给出明确的指标,所以我们通常在生产环境上按照上面说明的几点设计就好。