????????????????????
学习流程图解:
类加载器子系统
1、类的加载过程
加载–>链接(linking)(验证、准备、解析)–>初始化
代码演示
创建一个StringTest类
在src下创建包结构:java.lang
在java.lang下创建一个String类
沙箱安全机制:
自定义string类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中javalangString . class), 错信息说没有main方法,就是因为加载的是rt. jar包中的String类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。
双亲委派机制的优点:
- 避免类的重复加载
- 保护程序的安全,防止核心API被随意篡改
4、其他
在JVM中表示两个class对象是否为同一个类存在两个必要条件:
- 类的完整类名必须- -致,包括包名。
- 加载这个类的ClassLoader (指ClassLoader实例对象)必须相同。
对类加载器的引用:
JVM必须知道–个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的。
类的主动使用和被动使用
Java程序对类的使用方式分为:主动使用和被动使用。
- 主动使用,又分为七种情况:
1、创建类的实例
2、访问某个类或接口的静态变量,或者对该静态变量赋值
3、调用类的静态方法
4、反射(比如: Class. forName (“com. atguigu. Test”) )
5、初始化一个类的子类
6、Java虚拟机启动时被标明为启动类的类
7、JDK 7开始提供的动态语言支持:java.lang. invoke . MethodHandle实例的解析结果REF getStatic、 REF putStatic、 REF invokeStatic句柄对应的类没有初始化,则初始化 - 除了以上七种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化。
运行时数据区(Runtime Data Area)
线程
- 线程是一个程序里的运行单元。JVM允许一个应用有多个线程并行的执行。
- 在Hotspot JVM里, 每个线程都与操作系统的本地线程直接映射。 当一个Java线程准备好执行以后,此时一个操作系统的本地线程也同时创建。Java线程执行终止后,本地线程也会回收。
- 操作系统负责所有线程的安排调度到任何一个可用的CPU上。一旦本地线程初始化成功,它就会调用Java线程中的run()方法。
后台系统的主要线程
虚拟机线程:这种线程的操作是需要JVM达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点,这样堆才不会变化。这种线程的执行类型包括”stop-the-world”的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销。
周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一-般用于周期性操作的调度执行。
GC线程:这种线程对在JVM里不同种类的垃圾收集行为提供了支持。
编译线程:这种线程在运行时会将字节码编译成到本地代码。
信 调度线程:这种线程接收信 并发送给JVM,在它内部通过调用适当的方法进行处理。
1、程序计数器(PC寄存器)
JVM中的程序计数寄存器(Program Counter Register) 中,Register 的命名源于CPU的寄存器,寄存器存储指令相关的现场信息。CPU只有 把数据装载到寄存器才能够运行。这里,并非是广义上所指的物理寄存器,或许将其翻译为PC计数器(或指令计数器)会更加贴切(也称为程序钩子),并且也不容易引起一些不必要的误会。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟。
- 它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域。
- 在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一-致。
- 任何时间一一个线程都只有一一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;或者,如果是在执行native方法,则是未指定值(undefined)。
- 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
- 字节码解释器工作时就是通过改变这个计数器的值来选取下-“条需要执行的字节码指令。
- 它是唯一一个在Java虚拟机规范中没有规定任何OutOtMemoryError情况的区域。
作用:
PC寄存器用来存储指 向下一条指令的地址, 也即将要执行的指令代码。由执行引擎读取下一条指令。
使用举例:
- 局部变量表也被称之为局部变量数组或本地变量表
- 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference) ,以及returnAddress类型。
- 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
- 局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。 在方法运行期间是不会改变局部变量表的大小的。
- 方法嵌套调用的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。对一个函数而言,它的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,以满足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间,导致其嵌套调用次数就会减少。
- 局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。
Slot
- 参数值的存放总是在局部变量数组的index0开始,到数组长度-1的索引结束。
- 局部变量表,最基本的存储单元是Slot (变量槽)
- 局部变量表中存放编译期可知的各种基本数据类型(8种),引用类型(reference),returnAddress类型的变量。
- 在局部变量表里,32位以内的类型只占用一个slot (包括returnAddress类型),64位的类型(long和double)占用两个slot。
?byte 、short 、char 在存储前被转换为int,boolean 也被转换为int,0表示false,非0表示true。
?long和double则占据两个Slot。
局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。
2、操作数栈
- 每一个独立的栈帧中除了包含局部变量表以外,还包含一个后进先出的操作数栈,也可以称之为表达式栈。
- 操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push) /出栈(pop)
?某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈。使用它们后再把结果压入栈。
?比如:执行复制、交换、求和等操作, - 操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
- 操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。
- 每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的Code属性中,为max stack的值。
- 栈中的任何一个元素都是可以任意的Java数据类型。
?32bit的类型占用一个栈单位深度
?64bit的类型占用两个栈单位深度 - 操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈(push)和出栈(pop)操作来完成一次数据访 问。
- 如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新PC寄存器中下一-条需要执行的字节码指令。
- 另外,我们说Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的
就是操作数栈。
代码演示:
常量池的作用,就是为了提供一些符 和常量,便于指令的识别。
方法的调用(内部机制)
在JVM中,将符 引用转换为调用方法的直接引用与方法的绑定机制相关。
- 静态链接:
?当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知,且运行期
?保持不变时。这种情况下将调用方法的符 引用转换为直接引用的过程称之为静态链接。声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!