JVM
一、subSystrm(类加载子系统)
loding(加载)
类加载器
类加载器时我们java代码生成的.class文件已二进制流的方式加载到我们JVM当中的过程称为类加载器。
通过以下代码可以清楚看到我们不同加载器可加载的目录
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for (URL urL : urLs) {
System.out.println(urL.toExternalForm());
}
类加载防止重复加载是有锁的
BootstrapLoader(引导类加载器)
引导类加载器是java专门用来加载jre环境的加载器,通常加载c/c++代码和rt.jar等包下的类。
我们无法使用引导类加载器去加载我们的java类。
ExtClassLoader(扩展类加载器)
扩展类用来加载ext.jar等包下的类,同样我们也无法
AppClassLoader(应用类加载器)
又称系统类加载器,可加载我们classpath用户自定义的类加载到jvm当中
...ClassLoader(自定义类的加载器)
用户也可以自定义记载器
-
隔离加载类
-
修改类加载方式
-
扩展加载源
在jdk1.2之前可以继承ClassLoader类重写loadCalss方法,而在1.2之后建议成功将逻辑卸载findClass方法中。如果没有复杂的需求直接继承URLClassLoader类避免重新findClass
双亲委派机制和沙箱安全
双亲委派机制
当我们创建一个java.lang.String类时,显然他与我们java的String相冲突,我们的类加载器会首先委派给父类去加载String,当委派到BootstrapClassLoader时他会发现本身就又java.lang.String类最终加载,并且我们想在java.lang包下创建一个不存在与jdk里的类也是不可以的,这就破坏了引导类加载器的安全性,因为用户创建的类存在一定的安全问题。
沙箱安全
上诉描述的概念一样,期思想就时不让用户污染我们引导类加载器,保证我们jre本身不受外界影响。
linking(链接)
verify(检测)
文件格式验证
- 是否以魔术OXCAFEBABE开通
- 主次版本号是否在接受范围
- 常量也是否有不被支持的类型
- 检测常量值的引是否执行不符合类型的常量
- 、、、
元数据验证
- 这个类是否有父类
- 这个类是否继承了不被允许的父类
- 是否实现类的抽象方法
- 是否方法的重载和重写
- 、、、
字节码验证
- 验证局部变量表会不会波操作数栈放入不同类型数据
- 保证任何跳转不会跳到栈赖以为的字节码指令上
- 父类引用可以指向子类对象,而不是返回来或者不相干
- 、、、
符号引用验证
- 符号引导描述字符串是否能找到
- 符号引用描述名称、字段、方法是否存在
- 对于访问修饰符是否有权限
- 、、
prepare(准备)
在准备阶段会对类变量也就是被static修饰的变量进行默认初始值的赋值,而实例变量和静态的显示变量需要等待对象初始化时在进行赋值并分配到堆空间中。final修饰static的常量在准备阶段就会赋值,因为final在class编译阶段会生成与之相应ConstantValue值
这里需要注意的点: 在jdk1.7及前中我们的类变量会分配到方法区中也叫永久代,而在jdk1.7后我们类变量会随着Class对象存储在堆空间中,而这时候“类变量在方法区”也成了逻辑上的概念
resolve(解析)
解析的过程就是我们符号引用转为直接引用的过程,其中包括类,方法,字段,接口,静态常量等一系列的引用
initialization(初始化)
通过来对实例变量实现赋值。
通过来对类变量进行显示赋值并按顺序赋值。
二、RuntimeDateAreas(运行时数据区)
program count registers(程序计数寄存器)
是一块较小的内存空间,可以看做字节码行号指示器。字节码指示器就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支循环跳转异常处理,程序计数器是线程私有并且是唯——块不会发生内存溢出的区域。
virtual machine Stack(虚拟机栈)
java虚拟机栈是线程私有的。它的生命周期与线程相同,每个方法在执行时都会创建一个栈帧。用于存储局部变量表,操作数栈、动态链接、方法出口。如果线程请求虚拟机栈的深度大于虚拟机所允许的深度。将抛出StackOverflowError异常。如果扩展时无法申请到做够的内存,则会抛出O utofmemoryError异常
stack frame(栈帧)
LocalVariableTable(局部变量表)
局部变量表记录着我们栈帧中的类型表,实在而且时在编译期就确定下来了又几个变量以及栈中变量槽的大小类型与作用域
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
Slot(变量槽)
32位以内只栈1个操,所以在Double和Long类型在栈两个槽。
这里又一点当在一个栈帧中如果又变量生命周期提前结束的变量那么之后的变量会重复利用他所占的slot
operand stack(操作数栈)
我们java的操作指令是用的栈结构,特点是占用空间小,执行效率慢,而与只对应的句柄结构占用空间大,执行效率块。
操作数栈类似数组的栈,他的栈大小也是在编译期就确定的,保存在方法的Code属性中,为max_stack的深度。
32bit占用1个深度
64bit占用2个深度
return adress(返回值地址)
存放调用该方法的pc寄存器的值:
列入:
在方法中调用其他方法,这时一个压栈操作,当调用完成之后(出栈后)我们当前方法需要继续执行这就需要用到返回值地址,我们返回值地址交给执行引擎继续操作。
如果异常退出抛出异常等,这放回置
dynamic linking(动态链接)
指向运行时常量池的符号引用,每一个栈帧都包含一个对常量池的一个引用。
其他
LineNumberTable(行号表)
字节码指令对应我们java代码的行号信息
LineNumberTable:
line 12: 0
line 4: 4
line 13: 9
line 15: 15
line 18: 18
line 16: 21
line 17: 22
line 19: 26
Exception table
Exception table:
from to target type
15 18 21 Class java/lang/Exception
方法调用的过程
invokeddynamic(动态调用)
在jdk1.7后出现,1.8的时候被使用到,典型的例子lambad表达式以及一些函数接口就是使用此调用。通过实现反推出类型实现动态语言的一小步。
public static void main(String[] args) {
Function f = o -> "A";
}