我的第一篇博客:Java程序执行过程内存简单分析
Java虚拟机的内存模型图
(图片来源于:https://www.cnblogs.com/cjsblog/p/9850300.html)
特点
虚拟机栈: Java虚拟机栈为线程私有,它的生命周期与线程相同(随线程而生,随线程而灭), 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;当虚拟机栈可以动态扩展,但扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常;Java虚拟机栈描述的是Java方法执行的内存模型:每个方法执行的同时会创建一个栈帧。且Java 虚拟机栈使用的内存不需要保证是连续的。
本地方法栈: 在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(比如:Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
程序计数器: 程序计数器是一个记录着当前线程所执行的字节码的行号指示器。每个线程工作时都有属于自己的独立计数器。
通过一段简单的代码理解程序执行过程中内存的分析
public class Process {
String name;
int age;
public void show() {
System.out.println("名称:"+name+"\t年龄:"+age);
}
public static void main(String[] args) {
Process p1 = new Process();
p1.name = "张三";
p1.age = 20;
p1.show();
Process p2 = new Process();
p2.name = "李四";
p2.age = 23;
p2.show();
}
}
当程序从main方法开始执行时,在虚拟机栈中就创建了main方法和Process的栈帧,且在执行Process p1 = new Process();
时,在堆中创建了Process对象,从方法区中加载类的信息,其中包含了类Process中的各种属性。
public static void main(String[] args) {
Process p1 = new Process();
在执行完毕该语句后,Process栈帧出栈。
接下来执行赋值语句p1.name = "张三"; p1.age = 20; p1.show();
值得注意的是,其中 “张三” 字符串并不属于八种基本数据类型,因此在程序开始执行时字符串就会在常量池中加载完,当程序执行到p1.show;
时,创建p1.show的栈帧,在执行完后该栈帧出栈。如示意图所示。
至此p1对象的程序执行完毕,同理可以理解到p2对象的过程,但此时Process类信息已经在p1过程中加载了,因此在执行p2对象的程序过程中无需再次加载。
程序执行完后清空,程序退出。
方法区是一种规范,可以有不同的实现
规范就是大多数人都认同,且共同遵守的原理或者行为,不在乎内部如何操作;而实现则是个人根据自己需要进行的实际操作和行为。JVM的方法区就是一种规范,规定了它的作用和用途,以及能够存放哪些东西。一个是理论,一个是行为。
在JDK1.7及以前,将java类信息、常量池、静态变量等数据,存储在Perm(永久带)里,永久代和堆相互隔离,永久代的大小在启动JVM时可以设置一个固定值,不可变,类的元数据和静态变量在类加载的时候被分配到永久代里,当常量池回收或者类被卸载的时候,垃圾收集器会回收这一部分内存。
JDK1.7存储在永久代的部分数据就已经转移到Java Heap或者Native memory。但永久代仍存在于JDK 1.7中,并没有完全移除,譬如符号引用(Symbols)转移到了native memory;字符串常量池(interned strings)转移到了Java heap;类的静态变量(class statics variables )转移到了Java heap;即相对于1.7之前的区别就是将字符串常量池移到了堆中。
jdk1.8中把永久代给完全删除了,取而代之的是MetaSpace,运行时常量池和静态变量都存储到了堆中(仍然与堆不相连,但与堆共享物理内存,逻辑上可认为在堆中),MetaSpace存储类的元数据,MetaSpace直接申请在本地内存中(Native memory),这样类的元数据分配只受本地内存大小的限制。
(方法区部分内容参考自https://www.cnblogs.com/duanxz/p/3726574.html)
ps:第一篇博客难免会有瑕疵纰漏,还望不吝赐教,敬请雅正。