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