程序计数器是一块较小的内存空间,它的主要功能是通过计数器的值来选取下一条字节,分支,跳转,循环,异常处理,线程恢复都是靠计数器来完成,为了能做到切换线程再切回来的时候能够知道程序运行的位置,所以需要每个线程都有计数器,
也就是计数器是线程私有的
主要作用:
1:字节码解释器通过改变程序计数器的值来执行下一条语句从而实现流程控制功能
2:在多线程环境下,计数器会记录线程当前执行的位置,当下一次执行到该线程的时候会根据计数器恢复到上次执行的地方继续往下执行.
⚠️ 注意 :程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。
(占用空间极小,就算程序出现死递归也只有一份)
和程序计数器一样,虚拟机栈也是线程私有的,线程的每一次方法调用都会有一个对应的栈帧被压入栈中,当方法执行完毕后对于的栈帧就会被弹出
栈由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法返回地址。和数据结构上的栈类似,两者都是先进后出的数据结构,只支持出栈和入栈两种操作。
局部变量表:存储方法执行过程中的局部变量(包括基本类型和引用类型)
操作数栈:存储方法执行过程中的中间计算结果和临时变量
动态链接:用于方法内会调用另一个方法时,在 Java 源文件被编译成字节码文件时,所有的变量和方法引用都作为符号引用(Symbilic Reference)保存在 Class 文件的常量池里。当一个方法要调用其他方法,需要将常量池中指向方法的符号引用转化为其在内存地址中的直接引用。动态链接的作用就是为了将符号引用转换为调用方法的直接引用。
方法返回地址:记录方法执行完后跳转的地址
和虚拟机栈类似本地方法栈是系统Native方法执行时会创建的区域
存放对象实例,Java1.7及之后静态变量和字符串常量池也存放在堆中,
同时Java1.7及之后在创建对象的时候会进行逃逸分析,如果判断某些方法中对象引用没有被返回(即未被方法外使用,也就是未逃逸出方法),那么该对象就会被创建在栈区.
💡思考:内置类型存放在栈区,引用类型存放在堆区是否正确?
解答:正确说法应当是局部变量存放在栈区,成员变量存放在堆区,因为如果成员变量为内置类型时那么也会存放在堆区,同样如果引用类型位于方法内成为一个局部变量,那么也会存放在栈区.
方法区存放的是类对象,当cllss文件被加载时生成的类对象就会存放在方法区,
类对象描述了这个类的结构,包括类名,里面有哪些方法和成员,以及方法和成员的访问权限,常量,静态变量,JIT编译后的代码缓存
注意:方法区属于一块逻辑区域,所有线程共享,
运行时常量池位于方法区,当类加载到内存中后,jvm就会将
class常量池(用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References))
中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个
。在解析阶段之后会查询全局字符串池将符号引用改为直接引用,从而保证运行时常量池所引用的字符串与全局字符串池中的引用相同.
字符串常量池的内容是在类加载的时候完成的,当准备阶段完成后就会在堆中生成字符串对象实例,然后将字符串引用存放到字符串常量池中
(注意:字符串常量池存放的是字符串对象引用)
,在HotSpot VM里字符串常量池底层是一个哈希表,同时字符串常量池位于堆中,且全局唯一.
JVM规范外的一块区域,方法区在JDK8中已经完全转移到了直接内存中的元空间中.同时堆区的永久代部分也放到了元空间中.
使用直接内存能大大减少动态拓展空间时产生的OOM(OutOfMemoryError)问题
把.class文件加载到内存中,构建成类对象
1) Loading
找到对应的.class文件,然后打开并读取.class文件,初步构成一个类对象
class文件:
2) Linking
1:Veriication
验证读到的数据是不是和规范中的格式一样,如果不一样就会类加载失败同时抛出异常
2:Preparation
给静态变量变量分配空间同时赋上类型默认值(0, null, false)
3:Resolution
把运行时常量池常量池中的符号引用——>替换为直接引用,主要针对的是类或接口、字段、方法.
3) Initializing
在这个阶段才是真正对类对象进行初始化,尤其是静态变量.
双亲委派模型描述的就是类加载器如何通过类的限定名(Java.lang.String)找到对应的.class文件.
JVM默认的类加载器有三个:
BootstrapClassLoader 负责加载标准库中的类
ExtensionClassLoader 负责加载JDK拓展中的类
ApplicationClassLoader 负责加载当前项目目录里的类
也可以自己定义类加载器来负责加载特定目录的类(tomcat中就自定义了类加载器来加载webapps中的.class)
程序启动后先进入ApplicationClassLoader类加载器,然后ApplicationClassLoader会检查它的父类加载器(即ExtensionClassLoader)是否加载过,若没有加载过则调用父类加载器ExtensionClassLoader进行加载,ExtensionClassLoader在加载前会检查它的父类BootstrapClassLoader是否加载过,若未加载过则调用BootstrapClassLoader来进行加载,若BootstrapClassLoader在标准库中找到该.class文件则查找结束,没有找到该.class文件则回到子加载器进行扫描,如果最终ApplicationClassLoader加载器也没有扫描到该.class文件则会抛出ClassNotFoundException异常
用双亲委派模型的好处就是当我们写的类和标准库重复时,JVM也能通过该机制顺利的加载到标准库中的类
作者水平有限,若文章有任何问题欢迎私聊或留言,希望和大家一起学习进步!!!
创作不易,再次希望大家👍支持下,谢谢大家🙏