概述
JVM在运行代码时,他使用多块内存空间,不同的内存空间用来放不同的数据,然后配合代码流程,让系统运行起来。
存放类加载信息
举个最简单的例子,比如现在知道了JVM会加载类到内存里来供后续运行,所以JVM里就必须有一块内存区域,用来存放我们写的那些类。
这个区域在JDK 1.8以前的版本里叫方法区,代表JVM中的一块区域。主要是放从“.class”文件里加载进来的类,还会有一些类似常量池的东西放在这个区域里。但是在JDK 1.8以后,这块区域的名字改了,叫做“Metaspace”,可以认为是“元数据空间”这样的意思。当然这里主要还是存放我们自己写的各种类相关的信息。
public class Client {
public static void main(String[] args) {
//使用支付宝付款
Shopping shop = new Shopping(new Alipay());
shop.payMoney();
}
}
public class Shopping
{
private PayMethod payMethod;
public Shopping(PayMethod payMethod)
{
this.payMethod = payMethod;
}
//付钱,支付用什么方式付钱,客户自己选择
public void payMoney()
{
String name = payMethod.getClass().getName();
System.out.println("我在购物,现在要付钱了");
payMethod.payMoney();
}
}
public class Alipay implements PayMethod
{
@Override
public void payMoney()
{
System.out.println("我在使用支付宝支付");
}
}
![image.png](https://img-blog.csdnimg.cn/img_convert/2f72a7cd4a9be28a3a995c51a0cbdf5a.png#averageHue=#f7f7f7&crop=0&crop=0&crop=1&crop=1&height=377&id=PokIW&margin=[object Object]&name=image.png&originHeight=753&originWidth=961&originalType=binary&ratio=1&rotation=0&showTitle=false&size=48746&status=done&style=none&title=&width=480.5)
代码运行起来时,需要执行代码中的一个一个的方法,运行方法的时候,方法里面有很多变量之类的东西,需要放在某个内存区域里,如果代码里创建一些对象,这些对象也需要内存空间来存放。
程序计数器
我们写好的Java代码会被翻译成字节码,对应各种字节码指令, 首先Java代码被编译成字节码指令,然后字节码指令一定会被一条一条执行,这样才能实现代码执行的效果。所以当JVM加载类信息到内存之后,实际就会使用自己的字节码执行引擎,去执行代码编译出来的代码指令。
在执行字节码指令的时候,JVM里就需要一个特殊的内存区域了,那就是“程序计数器”
这个程序计数器就是用来记录当前执行的字节码指令的位置的,也就是记录目前执行到了哪一条字节码指令。
JVM是支持多个线程的,所以其实代码可能会开启多个线程并发执行不同的代码,所以就会有多个线程来并发的执行不同的代码指令因此每个线程都会有自己的一个程序计数器,专门记录当前这个线程目前执行到了哪一条字节码指令了。
Java虚拟机栈
Java代码在执行的时候,一定是线程来执行某个方法中的代码,哪怕就是下面的代码,也会有一个main线程来执行main()方法里的代码,在main线程执行main()方法的代码指令的时候,就会通过main线程对应的程序计数器记录自己执行的指令位置。
public class Client {
public static void main(String[] args) {
//使用支付宝付款
Shopping shop = new Shopping(new Alipay());
shop.payMoney();
}
}
在方法里,我们经常会定义一些方法内的局部变量,比如在上面的main()方法里,其实就有一个“shop
”局部变量,他是引用一个Shopping
实例对象的,关于这个对象先别去管他,先来看方法和局部变量。
因此,JVM必须有一块区域是来保存每个方法内的局部变量等数据的,这个区域就是Java虚拟机栈。每个线程都有自己的Java虚拟机栈,比如这里的main线程就会有自己的一个Java虚拟机栈,用来存放自己执行的那些方法的局部变量。如果线程执行了一个方法,就会对这个方法调用创建对应的一个栈帧。
栈帧里就有这个方法的局部变量表 、操作数栈、动态链接、方法出口等东西。先关注局部变量。
比如main线程执行了main()方法,那么就会给这个main()方法创建一个栈帧,压入main线程的Java虚拟机栈同时在main()方法的栈帧里,会存放对应的“shop
”局部变量。然后假设main线程继续执行Shopping
对象里的方法,比如下面这样,就在“payMoney
”方法里定义了一个局部变量:“name
”
如果“payMoney
”方法执行完毕了,就会把“payMoney
”方法也从Java虚拟机栈里出栈。
上述就是JVM中的“Java虚拟机栈”这个组件的作用**:调用执行任何方法时,都会给方法创建栈帧然后入栈。**在栈帧里存放了这个方法对应的局部变量之类的数据,包括这个方法执行的其他相关的信息,方法执行完毕之后就出栈。
Java堆内存
Java堆内存,这里就是存放我们在代码中创建的各种对象的。
比如下面的代码:
public class Client {
public static void main(String[] args) {
//使用支付宝付款
Shopping shop = new Shopping(new Alipay());
shop.payMoney();
}
}
上面的“new Shopping()
”这个代码就是创建了一个Shopping
类的对象实例,这个对象实例里面会包含一些数据,如下面的代码所示。
public class Shopping {
private PayMethod payMethod;
public Shopping(PayMethod payMethod) {
this.payMethod = payMethod;
}
//付钱,支付用什么方式付钱,客户自己选择
public void payMoney() {
String name = payMethod.getClass().getName();
System.out.println("我在购物,现在要付钱了");
payMethod.payMoney();
}
}
这个“Shopping
”类里的“payMethod
”就是属于这个对象实例的一个数据。类似Shopping
这样的对象实例,就会存放在Java堆内存里。
Java堆内存区域里会放入类似Shopping
的对象,然后我们因为在main方法里创建了Shopping
对象的,那么在线程执行main方法代码的时候,就会在main方法对应的栈帧的局部变量表里,让一个引用类型的“shop
”局部变量来存放Shopping
对象的地址。相当于你可以认为局部变量表里的“shop
”指向了Java堆内存里的Shopping
对象。
核心内存区域的全流程串讲
io=1&rotation=0&showTitle=false&size=98630&status=done&style=none&title=&width=651.5)
- JVM进程启动,就会先加载Client类到内存里。然后有一个main线程,开始执行Client中的main()方法。
- main线程是关联了一个程序计数器的,那么他执行到哪一行指令,就会记录在这里,main线程在执行main()方法的时候,会在main线程关联的Java虚拟机栈里,压入一个main()方法的栈帧。
- 接着会发现需要创建一个
Shopping
类的实例对象,此时会加载Shopping
类到内存里来。 - 然后会创建一个
Shopping
的对象实例分配在Java堆内存里,并且在main()方法的栈帧里的局部变量表引入一个“shop
”变量,让他引用Shopping
对象在Java堆内存中的地址。 - 接着,main线程开始执行
Shopping
对象中的方法,会依次把自己执行到的方法对应的栈帧压入自己的Java虚拟机栈,执行完方法之后再把方法对应的栈帧从Java虚拟机栈里出栈