1.jvm基本介绍
JVM是Java Virtual Machine(java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
jvm是直接与操作系统进行交互的,与操作系统交互的结构如下:
jvm是直接与操作系统进行交互,不会直接与服务器硬件进行交互,可以简单理解jvm就是一台小的电脑,一台运行在我们操作系统之上的虚拟电脑。
2.java文件的生命之旅
1)java文件编译后编程class字节码文件
2)字节码文件通过类加载器被搬运到jvm虚拟机当中
3)虚拟机主要有五大块(jdk1.8内存区域划分)
a)方法区:线程共享区域,存在多线程安全问题,主要存放全局变量,常量,静态变量,编译后的代码等;
b)堆:线程共享区,存在多线程安全问题,主要存放对象实力和数组等;
c)java栈空间:线程独享区,不存在多线程安全问题,主要存放java方法(局部变量等);
d)本地方法栈:线程独享区,不存在多线程安全问题,主要负责调用一些底层的C程序实现,一般不做研究;
e)程序计数器:线程独享区,不存在多线程安全问题,一般不做研究
4)程序运行详细步骤
3.类加载器的基本介绍
类加载器负责加载class文件,class文件在文件开头有特定的文件表示,将class字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,并且ClassLoader只负责class文件的加载,至于它是否可以运行,择优ExecutionEngine决定。
1)类加载器的过程
从类被加载到虚拟机内存中开始,到卸出内存为止,它的整个生命周期分为7个阶段:
加载(laoding)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)。其中验证、准备、解析三个部分统称为连接。7个阶段发生的顺序如下:
a)加载:
①将class文件加载在内存中
②将静态数据结构(数据存于class文件的结构中)转换成方法区中运行时的数据结构 tip:方法区中出现OOM,多半是因为加载的依赖太多
③在堆中生成一个代表这个类的java.lang.class对象,作为数据访问的入口
b)连接:
①验证:去报加载的类符合JVM规范与安全。保证被校验类的方法在运行时不会做出危害虚拟机安全的事件
②准备:为静态变量在方法区中分配空间,设置变量的初始值。ex:static int a = 3;在此阶段a会被初始化为0
tip:准备阶段,只设置类中的静态变量(方法区中),不包括实例变量(堆内存中),实例变量是在对象初始化的时候分配的
③解析:是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:简单的理解就是字符串,比如引用一个类,java.util.ArrayList 这就是一个符号引用
直接引用:指针或地址偏移量。引用对象一定在内存(已经加载)
c)初始化:
初始化是类加载的最后阶段,初始化阶段是执行类构造器<clinit>()方法。在类构造方法中,它将由编译器自动收集类中的所有变量的赋值动作(准备阶段的a正式被赋值为3)和静态变量与静态语句块static{}合并;
初始化为类的静态变量赋予正确的初始值。
d)使用:正常使用
e)卸载:GC把无用的对象从内存中卸载
2)类加载器的加载顺序
加载一个类进来之后,需要经过加载、连接、初始化、使用、卸载等一系列步骤,那么我们加载一个class类的顺序也是有优先级的,先加载我们rt.jar这个jar包下面的class类,才能使用其中的方法
a)Bootstrap ClassLoader:负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类;
b)Extension ClasSLoader:负责加载Java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包;
c)App ClassLoader:负责加载classpath中指定的jar及目录中的class;
d)Custom ClassLoader:属于应用程序根据自身需要定义的ClassLoader,如Tomcat、jboss都会根据J2EE规范自行实现ClassLoader
加载过程中会先检查类是否已被加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,保证每个类只有ClassLoader加载一次。加载顺序是自顶向下,逐层尝试加载。
3)类加载器中的双亲委派机制
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
采用双亲委派的一个好处是比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同加载器最终得到的都是同样一个Object对象。
双亲委派这种机制提供了一种沙箱环境来保证我们加载的class类都是同一个,保证我们在开放过程中,不会污染java当中自带的源代码