搞定java面试系列--jvm2 内存模型

1.1类加载完以后JVM干了什么?

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。

1.1.1JVM的内存模型

首先我们来了解一下JVM的内存模型的怎么样的:

  • 基于jdk1.8画的JVM的内存模型

《搞定java面试系列--jvm2 内存模型》

再来看看每个区域究竟存储的是什么(干的是什么):

  • 堆:存放对象实例,几乎所有的对象实例都在这里分配内存,同时包含一个常量池(final),是由1.7以前版本的方法区转移过来的。所以1.8之后的string常量都是存在堆里面。
  • 虚拟机栈:虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息,帧数超过限制(-Xss),就会出现StackOverFlow(=SOF)错误。另外超过线程分配的内存大小,也会报OOM错误。
  • 本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务。
  • 方法区:所有线程共享。存放class加载相关信息。(元空间)
  • 程序计数器:当前线程所执行的字节码的行号指示器

回到开始的代码看过程:

public class TestMain {
    public static void main(String[] args){
        JavaTestBean javaTestBean = new JavaTestBean();
        javaTestBean.setName("linhua");
        System.out.println(javaTestBean);
    }
  • 1、通过java.exe运行JavaMain.class,随后被加载到JVM中,元空间存储着类的信息(包括类的名称、方法信息、字段信息..)。
  • 2、然后JVM找到JavaTest的主函数入口(main),为main函数创建栈帧,开始执行main函数
  • 3、main函数的第一条命令是JavaTestBean javaTestBean = new JavaTestBean();就是让JVM创建一个JavaTestBean对象,但是这时候方法区中没有JavaTestBean类的信息,所以JVM马上加载JavaTestBean类,把JavaTestBean类的类型信息放到方法区中(元空间)
  • 4、加载完JavaTestBean类之后,Java虚拟机做的第一件事情就是在堆区中为一个新的JavaTestBean实例分配内存, 然后调用构造函数初始化JavaTestBean实例,这个JavaTestBean实例持有着指向方法区的JavaTestBean类的类型信息(其中包含有方法表,java动态绑定的底层实现)的引用
  • 5、当使用javaTestBean.setName("Java3y");的时候,JVM根据引用找到JavaTestBean对象,然后根据JavaTestBean对象持有的引用定位到方法区中JavaTestBean类的类型信息的方法表,获得setName()函数的字节码的地址
  • 6、为setName()函数创建栈帧,开始运行setName()函数

1.1.2 内存可能溢出的模块

  1. 虚拟机栈溢出:
public class TestMain {
    private static int index = 1;
    public void call(){
        index++;
        call();
    }
    public static void main(String[] args) {
        TestMain mock = new TestMain();
        try {
            mock.call();
        } catch (Throwable e) {
            System.out.println("Stack deep : " + index);
            e.printStackTrace();
        }
    }
}

运行两次结果:可以看出栈的深度是不一样的

《搞定java面试系列--jvm2 内存模型》

《搞定java面试系列--jvm2 内存模型》

2、堆内存是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过 GC 进行回收。当申请不到空间时会抛出 OutOfMemoryError。下面我们简单的模拟一个堆内存溢出的情况:

public class HeapOomMock {
    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<byte[]>();
        int i = 0;
        boolean flag = true;
        while (flag){
            try {
                i++;
                list.add(new byte[1024 * 1024]);//每次增加一个1M大小的数组对象
            }catch (Throwable e){
                e.printStackTrace();
                flag = false;
                System.out.println("count="+i);//记录运行的次数
            }
        }
    }
}

1.2、PermGen(永久代)

  绝大部分 Java 程序员应该都见过 “java.lang.OutOfMemoryError: PermGen space “这个异常。这里的 “PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。我们现在通过动态生成类来模拟 “PermGen space”的内存溢出

1.3、Metaspace(元空间)

  其实,移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。

不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:

参考链接:http://www.cnblogs.com/paddix/p/5309550.html

1.4 碰到内存分配免不了要处理类似这些问题了:

String aa = new String("aa");
aa.intern();
String bb = "bb";

这个问题可以参考这个链接:https://blog.csdn.net/u013366617/article/details/83618361

 

    原文作者:java内存模型
    原文地址: https://blog.csdn.net/baidu_27776187/article/details/87111058
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞