什么是JVM的内存区域划分
JVM在运行我们写好的代码时,需要使用多块内存空间,不同的内存空间存放不同的数据,然后配合我们写的代码流程,才能让系统运行起来。
内存区域划分图:
存放类的方法区
JDK1.8以前的版本里,代表JVM中的一块区域。主要用来存放从.class文件里加载进来的类,还会有一些变量池
的东西放在这个区域里。
JDK1.8以后的版本里,叫做Metaspace,可以认为是“元数据空间”,主要还是用来存放我们自己写的各种类的相关信息。
例如:
public class Dog {public static void main() {Food food = new Food();}}
程序计数器
public class Dog {public static void main() {Food food = new Food();food.loadFromDisk();}}
.java后缀的文件经过编译以后为后缀是.class的字节码文件,字节码才是计算机可以识别的一种语言,字节码示例(和代码并没有实际关系):
public java.lang.String getName();descriptor: ()Ljava/lang/String;flags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: get_field #24: areturn
0: aload_0这就是“字节码指令”,对应了一条一条的机器指令,计算机只有读到这种机器码指令,才知道应该干什么。
Java代码会被翻译成字节码,对应各种字节码指令,当JVM加载类信息到内存以后,实际就会使用自己的字节码执行引擎,去执行我们写的代码编译出来的代码指令。
在执行字节码指令的时候,JVM里需要一个特殊的内存区域,来记录当前执行的字节码指令的位置,这个就是我们常说得程序计数器。
注意多个线程去执行程序的时候,每个线程都会有一个自己的程序计数器,用来记录当前线程执行到哪一条字节码指令了。
Java虚拟机栈
用来存放方法内的局部变量等数据的区域就是Java虚拟机栈。
每个线程都有自己的Java虚拟机栈,如果线程执行了一个方法,就会对这个方法调用创建对应的一个栈帧。栈帧里存放这个方法的局部变量表、操作数栈、动态链接、方法出口等数据。
public class Dog {public static void main() {Food food = new Food();food.loadFromDisk();}}public class Food {public void loadFromDisk() {Boolean hasFood = false;if (isLoadFood()) {}}public boolean isLoadFood() {Boolean isFood = false;return isFood;}}
Java虚拟机栈在调用任何方法的时候,都会给方法创建栈帧然后入栈,方法执行完毕出栈。
Java堆内存
Java堆内存是用来存放我们代码中创建的各种对象的,如下图:
核心内存的整体流程
其他内存区域
在JDK中很多底层API里,比如IO相关的,NIO相关的,网络Socket相关的,底层很多地方已经不是Java代码了,而是走的native方法去调用本地操作系统里面的一些方法,可能调用的都是c语言写的方法,或者一些底层类库。
比如:public native int hashCode();
在调用这种native方法的时候,就会有线程对应本地的方法栈,这个里面也是跟Java虚拟机栈类似的,也是存放各种native方法的局部变量表之类的信息。
还有一个区域,是不属于JVM的,通过NIO中的allocateDirect这种API,可以在Java堆外分配内存空间。然后,通过Java虚拟机里的DirectByteBuffer来引用和操作堆外内存空间。
堆外内存分配可以提升性能,可以进行借鉴。
每日一问
Tomcat这种web容器中是如何实现类加载器的?
流程图如下:
Tomcat自定义了Common、Catalina、Shared等类加载器,用来加载tomcat自己的一些核心基础类库。
Tomcat为每个部署在里面的web应用都有一个对应的WebApp类加载器,负责加载我们部署的web应用类。
Jsp类加载器
Tomcat打破了双亲委派机制。
