文章目录
前言
少有java工程师可以抵挡探究jvm的诱惑,这个道理非常简单,人们总是喜欢学会怎么用,才去想知道为什么。本次的篇章将集中精力探究下大名鼎鼎的java虚拟机。jvm篇所有的内容几乎都是以hotSpot虚拟机展开的,下面我们开始。 ————————————————————————————————————————————— 本次章节重点不在于林林总总地敞开谈java virtual machine。而是将所有最关键的需要理解和记忆的地方进行梳理。大家看得话也是一样,把握重点!jvm整体把握
1、jvm整体结构
2、java代码的执行流程
两次编译:
源文件——>字节码文件——>机器指令 注意java编译器和执行引擎中的编译器的区别:java编译器称为前端编译器、执行引擎中的编译器称为后端编译器 ————————————————————————————————————————————— 注意虚拟机中的解释执行和jit编译执行的关系和区别:解释执行是为了保证程序执行的响应时间,逐行解释执行字节码文件,jit是针对重复出现的热点代码进行优化的,重点关注程序性能。但是两者可不是互相补充的关系,他们每一个离开另外一个仍可以独立支撑工作(将字节码转换为机器指令)。
3、Jvm的生命周期
jvm的生命周期分为以下几个部分:
虚拟机启动:
java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)完成的,这个类是由虚拟机的具体实现完成的。
- 程序正常执行结束
- 程序在执行过程中遇到了错误或者异常而终止。
- 操作系统出现错误导致了java虚拟机进程终止。
- 有线程调用Runtime类或者System类的exit方法,或Runtime类的halt方法,并且java安全管理器允许这次exit或者halt操作。
- 暂未想到的其它情形。
类加载子系统
1、类加载子系统作用
- 类加载子系统负责从文件系统或者网络中加载class文件,class文件在开头有特定的文件标识。
- classLoader只负责class文件的加载,至于它是否可以运行,则有执行引擎决定。
- 加载的类信息存放在一块被称为方法区的内存空间。除了类的信息以外,方法区中还存放运行时的常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是class文件常量池部分的内存映射)
请记住: ClassLoader加载calss文件除了将类的信息加载到方法区中,还会存放运行时的常量池信息。
2、类加载器ClassLoader
- classs file存在于本地硬盘上,可以理解为设计师画在纸上的模板,可以理解为设计师画在纸上的模板。但是模板在执行的时候需要加载到jvm当中,以便根据这个模板实例出n个一摸一样的实例。
- class file加载到jvm中,被称为DNA元数据模板,放在方法区中。
- 在.class文件——>jvm——>最终成为元数据模板的过程中需要一个运输工具,他就是类加载器(ClassLoader)!
3、类的加载过程
类的加载过程就是类加载子系统装载.class文件的过程,实际上你可以理解为直接编译拿到的.class文件还不能用,我们需要将它生成大的Class实例,且将这个类的常量池、类信息等装载到jvm中。这个过程很重要,咱们一点点说:
加载——loading:
- 加载的第一步通过类的全限定名获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口。
链接——linking
看链接过程之前咱们先明白两个概念。啥叫类变量和实例变量
链接阶段你要有印象的,最重要的,此时类变量会被分配内存并且设置该类的默认初始值,即零值,例如int类型的类变量此时被赋值为0,引用类型的类变量被赋值为null。
初始化——initial
下面咱们通过具体的例子来看下,在类加载子系统的加载下类变量都会发生什么。
public class ClassClinitTest {
static {
number = 20;
}
private static int number = 10;
public static void main(String[] args) {
System.out.println(number);
}
}
最终main()方法中输出的number的值是10,当然我们都能猜出来。结合咱们刚刚说的类加载子系统对这个类ClassClinitTest的初始化,这个number的值经历了几次变化呢?
首先,linking阶段中的prepare阶段,此时静态的变量被赋予初始值,那么number=10,然后linking阶段走完之后来到初始化阶段,此时按照代码的顺序,static先执行然后变量的定义语句执行,所以最终number=10;
_________________________________________________________________________
再来看下一个例子,注意,子类的clinit方法执行之前,虚拟机会保证他的父类的clinit方法一定已经执行了。
public class ClInitTestOne {
static class Father {
public static int A = 1;
static {
A = 2;
}
}
static class Son extends Father {
public static int B = A;
}
public static void main(String[] args) {
System.out.println(Son.B);
}
}
那么最终打印出的Son中的类变量B的值是多少呢?容易知道在linking中的prepare阶段B将被赋予0,然后Son的ClInit方阿飞执行之前父类Father的Clint方法会率先执行,因此最终B的值是2。
4、ClassLoader家族介绍
其实上面类加载子系统对.class文件做的一系列的装载工作,包括准备、链接、初始化,都是由具体的ClassLoader进行的。
下面尝试获取下各层classLoader.
系统类加载器和扩展类加载器都能获取到,但是尝试获取引导类加载器的时候却发现获取不到,这是为啥呢?
引导类加载器bootStrapClassLoader是不能靠上述方式获取的,系统核心类库如java.lang.String都是使用bootStrapClassLoader进行加载的.引导类加载器是使用c/c++语言编写的。
注意。除了启动类加载器BootStrapClassLoader,剩余所有的加载器均继承自ClassLoader。
5、双亲委派机制理解
java虚拟机对class文件采取的是按需加载的方式,也就是说当真正需要使用该类的时候才会将它的class文件加载到内存中生成class对象。并且加载某个类的class文件时,java虚拟机采用的是双亲委派机制,即把请求交由父类处理,它是一种任务委派模式。
双亲委派机制的工作原理:
- 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。
- 如果父类的加载器不能执行加载,但是父类的加载器还存在父类,那么继续向上委托,依次递归,请求将最终到达顶层的启动类加载器。
- 如果父类加载器可以完成加载任务,那么就成功返回,倘若父类加载器无法完成加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
我们举个例子说明下:
我们创建一个包,叫java.lang,这个包下创建一个类,叫做String。然后咱们来玩个好玩的,请看下图:
咱们尝试执行自定义的String类中的main()方法,却显示不能执行,报错的原因就涉及到双亲委派机制了。找到顶层的启动类加载器bootStrapClassLoader,无法加载你这个类!
补充
查看类的反编译信息请使用javap 命令
例如现在定义Test类如下:
cmd到这个类的.class文件夹下,然后执行Javap -v Test.class。可以看到相关的常量池信息。
还有别的一些很有趣的,例如方法的具体执行过程,都可以看到。
总结
本篇到此!大家看得开心!
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。