JVM参数类型与设置
安装JDK,配置系统环境参数后,输入java -help,红框标记的就是我们设置 JVM参数的区域。
[-options]意思是不强制要求设置,java命令支持各种参数设置,这些参数可以分为以下类别
- 标准参数:JVM的所有实现所支持的最常用的参数 (在jdk各版本之间稳定,不会有较大变化)。
- 非标准参数(-X):是特定于Java HotSpot虚拟机的通用选项(了解就行)。
- 高级运行时参数(-XX):控制Java HotSpot VM的运行时行为(重点)。
这里我们重点讲高级运行时参数,JVM参数主要有boolean值类型与key-value值类型。我们写两个case应用一下,Test是我们的资源类。用boolean值设置打印出GC的详情,用key-value值设置MetaspaceSize内存大小。
- +表示boolean值true,-表示boolean值false。
public class Test {
public static void main(String[] args) throws Exception {
System.out.println("hello world");
}
}
- -XX:属性(key)=属性值(value)
JVM参数查看
查看运行时JVM参数配置信息
//我们把资源类Test添加sleep用于测试
public class Test extends ClassLoader {
public static void main(String[] args) throws Exception {
System.out.println("hello world");
Thread.sleep(Integer.MAX_VALUE);
}
}
此时Test进程阻塞中,我们看看我们的设置的元空间大小是否成功,重新打开一个窗口
- jps:相当于linux的ps -ef|grep java,主要查询出JVM进程的进程编号
- jinfo:根据JVM进程编号查看该进程的信息
查看出厂的JVM的参数默认配置信息
- -XX:+PrintFlagsInitial:查看默认值
- -XX:+PrintFlagsFinal:查看最终值,就是JVM运行时的值
其中带 : 的就是修改过的值,就是JVM修改或者人为的修改过的值。 - -XX:+PrintCommandLineFlags:查看当前常用参数默认值
JVM常用参数
- -Xms(最小堆内存)、-Xmx(最大堆内存)
public class Test {
public static void main(String[] args) throws Exception {
long totalMemory = Runtime.getRuntime().totalMemory();
long maxMemory = Runtime.getRuntime().maxMemory();
System.out.println("totalMemory(-Xms) = " + totalMemory / (double) 1024 / 1024 + "MB");
System.out.println("maxMemory(-Xmx) = " + maxMemory / (double) 1024 / 1024 + "MB");
}
}
我的电脑内存为8G,JVM初始内存一般为物理内存的1/64,最大内存为物理内存的1/4。(拓展:-Xms=-XX:InitialHeapSize,-Xmx=-XX:MaxHeapSize)
totalMemory(-Xms) = 123.0MB
maxMemory(-Xmx) = 1803.0MB
- -Xss(单个线程栈的内存大小)
一般默认值为512KB~1024KB,我们可以按上面的查看方式确认一下默认值
发现竟然是0,怎么可能,我们去官网看看为什么,发现0表示使用的是默认值,这个默认值又依赖于平台,不同平台就不一样。
(拓展:-Xss=-XX:ThreadStackSize) - -Xmn(新生代内存大小)
一般不去设置,默认新生代大小堆内存的1/3,老年代堆内存的2/3 - -XX:MetaspaceSize
在再JDK1.8后,永久代被元空间所取代,它们之间最大的区别是永久代使用的内存是JVM的堆内存,而元空间使用的是本地内存。本地内存(Native memory),也称为C-Heap,是供JVM自身进程使用的。当Java Heap空间不足时会触发GC,但Native memory空间不够却不会触发GC。默认情况下元空间的大小只受本地物理内存限制。初始默认大小为21M左右。 - -XX:+PrintGCDetails
打印垃圾回收信息,设置一个需要GC的场景
public class Test {
public static void main(String[] args) throws Exception {
System.out.println("hello world");
//创建一个大对象
byte[] bytes = new byte[1024 * 1024 * 50];
}
}
执行时设置最大堆内存小于50m,即可出现GC的动作。
结合上图的GC信息,分析当前堆内存使用情况
-
-XX:SurvivorRatio
作用是设置新生代的Eden区与Survivor-From和Survivor-To的比例,默认为8,就是8:1:1,如果修改为2,则2:1:1,Survivor-From和Survivor-To是相同的。
使用-XX:PrintGCDetails,查看发现eden为33280K,from为5120K,to为5120K
-
-XX:NewRatio
设置新生代老年代的比例,默认为2,就是新生代1/3,老年代2/3。如果修改5,则新生代为1/6,老年代就是5/6。 -
-XX:MaxTenuringThreshold
设置用于自适应GC大小调整的最大使用期限阈值。最大值为15。并行(吞吐量)收集器的默认值为15,而CMS收集器的默认值为6。理解它之前我们先了解一下MinorGC执行的流程。
1)首先当Eden区满时,第一次GC,把还活着的对象拷贝到Survivor-From区,
2)当Eden区再次出发GC的时候,会扫描Eden区和Survivor-From区,对这俩区进行垃圾回收。
3)经过垃圾回收后,还存活的对象直接复制到Survivor-To区,同时把复制到Survivor-To区的对象年龄+1。
4)然后清空Eden和Survivor-From区,
5)最后将Survivor-From区改为Survivor-To区,Survivor-To区改为Survivor-From区,谁空谁就是Survivor-To区。
MinorGC执行时,部分对象在Survivor-From和Survivor-To区复制来复制区,来回交换15次最终还是存活则存入到老年代。改参数就是新生代对象转为老年代对象的难易程度,最大GC后存活15次即可移入老年代。
JVM常见异常
java.lang.StackOverflowError
原因:线程请求的栈深度 超过了虚拟机允许的深度
public class Test {
//代码例子
public static void main(String[] args) throws Exception {
stackOverflowError();
}
private static void stackOverflowError(){
stackOverflowError();
}
}
线程栈帧结构图:
java.lang.OutOfMemoryError:Java heap space
原因:表示的是新对象不能在java heap中分配。
public class Test {
//代码例子 启动参数添加 -Xms20m -Xmx20m
public static void main(String[] args) throws Exception {
byte[] bytes = new byte[1024 * 1024 * 50];
}
}
java.lang.OutOfMemoryError:GC overhead limit exceeded
原因:GC回收时间过长时会抛出OutOfMemoryError,过长的定义是超过98%的时间在GC并且回收不到2%的堆内存。如果不抛出GC overhead limit,将会频繁GC,造成恶性循环,造成CPU高使用率。
public class Test {
//代码例子 启动参数添加 -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails
public static void main(String[] args) throws Exception {
int i = 0;
List<String> list = new ArrayList<>();
try {
while (true) {
list.add(String.valueOf(++i).intern());
}
} catch (Throwable t) {
System.out.println("i = " + i);
t.printStackTrace();
throw t;
}
}
}
GC详情,可以看出GC效果差,GC前后内存变化小
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7048K->7048K(7168K)] 9096K->9096K(9728K), [Metaspace: 2841K->2841K(1056768K)], 0.0388129 secs] [Times: user=0.25 sys=0.00, real=0.04 secs]
java.lang.OutOfMemoryError:Direct buffer Memory
NIO:1.8的新特性,NIO三大组件Selector、Channel、Buffer。它可以通过Native函数库直接分配堆外内存,然后通过在JVM堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样可以在某些场景中显著提高性能,因为避免了数据在Java堆和Native堆来回复制。
原因:NIO代码造成操作系统本地内存不足。
public class Test {
//查看最大的本地直接内存,默认为物理内存1/4
public static void main(String[] args) throws Exception {
System.out.println("maxDirectMemory = " + (sun.misc.VM.maxDirectMemory()/(double)1024/1024+"MB"));
}
}
public class Test {
//代码例子 启动参数添加 -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m
public static void main(String[] args) throws Exception {
//在Java堆中分配内存,属于GC管辖范围,由于需要拷贝,速度相对较慢
// ByteBuffer.allocate(1);
//在本地内存中分配内存,不属于GC管辖范围,不需要拷贝,速度相对较快
ByteBuffer.allocateDirect(1024 * 1024 * 10);
}
}
java.lang.OutOfMemoryError:unable to create new native thread
原因:程序创建的线程太多,超过了平台系统的极限(Linux系统单个进程默认可以创建的线程最大数为1024个)
public class Test {
//代码例子 请在Linux上执行
public static void main(String[] args) throws Exception {
for (int i = 0; ; i++) {
System.out.println("i = " + i);
new Thread(() -> {
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
ulimit -u #查看当前用户的最大线程数
vim /etc/security/limits.d/90-nproc.conf #修改创建线程上线
java.lang.OutOfMemoryError:Metaspace
1.8的新特性:Metaspace代替了永久代,是方法区在HotSpot中的实现,并不占用虚拟机内存,而是使用本地内存,也可以称它为native memory。主要存放:虚拟机加载的类信息、常量、静态变量、即时编译后的代码。
原因:Metaspace内存不足
public class Test {
//资源类
static class Obj {
}
//代码例子 启动参数添加-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
public static void main(String[] args) throws Exception {
int i = 0;
try {
while (true) {
i++;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Obj.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invoke(obj, args);
}
});
enhancer.create();
}
} catch (Throwable t) {
System.out.println("i = " + i);
t.printStackTrace();
}
}
}
记得引包
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>