复习
前言
在Java中,我们定义了各式各样变量,这些变量,JVM都会为我们开辟相应的空间去存放,所以接下来就来看看这些变量到底是存放在JVM的哪里
内存区域
栈帧:一个方法对应一个栈帧,栈帧共有四块区域:局部变量、操作数栈、动态链接、方法出口
虚拟机栈:存放栈帧
堆:存放new出来的对象
方法区:存放类的信息、静态变量、常量池
首先分析上面这段代码,因为main()方法在Start类里面,所以Start类的信息首先被加载到JVM的方法区中
方法区中一般会存放类的加载信息以及静态变量和一些常量池,类被加载进来后,JVM会有一个执行引擎,寻找main()方法去执行,在main()方法中,首先创建了一个Cat的实例对象,先不要看等号右侧,先来看等号左侧,Cat cat,此时就是在main()方法栈帧中创建了一个局部变量:cat(Cat类的加载在上一节中已说明,此处不再重复),所以,这个cat会被存放到main()方法栈帧中的局部变量中,如下图
我们继续看代码,此时等号左侧cat的存放区域我们搞清楚了,那么再来看等号右侧的new Cat(),此时,是创建了Cat的一个实例对象,所以它是被存放到堆中的,如下图
我们继续看代码,创建完Cat类的实例对象后,我们执行了它的eat方法,eat方法的代码如下:
执行eat()方法,也就会将eat()方法的栈帧压入栈中,如下图
此时便将eat()栈帧压入了栈中,并且在eat()的栈帧中开辟一块局部变量的区域存放其flag变量,注意:因为flag变量是基本类型,所以它只在栈存储,不需要存放到堆中,当eat()方法执行完以后,这个方法已经结束了,那它就会出栈,出栈后如下图:
这里要注意的是,栈帧都是存放在虚拟机栈中,要遵循栈的规则,FILO,先进后出,从图中也可以感受到一个一个压入栈帧,出去的时候是从最后一个压入栈中的栈帧先执行的,类似于弹夹一样,后压入弹夹中的子弹是第一个被射出去的;接下里又执行了Cat的run()方法,run()方法内部如下图
run()方法中定义了Mouse类的实例对象,那么在JVM中就如下图所示:
因为run()方法中创建了对象实例,那么同样会到堆中开辟空间存放Mouse对象的实例,当执行完run()方法后,run()也要出栈,出栈后JVM中将会变成下图所示:
到此,JVM内存区域大致就讲完了,这其中还有一些概念没有讲,接下来就讲下这些概念
1、操作数栈
操作数栈中存放的是值,例如在eat()方法中,flag在创建时就被赋值为true了吗?并不是,在局部变量中,flag经历了从false到true,false是Boolean类型的默认值,所以,当flag被放入局部变量中时,它的初始值是false,那么true在哪里,它在操作数栈中,当所有的局部变量都被创建完毕后,执行引擎再依次将操作数栈中的数据赋值给变量,如下图
2、动态连接
动态链接是用来存放实例对象的地址值的,当我们创建了一个对象的实例,那如何将在局部变量中的变量指向堆中的实例呢?这时,动态链接就会去堆中获取实例的地址值
3、方法返回
在讲方法返回时,需要先讲一下程序计数器,程序计数器是用来记录代码执行的行标数的,这样当我们从执行完一个方法后,就不会不知道程序此时该执行哪里了,因为程序计数器中的行标数会告诉我们当前程序执行到了哪里,那么方法返回就是用来存放程序计数器记录的行标数的
4、本地方法栈、本地库接口、本地方法库
这三个区域是用来存放与本地方法相关的数据的,此处不需要关注,本地方法是用C语言编写的,是支撑java程序运行的方法,Object类中就有本地方法,如下图
此时,JVM内存结构基本讲解完了,接下来要思考一个问题,那就是JVM是支持多线程的,那么在上述过程中,哪些结构是共享区域,哪些是线程私有的区域呢?多线程模式下,虚拟机栈、程序计数器都是线程私有的,我们想一下,因为不同线程可能执行的代码是不同的,那么如果公用虚拟机栈时,就会违背栈的原则,可能在线程1执行的俩个方法之间压入了线程2执行方法的栈帧,并且线程2执行的方法很快执行完毕,此时,线程1的俩个方法都还没有执行完毕,那么线程2的栈帧要出占,但是线程1的一个方法栈帧要晚于线程2执行的方法栈帧进栈,此时就会出现问题;此外,程序计数器也一定是私有的,这个很好理解,因为每个线程执行的代码不同,那么程序计数器肯定是私有的,此外,本地方法栈也是私有的,此处就不做讲解,我们最后画一个完整的JVM内存结构来体会一下
注意:为了方便理解多线程模式下,所以图以多线程模式展现,实际的JVM内存结构,上图中三个线程中的结构,只需保留一份即可
PS:上图中run()出栈后在堆中留下了一个没有任何对象引用的mouse实例对象,那么这个对象要怎么处理呢?下节课讲解