Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为多个区域,这些区域各有自己的用途以及独特的创建和销毁时间,今天就来揭开这些不同的数据区域的神秘面纱
先来一张最经典的图:
程序计数器
程序计数器里记录的是当前线程字节码指令执行到的位置。
程序计数器的生命周期是随着一条线程的启动而创建的,每一个线程独有一个程序计数器,多个线程之间互不影响。(可以理解为Java中的ThreadLocal,相关文章可参考:ThreadLocal及InheritableThreadLocal的原理剖析)
我们知道多线程其实就是通过线程轮流切换并分配处理器执行时间的方式实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。当切换到另外一条线程时,若是当前线程没有程序计数器来记录此刻的执行位置,下次处理机再执行这条线程时就不知道该从哪开始了。
本地方法栈和虚拟机栈可以统称为栈,由于本地方法栈是jvm调用操作系统native方法所使用的栈且它们的作用是非常相似的,所以这里重点看一下虚拟机栈。
虚拟机栈与程序计数器一样,也是线程私有的,每个线程都会有一个自己的虚拟机栈。它描述的java方法执行的内存模型
为什么是Java方法执行的内存模型呢?
在虚拟机中,每一次方法调用都会创建栈帧,这个栈帧的生命周期就伴随着这个方法的执行周期。一个栈帧的组成主要包含以下部分:
局部变量表、操作数栈、常量池指针、动态地址、方法返回地址等信息。
局部变量表
操作数栈
Java中所有的参数传递都是依靠操作数栈进行的,例如如下代码:
static int methed1(int a,int b){ int c=0; c=a+b; return c; }
其实这短短的三行代码执行的过程是这样的:
1. 0压栈2. 弹出int存放局部变量c3. 局部变量a压栈4. 局部变量b压栈5. 弹出两个变量求和,将结果压栈6. 弹出结果放到局部变量c7. 局部变量c压栈8. return
因为方法中有可能使用类中的常量,所以必须有指向常量池的指针。
动态链接
在虚拟机运行的时候,运行时常量池会保存每个方法的间接引用,如果栈帧A的方法想调用栈帧B的方法,那么这个虚拟机的方法调用指令就会以B方法的符号引用作为参数,但是因为符号引用并不是直接指向代表B方法的内存位置,所以在调用之前还必须要将符号引用转换为直接引用,然后通过直接引用才可以访问到真正的方法,这时候就有一点需要注意,如果符号引用是在类加载阶段或者第一次使用的时候转化为直接应用,那么这种转换成为静态解析,如果是在运行期间转换为直接引用,那么这种转换就成为动态连接。
方法返回地址
方法的返回分为两种情况
堆
在我们的程序中,跟我们打交道最多的就是堆里的对象了。基本上所有(不包括常量池中存在的)通过new操作创建的对象都会保存在堆中。所以与栈的线程私有不同,堆是所有线程共享的(毕竟不共享难道每个线程调用时都new一次对象岂不是疯了),所以它也是虚拟里最大的一块。关于堆的更多内容请持续关注博客更新
方法区同样是各个线程共享的内存区域,它主要存储已经被虚拟机加载的类信息
存放该类所用到的常量的有序集合
当类和接口被加载到JVM后,对应的运行时常量池就被创建出来了,与常量池的不可变不同,运行时常量池是可变的,比如String的intern方法就可以做到
public class User{ private String name; public User(String name){ this.name = name; } //省略getset方法}
public class Test{ public static void main(String[] args){ User user1=new User("张三"); User user2=new User("李四"); }}
分析上方的一段代码,即可得到下方的图