JVM内存模型主要分为五大区域:栈、堆、本地方法栈、程序计数器、方法区。
本地方法栈:
跟虚拟机栈非常相似,也是线程私有的,不过虚拟机栈是针对Java方法,而本地方法栈是针对native方法,也就是底层方法。
由于Java是跨平台语言,导致的它不得不牺牲一些对底层方法的控制,而要实现这些底层方法的控制,就需要用到native方法,而本地方法栈就是针对native方法的。
程序计数器:
每一个线程内部都有自己的程序计数器,是相互独立的,保证每个线程的程序运行不会出错,因此程序计数器是线程私有的。
在idea将Java代码运行时都会被转译成字节码文件,字节码文件是二进制文件,识别起来比较困难,所以在编译时用到计数器,为编译好的字节码添加行号,后期调用的时候就能按顺序分条执行。记录程序运行的位置。
虚拟机栈:
是线程私有的,生命周期和线程相同,它描述的是Java方法执行的内存模型,每一个虚拟机栈都有自己的栈针,每一个方法的执行都是入栈和出栈的过程(想到关于栈和队列的区别,有一个形象的例子,队列是先进先出 吃了拉,栈是先进后出 吃了吐 ),每一个栈中都有着局部变量表和操作数栈,局部变量表是变量值的存储空间,用于存放方法参数和方法内部定义的局部变量,操作数栈存储的数据与局部变量表一致,是通过弹栈和压栈来进行访问的,操作数栈可以理解为Java虚拟机栈中的一个用于计算的临时数据存储区。(Java的基本数据类型大部分都是存储在栈的,除了一些引用变量和引用类型。String不是基本据类型!基本数据类型就只有八个,数值型:byte,short,int,long,浮点型:float,double,字符型:char,布尔型:boolean。
方法区:
也叫永久区,jdk8以后叫元数据区,是线程共享,里面存储着被虚拟机加载的类信息,常量,静态变量。
程序执行时,将字节码文件加载到方法区的常量池,存储一些和类有关的属性,方法区的清理也是通过垃圾回收,回收目标主要是常量池中废弃的常量和不再使用的类型。
堆:
堆区用于存放所有new出来的对象和数组,是线程共享的,堆区中的对象和栈区中的对象往往是成对出现的,一般的程序是通过栈区的对象引用来访问堆区的对象,因为是共享的,意味着对个对象引用可以指向同一个对象,在没有引用变量的指向时,就会变成垃圾,不被使用,被垃圾回收机制清理。
看一下代码的执行结果。
public static void main(String[] args) { ArrayList<String> a = null; test(a); System.out.println(a.size()); } public static void test(ArrayList<String> a){ a = new ArrayList<>(); a.add("a"); }
执行后会返回什么?打印出1吗?
结果显然不是,会在 System.out.println(a.size()); 时抛出空指针异常。
我们分析下程序执行的内存模型。
首先定义了一个ArrayList类型的a变量,a是一个指针,不过现在指向的是null。
调用了test方法,传入形参a,实际上传的是a的副本。
在静态方法test中,new了一个arrayList,在堆中开辟了空间存储了new的对象,将a指向堆中new出来对象的地址。
然后执行 a.add("a");
test方法结束,销毁test方法执行时产生的局部变量。
回到main方法中,打印a.size时的a还是之前声明的指向null的变量。故抛出空指针异常。
https://www.bilibili.com/video/BV12t411u726?from=search&seid=1631061805620865620