美文网首页
Java基础->JVM内存划分

Java基础->JVM内存划分

作者: 杨0612 | 来源:发表于2020-06-19 10:03 被阅读0次

    说到虚拟机的内存划分,一般会笼统地分为堆跟栈,因为这两块在开发当中接触较多。

    其实应该分为:虚拟机栈、本地方法栈 、程序计数器、堆、方法区,这5大部分。
    1.虚拟机栈

    对栈帧进行管理,即入栈和出栈。
    (1)该区域是线程私有;
    (2)当线程请求栈深度超出虚拟机栈允许的范围,则抛出StackOverflowError,例如递归调用;
    (3)当虚拟机栈超出可用的内存大小,则抛出OutOfMemoryError;
    (4)创建线程,它就被创建,线程执行完,它也就被销毁。

    1.1 栈帧

    用来支持方法调用的数据结构;执行某个方法,就会创建一个栈帧压入虚拟机栈,方法执行完,栈帧就被虚拟机栈弹出,内存自动清理;栈帧包括局部变量表、操作数栈、动态链接、返回地址。

    Tips1:每个方法从开始到执行完成,都有一个对应的栈帧在虚拟机栈中入栈(创建)和出栈(清理)。
    Tips2:JVM基于栈,也就是内存,DVM基于寄存器,寄存器比内存快,所以DVM比内存快;
    1.1.1 局部变量表

    存储方法参数以及局部变量,基本数据类型以及对象的引用都存放在局部变量表中;表的大小在Java编译成class文件时已经确定了。

    Tips:局部变量表下标为0的位置默认存储this。
    1.1.2 操作数栈

    是一个栈类型的数据结构,方法执行时,将局部变量表中的元素按照指令的要求压入或者弹出操作数栈。

    1.1.3 动态链接

    常用在多态链接以及so库的动态链接 ,以下代码在调用eat方法的时候,会用到动态链接;

    //Girl Boy分别是Person的子类
    public void add(){
             Person p=new Girl();
             p.eat();
             p=new Boy();
             p.eat();
    }
    
    1.1.4 返回地址

    用来帮助当前方法恢复到它的调用位置。
    正常退出时,返回地址为程序计数器的值。
    异常退出时,返回地址是通过异常处理器表确定的。

    1.1.4 实例讲解

    以下是比较简单的运算,通过查看字节码可以看到具体的运算过程;

    public void add(){
              int i=1;
               int j=2;
              return i+j;
    }
    

    开始,虚拟机栈压入该栈帧;
    (1)locals=3,局部变量表有三个元素,默认第0个元素为this;
    (2)iconst_1,将常量1压入操作数栈;
    (3)istore_1,将操作数栈 栈顶元素(值为1)存入局部变量表,下标为1;
    (4)iconst_1,将常量0压入操作数栈;
    (5)istore_2,将操作数栈 栈顶元素(值为2)存入局部变量表,下标为2;
    (6)iload_1、iload_2,局部变量表中下标1、2的元素压入操作数栈;
    (7)iadd,将栈顶元素分别出栈相加,把结果压入操作数栈;
    (8)ireturn,返回到调用处;
    最后,虚拟机栈弹出该栈帧;

      public int add();
        descriptor: ()I
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=1
             0: iconst_1
             1: istore_1
             2: iconst_1
             3: istore_2
             4: iload_1
             5: iload_2
             6: iadd
             7: ireturn
    
    2.本地方法栈

    与虚拟机栈类似,只不过是针对native本地方法。HotSpot以及ART将两者合二为一 ,本地方法栈也会抛出 StackOverflowError 和OutOfMemoryError。

    3.程序计数器

    (1)计数器是线程私有的;
    (2)用于记录当前线程指令执行的位置;
    (3)计数器没有OOM;
    (4)创建线程,它就被创建,线程执行完,它也就销毁;
    (5)当程序在执行native方法时,该计数器值为空;
    (6)线程执行、线程恢复依赖这个值。

    4.堆

    (1)该区域是线程共享的,不过可以通过ThreadLocal划分出线程私有的区域;
    (2)存放对象以及数组;
    (3)发生OOM的主要区域,区域当前所占内存加上需要分配的内存超过系统允许的值,则抛出OOM异常。

    5.方法区

    (1)该区域是线程共享的;
    (2)保存类的Class对象(版本、属性、方法、接口)、静态变量、常量等;

    Tips:常量如果没有任何对象引用,在GC必要时,会被移出方法区,Class对象也是一样,如果没有任何对应的实例、ClassLoader被回收、没有被任何地方反射调用Class,就可能会被回收。

    (3)也会出现OutOfMemoryError,例如加载的类太多。

    6.实例分析哪些变量在栈和哪些在堆

    (1)先分析test方法,age对应的值30,aArray引用,a1引用,都在栈中,具体点就是在局部变量表中,当方法执行完,局部变量表被清空,变量就失效;aArray、a1引用的对象都在堆中,注意aArray数组每个元素都是引用,引用的对象都在堆中;
    (2)外部调用的 A a1 = new A(),a1引用也在栈中,引用的对象在堆中,那么就引申一个问题:a1对象中的name、array引用、array对象在哪里呢?是都在堆中。

    Tips:只有方法中的局部变量才会在栈中。
    public class A {
        String name;
        String[] array=new String[10];
    
        public void test() {
            int age = 30;
            String[] aArray = new String[10];
            A a1= new A();
        }
        
        //外部调用
        A a1  = new A();
    }
    
    7.JVM、DVM、ART区别

    (1)JVM是Java虚拟机,是实现了以上内存划分的实例,它基于栈(我理解是栈帧),执行的是class文件;
    (2)DVM是Dalvik VM,是Google设计用于Android平台的Java虚拟机,它是基于寄存器,执行的dex文件;

    Tips:dex文件是通过Dex工具打包而成的,是多个class文件的集合,它对class文件去冗余,例如多个class文件包含同一个字符串常量,就会将他们合并在同一个;多个class文件合成一个dex文件,带来的问题就是方法数变多,超过65535个;

    (3)DVM的堆跟JVM的稍有不同,前者分成Active堆以及Zgoyte堆,Active堆跟JVM的堆一样存放创建的对象以及数组,而Zgoyte堆就是从Zgoyte复制给过来的那部分,例如预加载的资源;
    (4)ART大体跟DVM差不多,增加了AOT预编译技术,也就是在安装Apk时,提前将字节码转换成机器码,这样App运行速度就快,不过明显能感觉到安装速度变慢了,ART是Android 5.0以后默认的虚拟机;
    (5)无论是JVM、DVM、ART,堆内存都是按需分配的,随着指令的执行不断地向系统申请内存,直到最大可使用内存;

    Tips:堆内存是按需申请的,指的是物理内存。应用启动后,就会得到一个Runtime.getRuntime().maxMemory()值那么大的虚拟内存,目的就是防止后续虚拟内存动态增长,不断地需要进行数据拷贝。

    (6)Android几个重要的指标:

    Runtime.getRuntime().maxMemory() / 1024 * 1024 ;//最大可使用堆内存;
    Runtime.getRuntime().totalMemory() / 1024 * 1024 ;//目前可使用堆内存,包括freeMemory()
    Runtime.getRuntime().freeMemory() / 1024 * 1024 ;//目前空闲内存
    
    7.总结

    从下图可以清晰看出:
    (1)Java虚拟机内存划分为堆、虚拟机栈、本地方法栈 、方法区、程序计数器;
    (2)堆、方法区是线程共享的,虚拟机栈、本地方法栈 、程序计数器是线程私有;
    (3)以线程1为例,线程1有对应的虚拟机栈,当执行到某个方法就会产生一个栈帧,每个栈帧包含局部变量表、操作数栈、返回地址、动态链接;


    JVM虚拟机内存划分.png

    以上内存划分,是Java虚拟机规范中定义的内容,不同的虚拟机有不同实现。

    以上分析有不对的地方,大家可以讨论下,互相学习哦!

    相关文章

      网友评论

          本文标题:Java基础->JVM内存划分

          本文链接:https://www.haomeiwen.com/subject/wgiyxktx.html