JVM中的内存区域
本系列是为面试,更深的可以参看周志明那本国内最好的JVM书(要出第3版啦)
更好的阅读体验【长期有效】
上一节说了类加载,那加载进来总要有个地方存储,不同的数据(包括代码,变量,数据。。。),这就需要不同的内存空间。这个就涉及今天讲的 内存区域 。接下来我们就依次看看 JVM
有哪些内存区域。
存放类的方法区
方法区是在 JDK1.8
之前的版本里存放 .class
文件里加载进来的类,还会有一些类似常量池放在这个区域。但是 1.8
之后这块区域名字改了:Matespace ,可以认为是 元数据空间 。当然这里主要存放自己写的各种类相关的信息。
程序计数器
public java.lang.String getName();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: get_field #2
2: areturn
这段字节码就是让大家知道 .java
编译成 .class
大概是什么样子。其中像什么 0: aload_0
就是 字节码指令,它就对应一条机器指令。所以首先明白一点:java代码会先被编译为字节码,对应各种字节码指令。而在执行字节码指令时,就需要一个特殊的内存区域,来 记录当前字节码指令的位置,也就是目前执行到哪一条了。而 JVM
是支持多线程的,也就是说每一个线程都有它自己的一个 程序计数器 。
Java虚拟机栈
public class Test {
public static void main() {
Manager manager = new Manager();
manager.load();
}
}
接着上面的计数器,如上述代码在执行的时候,当前的 main
线程在执行 main()
方法的代码指令时,就会通过main线程对应的程序计数器来记录自己执行的指令位置。但在方法里,经常会定义一些方法内部的局部变量。如上面的 manager
,它就是引用一个 Manager
实例对象,这个对象我们先不管,先来看看方法和局部变量。
所以,JVM 必须有一块区域来保存每个方法内的 局部变量 等数据,这个区域就是 Java虚拟机栈。
同样的,每个线程都有自己的Java虚拟机栈 。当线程执行一个方法,就会对这个方法调用创建对应的一个 栈帧。栈帧里就有这个方法的局部变量表,操作数栈,动态链接,方法出口等东西,这里先不用全都理解,先关注局部变量。
比如 main
线程执行 main()
,那就会给这个 main()
方法创建一个栈帧,压入main线程的java虚拟机栈,同时在main方法的栈帧里面,会存放对应的 manager
局部变量。(这个有点像套娃)
然后假设main
线程继续执行Manager
对象里的方法,如下面的,就在 load()
里面定义了一个局部变量:hasLoad
public class Manager {
public void load() {
Boolean hasLoad = false;
}
}
那么在main线程执load()
时,就会为load()
创建一个栈帧压入线程自己的Java虚拟机栈里面去。然后在栈帧的局部变量表里面就会有hasLoad
局部变量。(这个时候就有点像一个盒子里面存着几张记录纸)
总结上面:每个线程在执行代码是,除了程序计数器之外,还搭配了一个Java虚拟机栈内存区域来存放每个方法中的局部变量表。
堆内存
这个区域就是来存放代码中创建的各种对象,例如下面:
public class Test {
public static void main() {
Manager manager = new Manager();
manager.load();
}
}
上述的new Manager()
就是创建了一个Manager
类的对象实例,这个对象实例包含一些自己的数据:
public class Manager {
private long count;
public void load() {
Boolean hasLoad = false;
if (isData()) { ... }
}
public void isData() {
Boolean hasData = false;
return hasData;
}
}
这个类里面的count
就是属于这个对象实例的一个数据。类似Manager
这样的对象实例,就会被放在Java堆内存中。相当于你可以认为局部变量表的manager
指向了堆内存里的Manager
对象。
其他内存区域
其实在JDK很多底层的api里,比如IO
,NIO
,Socket
网络相关的,深入它的源码你就发现很多地方都已经不是Java代码了,而是一种native
本地方法调用本地OS里面的一些方法,可能是是一些C语言写的方法,或者是一些底层类库:public native int hashCode();
而在调用这些native方法是,就会有线程对应本地方法栈,这个也是和Java虚拟机栈
类似的,也是存放一些native方法的局部变量表之类的信息。
还有一个区域,不属于JVM,通过NIO
中的allocateDirect
这种API,可以在Java堆外分配内存空间。然后通过Java虚拟机栈里的DirectByteBuffer
来引用和操作堆外内存空间。
也就说明其实很多技术都会使用这种方式,堆外内存分配可以提升性能。
总结
一图胜千言:
我猜图是用processon画的,hhh
hhh,是的,我基本上画图都用这个
hhh,我也是