思维导图

一、JVM介绍

在介绍JVM之前,先看一下.java文件从编码到执行的过程:

整个过程是,x.java文件需要编译成x.class文件,通过类加载器加载到内存中,然后通过解释器或者即时编译器进行解释和编译,最后交给执行引擎执行,执行引擎操作OS硬件。

类加载器到执行引擎这块内容就是JVM

JVM是一个跨语言的平台。从上面的图中可以看到,实际上JVM上运行的不是.java文件,而是.class文件。这就引出一个观点,JVM是一个跨语言的平台,他不仅仅能跑java程序,只要这种编程语言能编译成JVM可识别的.class文件都可以在上面运行。

所以除了java以外,能在JVM上运行的语言有很多,比如JRuby、Groovy、Scala、Kotlin等等。

从本质上讲JVM就是一台通过软件虚拟的计算机,它有它自身的指令集,有它自身的操作系统。

所以Oracle给JVM定了一套JVM规范,Oracle公司也给出了他的实现。基本上是目前最多人使用的java虚拟机实现,叫做Hotspot。使用java -version可以查看:

一些体量较大,有一定规模的公司,也会开发自己的JVM虚拟机,比如淘宝的TaobaoVM、IBM公司的J9-IBM、微软的MicrosoftVM等等。

二、JDK、JRE、JVM

JVM应该很清楚了,是运行.class文件的虚拟机。JRE则是运行时环境,包括JVM和java核心类库,没有核心的类库是跑不起来的。

JDK则包括JRE和一些开发使用的工具集。

所以总的关系是JDK > JRE > JVM

三、Class加载过程

类加载是JVM工作的一个很重要的过程,我们知道.class是存在在硬盘上的一个文件,如何加载到内存工作的呢,面试中也经常问这个问题。所以你要和其他程序员拉开差距,体现差异化,这个问题要搞懂。

类加载的过程实际上分为三大步:Loading(加载)、Linking(连接)、Initlalizing(初始化)

其中第二步Linking又分为三小步:Verification(验证)、Preparation(准备)、Resolution(解析)

3.1 Loading

Loading是把.class字节码文件加载到内存中,并将这些数据转换成方法区中的运行时数据,在堆中生成一个java.lang.Class类对象代表这个类,作为方法区这些类型数据的访问入口

3.2 Linking

Linking简单来说,就是把原始的类定义的信息合并到JVM运行状态之中。分为三小步进行。

3.2.1 Verification

验证加载的类信息是否符合class文件的标准,防止恶意信息或者不符合规范的字节信息。是JVM虚拟机运行安全的重要保障。

3.2.2 Preparation

创建类或者接口中的静态变量,并初始化静态变量赋默认值。赋默认值不是赋初始值,比如static int i = 5,这一步只是把i赋值为0,而不是赋值为5。赋值为5是在后面的步骤。

3.2.3 Resolution

把class文件常量池里面用到的符号引用转换成直接内存地址,直接可以访问到的内容。

3.3 Initlalizing

这一步真正去执行类初始化clinit()(类构造器)的代码逻辑,包括静态字段赋值的动作,以及执行类定义中的静态代码块内(static{})的逻辑。当初始化一个类时,发现父类还没有进行过初始化,则先初始化父类。虚拟机会保证一个类的clinit()方法在多线程环境中被正确加锁和同步。

四、类加载器

上面就是类加载的整个过程。而最后一步Initlalizing是通过类加载器加载类。类加载器这里我单独讲一下,因为这是一个重点。

Java中的类加载器由上到下分为:

  • Bootstrap ClassLoader(启动类加载器)
  • ExtClassLoader(扩展类加载器)
  • AppClassLoader(应用程序类加载器)

从类图,可以看到ExtClassLoader和AppClassLoader都是ClassLoader的子类

所以如果要自定义一个类加载器,可以继承ClassLoader抽象类,重写里面的方法。重写什么方法后面再讲。

五、双亲委派机制

讲完类加载器,这些类加载器是怎么工作的呢。对于双亲委派机制可能多多少少有听过,没听过也没关系,我正要讲。

上面说过有Bootstrap,ExtClassLoader,AppClassLoader三个类加载器。工作机制如下:

加载类的逻辑是怎么样的呢,核心代码是可以在JDK源码中找到的,在抽象类ClassLoader类的loadClass(),有兴趣可以源码看看:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {//如果上层有类加载器,递归向上,往上层的类加载器寻找c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}//如果上层的都找不到相应的classif (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();//自己去加载c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

其实整个逻辑已经很清晰了,为了更好理解,我这里画张图给给大家,更好理解一点:

看到这里,应该都清楚了双亲委派机制的流程了。重点来了,为什么要使用双亲委派机制呢?

如果面试官问这个问题,一定要答出关键字:安全性

反证法来辩证。假设不采用双亲委派机制,那我可以自定义一个类加载器,然后我写一个java.lang.String类用自定义的类加载器加载进去,原来java本身又有一个java.lang.String类,那么类的唯一性就没法保证,就不就给虚拟机的安全带来的隐患了吗。所以要保证一个类只能由同一个类加载器加载,才能保证系统类的的安全

六、自定义类加载器

自定义类加载器,上面讲过可以有样学样,自定义一个类继承ClassLoader抽象类。重写哪个方法呢?loadClass()方法是加载类的方法,重写这个不就行了?

如果重写loadClass()那证明有思考过,但是不太对,因为重写loadClass()会破坏了双亲委派机制的逻辑。应该重写loadClass()方法里的findClass()方法。

findClass()方法才是自定义类加载器加载类的方法。

那findClass()方法源码是怎么样的呢?

明显这个方法是给子类重写用的,权限修饰符也是protected,如果不重写,那就会抛出找不到类的异常。如果学过设计模式的同学,应该看得出来这里用了模板模式的设计模式。所以我们自定义类加载器重写此方法即可。开始动手!

创建CustomerClassLoader类,继承ClassLoader抽象类的findClass()方法。

public 

这样就完成了,接下来测试一下,定义一个Hello类。

public class Hello {public void say() {System.out.println("hello.......java");}
}

使用javac命令编译成class文件,如下图:

最后写个main方法运行测试一把:

public class Main {public static void main(String[] args) throws Exception {String path = "D:mallcoresrcmainjavaiogithubyehongzhiclassloaderHello.class";CustomerClassLoader classLoader = new CustomerClassLoader(path);Class<?> clazz = classLoader.findClass("io.github.yehongzhi.classloader.Hello");System.out.println("使用类加载器:" + clazz.getClassLoader());Method method = clazz.getDeclaredMethod("say");Object obj = clazz.newInstance();method.invoke(obj);}
}

运行结果:

七、破坏双亲委派机制

看到这里,你肯定会很疑惑。上面不是才讲过双亲委派机制为了保证系统的安全性吗,为什么又要破坏双亲委派机制呢?

重温一下双亲委派机制,应该还记得,就是底层的类加载器一直委托上层的类加载器,如果上层的已经加载了,就无需加载,上层的类加载器没有加载则自己加载。这就突出了双亲委派机制的一个缺陷,就是只能子的类加载器委托父的类加载器,不能反过来用父的类加载器委托子的类加载器

那你会问,什么情况会出现父的类加载器委托子的类加载器呢?

还真有这个场景,就是加载JDBC的数据库驱动。在JDK中有一个所有 JDBC 驱动程序需要实现的接口Java.sql.Driver。而Driver接口的实现类则是由各大数据库厂商提供。那问题就出现了,DriverManager(JDK的rt.jar包中)要加载各个实现了Driver接口的实现类,然后进行统一管理,但是DriverManager是由Bootstrap类加载器加载的,只能加载JAVA_HOME下lib目录下的文件(可以看回上面双亲委派机制的第一张图),但是实现类是服务商提供的,由AppClassLoader加载,这就需要Bootstrap(上层类加载器)委托AppClassLoader(下层类加载器),也就破坏了双亲委派机制。这只是其中一种场景,破坏双亲委派机制的例子还有很多。

那么怎么实现破坏双亲委派机制呢?

  • 最简单就是自定义类加载器,前面讲过为了不破坏双亲委派机制重写findClass()方法,所以如果我要破坏双亲委派机制,那就重写loadClass()方法,直接把双亲委派机制的逻辑给改了。在JDK1.2后不提倡重写此方法。所以提供下面这种方式。
  • 使用线程上下文件类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是AppClassLoader类加载器

那么刚刚说的JDBC又是采用什么方式破坏双亲委派机制的呢?

当然是采用上下文文件类加载器,还有使用了SPI机制,下面一步一步分解。

第一步,Bootstrap加载DriverManager类,在DriverManager类的静态代码块调用初始化方法。

public class DriverManager {static {loadInitialDrivers();println("JDBC DriverManager initialized");}
}

第二步,加载Driver接口的所有实现类,得到Driver实现类的集合,获取一个迭代器。

第三步,看ServiceLoader.load()方法。

第四步,看迭代器driversIterator。

接着一直找下去,就会看到一个很神奇的地方。

而这个常量值PREFIX则是:

private static final String PREFIX = "META-INF/services/";

所以我们可以在mysql驱动包中找到这个文件:

通过文件名找接口的实现类,这是java的SPI机制。到此为止,破案了大人!

作为暖男的我,就画张图,总结一下整个过程吧:

总结

这篇文章主要介绍了JVM,然后讲到JVM的类加载机制的三大步骤,接着讲自定义类加载器以及双亲委派机制。最后再深入探讨了为什么要使用双亲委派机制,又为什么要破坏双亲委派机制的问题。可能讲得有点长,不过我相信应该都看懂了,因为我讲得比较通俗,而且图文并茂。

上面所有例子的代码都上传Github了:

https://github.com/yehongzhi/mall

觉得有用就点个赞吧,你的点赞是我创作的最大动力~

拒绝做一条咸鱼,我是一个努力让大家记住的程序员。我们下期再见!!!

能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!

ie不加载jre_详细讲解!从JVM直到类加载器相关推荐

  1. 深入理解JVM(6)——类加载器

    虚拟机设计团队把类加载阶段中的"通过一个类的全限定名来获取描述此类的二进制字节流(即字节码)"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现 ...

  2. JVM之类加载器ClassLoader

    JVM之类加载器ClassLoader 本文目录 JVM简介 类加载器解析 1. JVM简介 ①. JVM是运行在操作系统之上的,它与硬件没有直接的交互 ②. JVM体系结构概览 注: 2. 类装载器 ...

  3. 细说JVM(类加载器)

    一.类加载器的基本概念 顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中.一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java ...

  4. 【JVM】类加载器:双亲委派机制、沙箱安全机制

    · 双亲委派机制.沙箱安全机制是JVM中类加载器系统的相关术语 · 在这之前,应该先了解JVM类加载器系统的相关概念 一.类加载器基础知识 见下图1,java文件首先会被编译成class文件,clas ...

  5. jvm十三:类加载器命名空间

    package com.atChina.jvm;import java.io.*;public class Test16 extends ClassLoader{private String clas ...

  6. jdk包含java语言核心的类_1.1 jvm核心类加载器--jdk源码剖析

    目录 前提: 运行环境 1. 类加载的过程 1.1 类加载器初始化的过程 1.2 类加载的过程 1.3 类的懒加载 2. jvm核心类加载器 3. 双亲委派机制 4. 自定义类加载器 5. tomca ...

  7. JVM自定义类加载器在代码扩展性的实践

    一.背景 名单管理系统是手机上各个模块将需要管控的应用配置到文件中,然后下发到手机上进行应用管控的系统,比如各个应用的耗电量管控:各个模块的管控应用文件考虑到安全问题,有自己的不同的加密方式,按照以往 ...

  8. JVM——自定义类加载器

    0. 为什么需要自定义类加载器   网上的大部分自定义类加载器文章,几乎都是贴一段实现代码,然后分析一两句自定义ClassLoader的原理.但是我觉得首先得把为什么需要自定义加载器这个问题搞清楚,因 ...

  9. jvm十一:类加载器双亲委托机制

    在双亲委托机制中,各个加载器按照父子关系形成了树形结构,除了根类加载器之外,其余的类加载器都且只有一个父加载器. package com.atChina.jvm;import sun.awt.Symb ...

最新文章

  1. tcp 和 dcp 的几大区别
  2. 获取显示器的唯一编号_宝马奔驰才配拥有的HUD抬头显示器,买菜车也能无损安装!...
  3. javascript数字格式化通用类——accounting.js使用
  4. SmartDial - 简单你的生活
  5. Financial Managemen
  6. 百度启动史上规模最大校园招聘;荣耀Magic3系列售价4599元起;阿里成立反职场陋习小组,无条件支持员工拒绝劝酒|极客头条...
  7. IT软件创业之 -- 小软件项目也有风险、也会失败、也会损兵折将
  8. C# 创建、部署和调用WebService的简单示例
  9. 又丢脸了,“要源码上门自取”,结果美女真上门了!国内企业再惹争议
  10. ADO中最重要的对象有三个:Connection、Recordset和Command
  11. 《CCNA网络技术学习指南》-命令版
  12. TEncCu::xCheckRDCostMerge2Nx2N
  13. vue中使用leaflet加载地图影像并拾取坐标点
  14. html5实现在线动态画板,HTML5 canvas实现一个简易画板
  15. win10装debian 双系统_超详细小白部署win10和debian10双系统教程
  16. 朋友会触动我们的心灵
  17. 计算机主板 也叫系统板或母版,电脑主板与CPU常见故障维修
  18. python爬取网易云音乐百强榜单
  19. 目标跟踪数据集GOT-10k的配置
  20. miRNA数据库篇——miRDB:软件预测的哺乳动物miRNA靶基因数据库(假阳性较高)

热门文章

  1. 产生随机小数_如果取到小数区间内的任一数字?
  2. jsp怎么调用servlet_Servlet简述
  3. is not a function_libcxx 的 std::function 源码分析
  4. 微型计算机二进制,微型计算机原理二进制十进制十六进制.docx
  5. vue用公共组件页面传值_vuejs几种不同组件(页面)间传值的方式
  6. sql注入 练手网站_靶场sql注入练手----sqlmap篇(纯手打)
  7. 用遗传算法求3维函数 的最小值_遗传算法可视化项目(4):遗传算法
  8. python3 x和python2 x区别_Python3.x和Python2.x的区别(转存参考)
  9. python3 判断文件是否存在_Python判断文件是否存在的三种方法
  10. automapper自动创建映射_ASP.NET Core教程:ASP.NET Core使用AutoMapper