目录
类的加载
图示
类的加载:
-
将外部的字节码文件加载到虚拟机中,并且储存在方法区内。
-
通过类的全限定类名来获取此类的二进制字节流。
-
将这个字节码文件流所代表的静态结构转化为方法区的运行时结构。
-
在内存中生成一个这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口。
链接:
-
验证
-
确保加载类的正确性
-
-
准备
-
为类变量在方法区中分配内存
-
为类变量设置初始值
-
注意
-
实列变量的初始化不在准备阶段分配内存
-
实列变量是随着对象实列化随着对象一起分配在内存中
-
-
-
解析
-
将常量池内的符号引用转化为直接引用
-
初始化
初始化是指为类的静态变量赋值
-
在声明变量时指定初始值
-
使用静态代码块为变量指定初始值
具体的流程是
-
生成类的构造器<clinit>
-
它是由类变量和静态代码块构成。
-
注意
-
init是为对象初始化,而<clinit>是为类变量赋值
-
-
执行<clinit>方法
-
类加载器
类加载器
-
引导类加载器(获取不到,不是用java语言写的)
-
主要加载的是jvm自身需要的类
-
只能加载包名为java、javax、sun开头的类
-
-
扩展类加载器
-
负责加载<JAVA_HOME>/lib/ext下的类库
-
-
系统类加载器
-
负责加载系统类路径
java -classpath
或-D java.class.path
指定路径下的类库,也就是我们经常用到的classpath路径
-
双亲委派机制
双亲委派机制是当前类加载器不要进行处理而是由父类进行处理,如果父类处理不了则再子类进行处理
作用:
-
防止类的重复加载
-
保护程序的安全,防止api被篡改
类的主动使用和被动使用
jvm规定了以下6种为主动使用
-
通过new 关键字会导致类的初始化
-
访问类的静态变量
-
访问类的静态方法
-
对某个类进行反射操作
-
初始子类会导致父类的初始化
-
启动类:也就是类的main方法执行会导致该类的初始化
除了上述的六种方式会导致类的主动使用,其它的会导致类的被动使用
运行时数据区内部结构
-
程序计数器
-
堆区
-
方法区
-
栈
-
本地方法栈
-
虚拟机栈
-
其中堆区和方法区是线程共享的,而程序计数器,本地方法栈、虚拟机栈是线程私有的。
程序计数器
程序计数器又叫pc计数器(并不是物理意义上的pc寄存器jvm的pc寄存器是对物理pc基础器的抽象模拟),它储存的是下一条即将执行的字节码储存地址。
-
作用
-
因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。JVM的字节码解释器就需要通过PC寄存器的值来明确下一条应该执行什么样的字节码指令。
-
-
PC寄存器为什么会设置线程私有
-
为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。
-
虚拟机栈
内存中栈和堆的区别
栈是运行时的单位,而堆是储存的单位,栈解决了程序证明执行,堆解决的是数据储存问题,数据怎么放,放在哪里。
每个线程都有一个虚拟机栈,虚拟机栈内部保存着一个个栈帧,一个栈帧就是一个方法(虚拟机栈是线程私有的)
生命周期
生命周期和线程一致,线程结束了,生命周期也就结束了。
作用
-
它保存方法的局部变量(八种基本数据类型和对象的引用),部分结果,并且参与方法的调用和返回。
栈的特点
jvm对于栈的操作只用入栈和初栈这两种,对于栈来说不存在垃圾回收问题,但是存在栈溢出问题
栈溢出:
程序运行时,栈的大小小于当前线程运行时需要的栈的大小
栈的储存结构和运行原理
-
栈是由栈帧构成,而一个栈帧对应一个方法。jvm对于java栈的操作只有两种,压栈和出栈。
-
在一条栈帧中,一个时间点上只会有一个活动栈帧,即当前活动的栈帧的是有效的,这个栈帧是当前栈帧。对于的方法就是当前方法,定义这个方法的类就是当前方法。
-
执行引擎运行的字节码是对当前栈帧进行操作。
-
如果该方法调用其他方法,就会创建调用方法的栈帧,放在栈顶成为新的当前栈。
-
栈帧是线程私有的
-
.当前方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧
-
Java函数返回方式有两种,使用return或者抛出异常。不管哪种方式,都会导致栈帧被弹出。
栈的运行原理图
栈帧的内部结构
-
局部变量表
-
操作数栈
-
动态链接
-
方法返回地址
-
一些附加信息