6.认识JVM-JVM内存模型
目录
1.认识JVM之Classfile:https://www.cnblogs.com/nuti/p/16270652.html
2.认识JVM之类加载机制:https://www.cnblogs.com/nuti/p/16270672.html
3.认识JVM之运行时数据区: https://www.cnblogs.com/nuti/p/16270703.html
4.认识JVM之编译器: https://www.cnblogs.com/nuti/p/16270741.html
5.认识JVM之垃圾收集器: https://www.cnblogs.com/nuti/p/16270755.html
6.认识JVM之JVM内存模型: https://www.cnblogs.com/nuti/p/16270816.html
书接上文,https://www.cnblogs.com/nuti/p/16270755.html
看到这里大概对JVM是什么样子,过程有了了解,那么这里就深入理解一下JVM内存模型吧
0.整体描述
上一篇对运行时数据区整体都描述了一番,其实重点的数据储存还是在堆和方法区(非堆),所以内存的设计也注重从这两方面展开,对于虚拟机栈 本地方法栈 程序计数器这些都是线程私有的
一块是非堆区,一块是堆区
堆区分为两大块:一个是Old区,一个是Young区
Young区分为两大块:一个是Survivor区(S0+S1),一块是Eden区
S0和S1一样大,也可以叫From和To
也可以使用java自带的jvisual工具看一下堆和非堆(在java目录的bin下面)
方法区jdk7或者之前叫 perm space 永久代jdk8或者以后叫 meta space 元空间
1.设计理解
这里先不讨论什么具体的GC这个放在下面一个章节具体讲解
下面会一步步理解为什么要分young区 和使用 eden区 和Survivor区
1.1.过程理解
1.1.1.无young区
在没young区的时候
空间很大回收起来比较耗时,其实绝大部分对象的生命周期都很短不需要长期停留在内存空间
那如果垃圾回收之后 那么内存空间存在空间碎片fragmented造成空间浪费,有比较大的对象 就无法存入
这个时候Garbage Collector会觉得应该是空间不够了所以启动垃圾收集线程 gc thread 一定会抢夺cpu的时间片会和业务代码抢夺cpu资源
1.1.2.引入young区
这样引入young区之后 不必要扫描全部的堆内存 只需要扫描young区就可以 这也节省了性能的消耗
那么young区什么时候到老年代,他会有一个阈值的判断这个后面在说
这样引入了young区之后 还是有会空间碎片的情况产生,那么怎么办?
1.1.2.引入eden、Survivor区
这样换取了eden区的空间连续性 但是再次发生young gc的时候
他会对整个young区进行垃圾回收 这样的话Survivor区又会有空间碎片的情况发生
Eden区和Survivor区哪个大?
肯定是Eden区,因为每次创建对象还是在Eden区产生,这些都是一些朝生夕死的对象很快就会被回收
Survivor区还是会发生空间碎片的情况产生。那么怎么办?
1.1.2.1.拆分Survivor区
再打开jvisualvm看一下是不是一样的
那么问题来了?那age到什么时候去老年代?
1.他会有一个阈值的判断 到达一定的阈值就会去老年代(因为对象头的age只有4位那么撑死了就是15岁)
2.如果s0或者s1空间不够用了 就会向old区借空间 (空间担保机制)
3.大文件 young区放不下。他会直接去老年代
1.2.结论流程图
2.代码演示
创建一个springboot的demo 启动项目观察jvm内存模型
2.1.堆
限制一下堆内存大小-Xmx50M -Xms50M
@RestController public class HeapController { List<Worker> list=new ArrayList<Worker>(); @GetMapping("/heap") public String heap() throws Exception{ while(true){ list.add(new Worker()); }}}
打开打开工具看看过程 最后控制台肯定会报OOM
2.2.方法区
限制一下大小 -XX:MetaspaceSize=50M -XX:MaxMetaspaceSize=50M
自己创建类很慢 我可以用一下asm写好的方法 引入一下他的依赖
<dependency> <groupId>asm</groupId> <artifactId>asm</artifactId> <version>3.3.1</version> </dependency>
工具类
public class MetaspaceUtil extends ClassLoader { public static List<Class<?>> createClasses() { List<Class<?>> classes = new ArrayList<Class<?>>(); for (int i = 0; i < 10000000; ++i) { ClassWriter cw = new ClassWriter(0); cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null); MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); mw.visitVarInsn(Opcodes.ALOAD, 0); mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); mw.visitInsn(Opcodes.RETURN); mw.visitMaxs(1, 1); mw.visitEnd(); MetaspaceUtil test = new MetaspaceUtil(); byte[] code = cw.toByteArray(); Class<?> exampleClass = test.defineClass("Class" + i, code, 0, code.length); classes.add(exampleClass); } return classes; }
入口
@RestController public class NonHeapController { List<Class<?>> list=new ArrayList<Class<?>>(); @GetMapping("/nonheap") public String heap(){ while(true){ list.addAll(MetaspaceUtil.createClasses()); } } }
最后也会oom
方法区的oom在控制台会看不到溢出的情况
因为:
java7 perm space空间是在java内存中
java8 metaspac空间用的是本地内容 系统的内存