当前位置:编程学习 > > 正文

jvm内存结构及运行原理(详解JVM系列之内存模型)

时间:2021-11-08 16:55:52类别:编程学习

jvm内存结构及运行原理

详解JVM系列之内存模型

1. 内存模型和运行时数据区

这一章学习java虚拟机内存模型(Java Virtual machine menory model),可以这样理解,jvm运行时数据库是一种规范,而JVM内存模型是对改规范的实现

jvm内存结构及运行原理(详解JVM系列之内存模型)

java虚拟机重点存储数据的是堆和方法区,所以本章节也重点从这两个方面进行比较详细描述。堆和方法区是内存共享的,而java虚拟机栈、Native方法栈、程序计数器是线程私有的

jvm内存结构及运行原理(详解JVM系列之内存模型)

2、思维导图和图例

jvm内存结构及运行原理(详解JVM系列之内存模型)

一个是非堆区(方法区),方法区也一般被称之为“永久代”。另外一个是堆区,分为young区和old区,young区又分为两个部分,一个是Eden区,一个是Survivor区(S0+S1),S0区也可以称之From区,S1也可以称之为To区

jvm内存结构及运行原理(详解JVM系列之内存模型)

3、对象向JVM申请空间

jvm内存结构及运行原理(详解JVM系列之内存模型)

4、为什么需要Survivor区?

为什么需要Survivor区?只有Eden不行吗?

假设不设计出Survivor区,Eden区进行一次MinorGC,对象就直接被送到Old区,这样一来Old区很快就被填满,Old区一满,就会进行FullGC(Old区会进行MajorGC,一般伴随着MinorGC),FullGC是很耗时的,所以设计出Survivor区的目的是减少对象被送到Old区,有一个过渡的Survivor区

补充:Minor GC:新生代
Major GC:老年代
Full GC:新生代+老年代
Eden:S1:S2是8:1:1

5、为什么需要两个Survivor区?

需要两个Survivor区的目的是为了避免内存碎片化。为什么这么说?
假设只设计出一个Survivor区,一旦Eden区满了,就会进行Minor GC,Eden区存活的对象就会被移动到Survivor区,等下一次Eden区满时候,问题就来了,进行MinorGC就将Eden区对象硬放到Survivor区,这样就导致了对象所占的内存是不连续的

6、例子进行验证

堆内存溢出

  • import lombok.Data;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @RestController
    public class HeapController {
    
        List<Foo> list = new ArrayList<Foo>();
        @GetMapping(value = {"heap"})
        public String heapTest() {
            while (true) {
                list.add(new Foo());
            }
        }
    
    
        @Data
        class Foo {
            String str;
        }
    }
    
  • 访问接口,出现内存溢出;

    java.lang.OutOfMemoryError: Java heap space

    ...

    可以设置参数:比如-Xms64M -Xmx512M

    方法区内存溢出

    使用asm,maven配置:

  • <dependency>
      <groupId>asm</groupId>
      <artifactId>asm</artifactId>
      <version>3.3.1</version>
    </dependency>
    
  • 编写代码,向方法区中添加Class的信息,注意,电脑性能不够好,不要执行此代码,很容易,造成电脑重启,太吃内存,也可以调小循环次数

  • import org.objectweb.asm.ClassWriter;
    import org.objectweb.asm.MethodVisitor;
    import org.objectweb.asm.Opcodes;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MyMetaspace 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();
          MyMetaspace test = new MyMetaspace();
          byte[] code = cw.toByteArray();
          Class<?> exampleClass = test.defineClass("Class" + i, code, 0,
                  code.length);
          classes.add(exampleClass);
        }
        return classes;
      }
    }
    
  • 方法区测试接口:

  • import com.example.jvm.jvmexceptionexample.asm.MyMetaspace;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @RestController
    public class NonHeapController {
    
        List<Class<?>> list = new ArrayList<Class<?>>();
    
        @GetMapping(value = {"/noheap"})
        public String noheap() {
            while (true) {
                list.addAll(MyMetaspace.createClasses());
            }
        }
    
    }
    
  • java.lang.OutOfMemoryError: Metaspace

    at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.5_54]

    处理方法,设置Metaspace的大小,比如-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=512M

    Java虚拟机栈

    在前面学习,java虚拟机栈是通过栈帧方式存储,一个方法对应一个栈帧,按照队列模式进栈,所以要测试程序导致java虚拟机栈出现问题,可以通过递归方法方式进行测试:

  • import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class StackController {
    
        public static long count = 0;
    
        public static void add(long i) {
            count ++ ;
            add(i);
        }
    
        @GetMapping(value = {"stack"})
        public void stack() {
            add(1);
        }
    
    }
    
  • StackOverflow,栈溢出异常:

    java.lang.StackOverflowError: null

    at com.example.jvm.jvmexceptionexample.controller.StackController.add(StackController.java:14) ~[classes/:na]

    处理方法,设置-Xss256k:设置每个线程的堆栈大小。JDK 5以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K

    以上就是详解JVM系列之内存模型的详细内容,更多关于JVM 内存模型 内存结构的资料请关注开心学习网其它相关文章!

    上一篇下一篇

    猜您喜欢

    热门推荐