Java进阶之JVM(一)类加载器详解

一、JVM简介

Java跨平台:
Java是高级语言,但是真正的执行需要最终转换成机器指令才能执行,计算机并不认识高级语言,了解JVM相关的知识也能
更好帮助我们了解Java语言的执行流程。
Java进阶之JVM(一)类加载器详解_第1张图片
首先.java文件经过编译成为.class文件(字节码文件),然后将编译好的字节码文件放到虚拟机中就可以执行了。
Java进阶之JVM(一)类加载器详解_第2张图片

1.1JVM简介:

Java虚拟机:
1、Java虚拟机是一台执行Java字节码的虚拟计算机,它拥有独立的运行机制,其运行的Java字节码也未必由Java语言编译而成。
2、JVM平台的各种语言可以共享Java虚拟机带来的跨平台性、优秀的垃圾回器,以及可靠的即时编译器。
3、Java技术的核心就是Java虚拟机(JVM,Java Virtual Machine),因为所有的Java程序都运行在Java虚拟机内部。
4、Java虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台上的机器指令执行。
每一条Java指令,Java虚拟机规范中都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放在哪里。

作用:
Java虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台上的机器指令执行。
每一条Java指令,Java虚拟机规范中都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放在哪里。

特点:
1、一次编译,到处运行
2、自动内存管理
3、自动垃圾回收功能 GC

JVM位置:
JVM是运行在操作系统之上的,它与硬件没有直接的交互
Java进阶之JVM(一)类加载器详解_第3张图片

Java的体系结构Java进阶之JVM(一)类加载器详解_第4张图片

1.2JVM架构:

JVM整体结构:
1、 HotSpot VM是目前市面上高性能虚拟机的代表作之一。
2、它采用解释器与即时编译器并存的架构。
3、在今天,Java程序的运行性能早已脱胎换骨,已经达到了可以和C/C++程序一较高下的地步。
Java进阶之JVM(一)类加载器详解_第5张图片
字节码文件:Java字节码类文件(.class)是Java编译器编译Java源文件(.java)产生的“目标文件”。
类加载器子系统
虚拟机栈:每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(stack Frame) ,对应着一次次的Java方法调用。一次方法的调用,就是栈帧入栈到出栈的过程 ​

Class Files(字节码文件) 要加载到运行时数据区(Runtime Data Area)
类加载器子系统的作用就是将字节码文件加载到JVM里面
方法区:就是字节码文件加载到JVM后,存放的地方
堆:new的对象,就在堆中
虚拟机栈:就是每当调用一个方法的时候,就要压栈,方法中的变量也存放在里面。
程序计数器:就是控制如何一条一条的执行指令

1.3JVM 生命周期:

虚拟机的启动:
Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定的

虚拟机的退出:
有如下的几种情况:

  1. 程序正常执行结束
  2. 程序在执行过程中遇到了异常或错误而异常终止
  3. 由于操作系统用现错误而导致Java虚拟机进程终止
  4. 某线程调用Runtime类或System类的exit()方法,或Runtime类的halt()方法,并且Java安全管理器也允许这次exit()或halt()操作。
  5. 除此之外,JNI(Java Native Interface)规范描述了用JNI Invocation API来加载或卸载 Java虚拟机时,Java虚拟机的退出情况。

二、类加载器

2.1.类加载器作用:

2.1.1类加载器子系统作用:

  1. 类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识。
  2. ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
  3. 加载的类信息存放于一块称为方法区的内存空间。

Java进阶之JVM(一)类加载器详解_第6张图片
加载阶段只会选一个类加载器去加载
验证:验证字节流中包含信息的 格式是否正确
准备:为类变量(static)分配内存并且设置该类变量的默认初始值
解析:将符号引用转化为直接引用
初始化:给静态变量、静态代码块,赋初始值

2.1.2类加载器ClassLoader作用:

  1. class file,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到JVM当中来根据这个文件实例化出n个一模一样的实例。
  2. class file加载到JVM中,被称为DNA元数据模板,放在方法区。
  3. 在.class文件–>JVM–>最终成为元数据模板,此过程就要一个运输工具(类装载器Class Loader),扮演一个快递员的角色。
    Java进阶之JVM(一)类加载器详解_第7张图片
    字节码文件,通过类加载器(ClassLoader)加载并初始化,得到字节码对象(放在方法区),通过字节码对象,就可以的 new出car1、car2这些实例。
    有了字节码实例对象后,通过getClass()方法,就可以反过来得到字节码对象,字节码对象也可以调用getClassLoader()方法,得到类加载器

2.2类加载过程:

public class HelloLoader {
    public static void main(String[] args) {
        System.out.println("谢谢ClassLoader加载我....");
        System.out.println("你的大恩大德,我下辈子再报!");
    }
}

它的加载过程是怎么样的呢?
1、执行 main() 方法(静态方法)就需要先加载承载类 HelloLoader
2、加载成功,则进行链接、初始化等操作,完成后调用 HelloLoader 类中的静态方法 main
3、加载失败则抛出异常
Java进阶之JVM(一)类加载器详解_第8张图片
完整的流程图如下所示: 加载 --> 链接(验证 --> 准备 --> 解析) --> 初始化
Java进阶之JVM(一)类加载器详解_第9张图片

2.2.1加载阶段:

加载流程:

  1. 通过一个类的全限定名获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为 方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

加载class文件的方式:
4. 从本地系统中直接加载
5. 通过网络获取,典型场景:Web Applet
6. 从zip压缩包中读取,成为日后jar、war格式的基础
7. 运行时计算生成,使用最多的是:动态代理技术
8. 由其他文件生成,典型场景:JSP应用从专有数据库中提取.class文件,比较少见
9. 从加密文件中获取,典型的防Class文件被反编译的保护措施

2.2.2链接阶段:

链接分为三个子阶段:验证 --> 准备 --> 解析

验证(Verify):

  1. 目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全
  2. 主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

准备(Prepare):

  1. 为类变量(static)分配内存并且设置该类变量的默认初始值
  2. 这里不包含用final修饰的static,因为final在编译的时候就会分配好了默认值,准备阶段会显式初始化
  3. 注意:这里不会为实例变量分配初始化
    案例:
    代码:变量a在准备阶段会赋初始值,但不是1,而是0,在初始化阶段会被赋值为 1
public class HelloApp {
    private static int a = 1;


    public static void main(String[] args) {
        System.out.println(a);
    }
}

解析(Resolve):

  1. 将常量池内的符号引用转换为直接引用的过程
  2. 符号引用就是一组符号来描述所引用的目标。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄

在编译的时候一个每个java类都会被编译成一个class文件,但在编译的时候虚拟机并不知道所引用类的地址,所以就用符号引用来代替,而在这个解析阶段就是为了把这个符号引用转化成为真正的地址的阶段。
String str = “abc”; 编译时候没有分配内存,不知道在内存哪里所以先用符号代替,解析阶段把符号引用替换为直接引用。

2.2.3初始化阶段:(重点)

  1. 初始化阶段就是执行"类构造器方法" () 的过程
  2. 此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。也就是说, 当我们代码中包含static变量的时候,就会有clinit方法, () 方法中的指令按语句在源文件中出现的顺序执行
  3. ()不同于类的构造器。(关联:构造器是虚拟机视角下的 ())
  4. 若该类具有父类,JVM会保证子类的 () 执行前,父类的 () 已经执行完毕
  5. 虚拟机必须保证一个类的 () 方法在多线程下被同步加锁

Java进阶之JVM(一)类加载器详解_第10张图片

2.2.3.1初始化案例:

当我们代码中包含static变量的时候,就会有clinit方法
1、无 static 变量:Java进阶之JVM(一)类加载器详解_第11张图片
2、有static变量
Java进阶之JVM(一)类加载器详解_第12张图片

3、构造器方法中指令按语句在源文件中出现的顺序执行

Java进阶之JVM(一)类加载器详解_第13张图片
静态变量 number 的值变化过程如下
1、prepare准备阶段时:0
2、 执行静态变量初始化:10
3、 执行静态代码块:20
Java进阶之JVM(一)类加载器详解_第14张图片
静态变量 number 的值变化过程如下
1、准备阶段时:0
2、执行静态代码块:20
3、执行静态变量初始化:10

4、若该类具有父类,JVM会保证子类的 () 执行前,父类的 () 已经执行完毕
Java进阶之JVM(一)类加载器详解_第15张图片

加载流程如下:
1、首先,执行 main() 方法需要加载 ClinitTest5 类
2、 获取 Son.B 静态变量,需要加载 Son 类
3、Son 类的父类是 Father 类,所以需要先执行 Father 类的加载,再执行 Son 类的加载
类加载器的分类:

2.3类加载器的分类

  1. JVM支持两种类型的类加载器 。分别为: 1、引导类加载器(Bootstrap ClassLoader)和2、自定义类加载器(User-Defined ClassLoader)
  2. 从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是 将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
  3. 无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个,如下所示
  4. 这里的四者之间是包含关系,不是上层和下层,也不是子父类的继承关系。
    Java进阶之JVM(一)类加载器详解_第16张图片

2.3.1为什么 ExtClassLoader 和 AppClassLoader 都属于自定义加载器?

规范定义: 所有派生于ClassLoader的类加载器都划分为自定义类加载器,所以ExtClassLoader和AppClassLoader也归类到自定义类加载器。
BootstrapClassLoader是用C、C++语言写的,其余都是Java写的。

2.3.2双亲委派机制执行的流程:

自低而上检查类是否已经加载,首先是CustomClassLoader自定义类加载器**有没有曾经加载过Student.class这个类**,如果加载过直接返回,
如果没有加载过就委派给他的Parent就是AppClassLoader,看他有没有加载过这个类,如果加载过直接返回,
如果也没有加载过,那么AppClassLoader又会 委派给ExtensionClassLoader,如果加载过直接返回,
如果也没有就委派给最顶层的Bootstrap ClassLoader,如果加载过直接返回,
如果之前也没有加载过,那么Bootstrap ClassLoader就会尝试去JRE\lib\rt.jar或者Xbootclasspath(可以指定位置)选项指定的jar包下面有没有要加载的Student.class这个类,如果发现了Class文件就 加载并返回,
如果没有就会自顶向下委派给ExtensionClassLoader去到对应的目录看有没有对应的Class文件,这样一直往下,最后CustomClassLoader自定义ClassLoader会按照findClass的方式去寻找有没有对应Class文件的字节byte数组,如果有就直接装载到jvm中。
public class ClassLoaderTest {
    public static void main(String[] args) {
        //获取系统类加载器AppClassLoader 也叫应用类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
 
        //获取其上层:扩展类加载器  ExtensionClassLoader
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@45ee12a7
 
        //获取其上层:获取不到引导类加载器 Bootstrap ClassLoader
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//null (C、C++写的,我们拿不到)
 
        //对于用户自定义类来说:默认使用系统类加载器进行加载
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
 
        //String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null
    }
}

输出结果:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@45ee12a7
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null
  • 我们尝试获取引导类加载器,获取到的值为 null ,这并不代表引导类加载器不存在, 因为引导类加载器右 C/C++ 语言,我们获取不到
  • 两次获取系统类加载器的值都相同:sun.misc.Launcher$AppClassLoader@18b4aac2 ,这说明系统类加载器是全局唯一的

2.3.3启动类加载器(引导类加载器,Bootstrap ClassLoader)

  1. 这个类加载使用C/C++语言实现的,嵌套在JVM内部
  2. 它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar(rt是runtime的缩写)、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
  3. 并不继承自java.lang.ClassLoader,没有父加载器
  4. 出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
    例如java.lang这个包,里面的类都是他负责加载的,通常这些核心类被签名,不能被替换掉。

2.3.4扩展类加载器(Extension ClassLoader)

  1. Java语言编写,由sun.misc.Launcher$ExtClassLoader实现
  2. 派生于ClassLoader类
  3. 父类加载器为启动类加载器
  4. 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。
    如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载
    Java进阶之JVM(一)类加载器详解_第17张图片

2.3.5应用程序类加载器(系统类加载器,AppClassLoader)

  1. Java语言编写,由sun.misc.LaunchersAppClassLoader实现
  2. 派生于ClassLoader类
  3. 父类加载器为扩展类加载器
  4. 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
  5. 该类是程序中默认的类加载器,一般来说,项目中自己写的类由应用程序类加载器加载
  6. 通过classLoader.getSystemclassLoader()方法可以获取到该类加载器

2.3.6双亲委派机制优势:

举例 1 :代码:我们自己建立一个 java.lang.String 类,写上 static 代码块
Java进阶之JVM(一)类加载器详解_第18张图片
因为双亲委派机制,所以要先自底向上去去检查有没有加载 java.lang.String,然后再自顶向下去尝试加载,由于Bootstrap启动类加载器里面有java.lang.String这个包,所以直接就在Bootstrap启动类加载器加载了,应用类加载器根本就没有机会去加载 java.lang.String

案例2:在我们自己的 String 类中整个 main() 方法
Java进阶之JVM(一)类加载器详解_第19张图片
举例 3 :在 java.lang 包下整个 Robot类
Java进阶之JVM(一)类加载器详解_第20张图片
通过上面的例子,我们可以知道,双亲机制可以1. 避免类的重复加载2. 保护程序安全,防止核心API被随意篡改 1. 自定义类:java.lang.String 没有用 2. 自定义类:java.lang. Robot(报错:阻止创建 java.lang开头的类)

你可能感兴趣的:(Java学习,Java,jvm,java,面试)