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

原文地址:https://www.cnblogs.com/simpleDi/p/11345784.html