• JVM内存分区


    1、java虚拟机内存结构

     

    java程序的执行依赖于JAVA虚拟机(运行于机器内存中),其开始于一个main()方法,如果在一台机器上运行三个java程序,就需要三个java虚拟机。

    1.1 程序计数器

    • 功能:一块较小的内存,执行引擎Execution Engine通过改变计数器的值选取下一条需要执行的字节码指令。条件、循环,异常处理等都需要程序计数器来控制。
    • 线程私有。单核cpu同一时间只能执行一个线程,多线程程序需要进行线程切换,因此每个线程都需要程序计数器记录自己被执行到了哪一行
    • 异常:唯一一个不会产生异常的区域。
    • 备注:如果执行的是native方法,则计数器值为空。

    1.2 JVM 栈

    •  功能:用于方法的执行。每执行一次一个方法都会创建一个栈帧(从方法被调用到执行完成)入栈,函数调用结束(return或者异常)栈帧出栈,方法A调用方法B,则A入栈后,B入栈,当前正在执行的方法一定在栈顶。包括:存储局部变量表、操作数栈、动态链接、方法出口信息。
      • 局部变量表:函数参数、基本类型数据、对象引用(reference)等。
      • 操作数栈:计算中间结果。
    • 线程私有。生命周期同线程。
    • 异常:StackOverflowError(线程请求的栈深度大于虚拟机所允许的深度)和OutOfMemoryError(栈在扩展时无法申请到足够的内存)

    1.3 本地方法栈

    • 同JVM栈,区别在于本地方法栈为native方法服务。 java通过JNI调用其他语言代码编译得到的动态代码库dll。

    1.4 堆

    • 功能:存放对象实例。垃圾回收机制管理的主要区域,java堆可以细分为新生代、老生代等分区,分区是为了更好的分配和回收内存,堆不需要连续的内存空间
    • 线程共享。JVM内存中很大的一部分,虚拟机启动时创建。
    • 异常:堆中没有内存完成实例分配,并且堆无法再扩展时,抛出OutOfMemoryError异常。

    1.5 方法区

    • 功能: 存放被加载了的类信息(代表该类的class对象,作为该类各种数据的访问入口)、常量、静态变量、方法信息等方法区也不需要连续的内存。
    • 线程共享。属于垃圾回收机制管辖范围。
    • 异常:方法区无法满足内存分配时,抛出OutOfMemoryError异常。
    • 备注:运行时常量池也是方法区的一部分,包括字面常量(final)和符号引用(-类和接口的全限定名 CONSTANT_Class_info、-属性描述符CONSTANT_Fieldref_info、-方法描述符CONSTANT_Methhod_info两部分。

    另外:直接内存,其不属于JVM运行时数据区的一部分,但是该内存区域也被频繁的使用,并且也可能导致OutOfMemoryError异常出现。

    除程序计数器之外的其他四种内存空间都可以申请动态扩展,因此也都会出现内存溢出情况,由于各个分区的作用不同,内存溢出的原因也各不相同,堆溢出由于创建太多对象(可达的);栈溢出由于定义大量局部变量使栈帧变大,使用递归造成栈帧过多,线程过多导致栈内存溢出等;方法区溢出由于产生大量动态类并且不进行类卸载等。

    因为程序计数器、JVM栈、本地方法栈都是线程私有,因此不需要对其进行垃圾回收,垃圾回收主要针对堆和方法区。

    2、java继承中的内存分配

     1 class Person{
     2     public int age;
     3     public String name;
     4     public Person(){
     5         System.out.println("父类");
     6         say();
     7     }
     8     public void say(){
     9         System.out.println("有人说话。");
    10     }
    11 }
    12 class Student extends Person{
    13     public String school;
    14     public Student(){
    15         System.out.println("子类");
    16     }
    17     public void say(){
    18         System.out.println("学生"+name+age);
    19     }
    20 }
    21 public class TestOverride
    22 {
    23     public static void main(String[] args) {
    24         Student s = new Student();
    25         s.age=20;
    26         s.name="tom";
    27         s.say();
    28     }
    29 }

    输出结果为:

    父类       //先执行父类构造方法
    学生null0  //执行父类构造方法时,调用say()方法,因为子类重写了say()方法
    子类    //执行子类构造方法
    学生tom20

    1、虚拟机加载TestOverride类至方法区。

    2、执行main方法,将其入栈(JVM栈)。

    3、执行 Student s = new Student(); 加载Person类和Student类至方法区(类信息,常量,静态变量,成员方法),main栈帧中存储s,在堆中实例化对象时,根据方法区类信息,先执行父类构造函数,再执行子类构造函数。

    4、执行s.age=20,s.name="tom",通过栈帧中s变量找到堆中对象,为其赋值。

    5、调用say()方法,通过引用变量s持有的引用找到堆中的实例对象,通过实例对象持有的本类在方法区的引用,找到本类的类型信息,定位到say()方法。say()方法入栈。开始执行say()方法中的字节码。

    6、say()方法执行完毕,say方法出栈,程序回到main方法,main方法执行完毕出栈,主线程消亡,虚拟机实例消亡,程序结束。

    参考文献:https://blog.csdn.net/a910626/article/details/52318590

    参考文献:https://www.cnblogs.com/augus007/articles/10185796.html

  • 相关阅读:
    Poj3678:Katu Puzzle
    2-SAT
    Bzoj3238: [Ahoi2013]差异
    expressJS
    expressJS
    expressJS
    [转]View属性 之 paddingStart & paddingEnd
    在Activity之间使用Intent传值和Bundle传值的区别和方式
    [转]Java初始化顺序总结
    final关键字修饰的变量
  • 原文地址:https://www.cnblogs.com/simpleDi/p/11345784.html
Copyright © 2020-2023  润新知