JDK1.8之后包含1.8 将方法区称为 MetaSpace 元空间。
分配对象、new 实例。
堆内存当中划分为两个区域:老年代和新生代。
如何去划分老年代和新生代,根据对象的年龄。这个年龄是一个对象经过一次GC,如果还存在的话,年龄就加一。当年龄超过默认值(15)时,就会从新生代划分到老年代当中。
新生代new出来的对象是朝生夕死,将新生代划分为两个区域:Eden区,Survivor区。Survivor区又划分为S0、S1两个区域;
新生代内存分配情况是:Eden区80%,Survivor区20%(S0:10%,S1:10%)。
如果刚new出来的对象太大,超过了新生代的Eden区内存,会直接存入在老年代。
举例说明:
老年代:2G内存
新生代:1G内存
S0,S1各100MB
这是new一个900MB的对象,会直接分配在老年代(Old)里。
所有刚刚new出来的对象,就会分配在Eden区。
如果新生代(Young)区的Young GC之后对象的年龄不断的+1+1+1 > 年龄15之后,会将该对象存放到老年代(Old)区;
假如这时新生代(Young)区有120MB存活对象,S区不够放了,会跟老年代借20MB的空间存放,会触发担保机制,这20MB依旧还是属于老年代(Old)管理的。
如果老年代的内存不够用了,会触发 Old GC 也可 称为 Major GC 。Old GC会比较耗时。当然一旦触发了 Old GC(Major GC) 通常都会伴随着 Young GC(Minor GC) 。
调优的原则:
避免触发Full GC,换句话说避免触发Old GC(Major GC);如果要触发GC,尽量只触发Young GC(Minor GC)。
<img src="https://happyloves.oss-cn-hangzhou.aliyuncs.com/img/20200429224902.png" style="zoom:150%;" />
在IDEA中VM options设置JVM堆内存:-Xms30M -Xmx30M(设置堆内存30MB,最大30MB)
@RestController @RequestMapping("/test/jvm") public class TestJvmController { List<AuthAccount> list = new ArrayList<>(); @GetMapping("/jvmTest") public void jvmTest() { while (true) { list.add(new AuthAccount()); } } }
运行SpringBoot程序后、在JAVA安装目录中,找到bin文件夹下的 jvisualvm 工具(这个工具是JDK自带的), 首先还要安装Visual GC的插件才能查看到JVM GC运行时状况 。
之后等待程序运行后,打开这个 jvisualvm 工具就可以查看到JVM内存运行时的状况
如果堆内存中,没有可分配的内存空间了,就会报OOM。
同理方法区Metaspace也会报OOM,设置JVM中方法区大小:-XX:MetaspaceSize=40M -XX:MaxMetaspaceSize=40M。
栈也会报OOM,首先我们先测试栈的深度:
// 通过递归操作 public static long count = 0; public static void test(long i) { System.out.println(count++); test(i); } public static void main(String[] args) { test(count); }
通过测试我们发现,栈的默认深度是7000左右。之后就会报OOM错误。
可以根据需求去调整栈的深度大小;
一个栈的深度大小,太大或太小都会有弊端,太小的话影响方法链调用的深度、太大的话在整个JAVA进程当中它能够创建这样一个的线程的数量是有限的,如果太大会影响到其他线程创建栈的深度。
通过前人的经验来看,最佳值设置到5000左右就可以了。可以通过JVM参数去设置。
赵小胖个人博客