通过实例解析Java class文件编译加载过程

 更新时间:2020年7月26日 08:31  点击:2101

一、Java从编码到执行

首先我们来看一下Java是如何从编码到执行的呢? 我们有一个x.java文件通过执行javac命令可以变成x.class文件,当我们调用Java命令的时候class文件会被装载到内存中,这个过程叫做classloader。一般情况下我们自己写代码的时候会用到Java的类库,所以在加载的时候也会把Java类库相关的类也加载到内存中。装载完成之后会调用字节码解释器和JIT即时编译器来进行解释和编译,编译完之后由执行引擎开始执行,执行引擎下面对应的就是操作系统硬件了。下图是大体的流程:

Java叫做跨平台的语言,JVM可以称之为跨语言的平台;

有个问题:java是解释执行还是编译执行?答:解释和编译是可以混合的,特别常用的代码或则是代码用到的次数特别多的时候,会把一个即时编译做成本地编译,这样会很大程度上的提高效率。

Java虚拟机是如何做到这么多语言都可以在上面运行,关键在于class文件,任何语言只要能编译成class文件,并且符合class文件的规范你就可以放在Java虚拟机上去运行。

二、详解class文件的加载过程

接下来主要讲的是一个class文件是怎么从硬盘上到内存中,并开始执行的。

类加载主要有三个过程:loading 、linking 、initializing;其中linking又分为三个步骤:verification 、preparation 、resolution;

1、首先Loading是什么意思呢?是把一个class问价load到内存中去;

2、接下来是Linking分为了三小步:

  • verification 是用来校验加载进来的class文件是否符合class文件标准,如果不符合直接就会被拒绝了;
  • preparation 是将class文件静态变量赋默认值而不是初始值,例如static int i =8;这个步骤并不是将i赋值为8,而是赋值为默认值0;
  • resolution 是把class文件常量池中用到的符号引用转换成直接内存地址,可以访问到的内容;

3、initializing 成为初始化,静态变量在这个时候才会被赋值为初始值;

下面为类加载过程的简化图:

类加载器的加载过程是分成不同的层次来加载的,不同的类加载器来加载不同的class文件, Bootstrap >Extension>Application>Custom(自定义类加载器)

1、第一个类加载器的层次为:Bootstrap 称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库。

2、第二个类加载器的层次为:Extension 是用来加载扩展类的,主要负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有jar包。

3、第三个类加载器的层次为:Application又称为系统类加载器,负责在JVM启动时,加载来自在命令java中的classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的JAR类包和类路径。

4、第三个类加载器的层次为:CustomClassLoader(自定义加载器)

package com.example.demo.classloader;

public class ClassLoaderScope {
  public static void main(String[] args) {
    System.out.println("-------------------Bootstrap加载类-------------------");
    String property = System.getProperty("sun.boot.class.path");
    String s = property.replaceAll(";", System.lineSeparator());
    System.out.println(s);

    System.out.println("-------------------Ext加载类-------------------");

    String property1 = System.getProperty("java.ext.dirs");
    String s1 = property1.replaceAll(";", System.lineSeparator());
    System.out.println(s1);

    System.out.println("-------------------App加载类-------------------");

    String property2 = System.getProperty("java.class.path");
    String s2 = property2.replaceAll(";", System.lineSeparator());
    System.out.println(s2);
  }
}
    /**输出结果只截取了部分*/
    //E:\JDK\jdk1.8\jre\lib\resources.jar
    //E:\JDK\jdk1.8\jre\lib\rt.jar
    //E:\JDK\jdk1.8\jre\lib\sunrsasign.jar
    //E:\JDK\jdk1.8\jre\lib\jsse.jar
    //E:\JDK\jdk1.8\jre\lib\jce.jar
    //E:\JDK\jdk1.8\jre\lib\charsets.jar
    //E:\JDK\jdk1.8\jre\lib\jfr.jar
    //E:\JDK\jdk1.8\jre\classes
    //----------------------------------------------
    //E:\JDK\jdk1.8\jre\lib\ext
    //C:\Windows\Sun\Java\lib\ext
    //----------------------------------------------
    //E:\JDK\jdk1.8\jre\lib\charsets.jar
    //E:\JDK\jdk1.8\jre\lib\deploy.jar
    //E:\JDK\jdk1.8\jre\lib\ext\access-bridge-64.jar
    //E:\JDK\jdk1.8\jre\lib\ext\cldrdata.jar
    //E:\JDK\jdk1.8\jre\lib\ext\dnsns.jar
    //E:\JDK\jdk1.8\jre\lib\ext\jaccess.jar
    //E:\JDK\jdk1.8\jre\lib\ext\jfxrt.jar

特别注意一点这个的层级关系并没有继承的关系在里面,只是单单纯纯的语法上的继承;

下图为类加载的一个全过程:

用比较通俗的话来解释这个过程,当有一个类需要被加载时,首先要判断这个类是否已经被加载到内存,判断加载与否的过程是有顺序的,如果有自己定义的类加载器,会先到custom class loader 的cache(缓存)中去找是否已经加载,若已加载直接返回结果,否则到App的cache中查找,如果已经存在直接返回,如果不存在,到Extension中查找,存在直接返回,不存在继续向父加载器中寻找直到Bootstrap顶层,如果依然没找到,那就是没有加载器加载过这个类,需要委派对应的加载器来加载,先看看这个类是否在自己的加载范围内,如果是直接加载返回结果,若不是继续向下委派,以此类推直到最下级,如果最终也没能加载,就会直接抛异常ClassNotFoundException,这就是双亲委派模式。

理解双亲委派模式:

1、父加载器:不是类加载器的加载器,也不是类加载器的父类加载器(此处意思是没有父类与子类之间的继承关系)。

package com.example.demo.classloader;

/**
 * 验证了父加载器不是加载器的加载器
 */
public class ParentAndChild {
  public static void main(String[] args) {
    //AppClassLoader
    ClassLoader classLoader = ParentAndChild.class.getClassLoader();
    System.out.println(classLoader);

    //null 这里AppClassLoader的加载器不是ExtClassLoader 而是Bootstrap
    ClassLoader appclassLoader = ParentAndChild.class.getClassLoader().getClass().getClassLoader();
    System.out.println(appclassLoader);

    //ExtClassLoader  AppClassLoader的父加载器是ExtClassLoader
    ClassLoader parent = ParentAndChild.class.getClassLoader().getParent();
    System.out.println(parent);

    //null
    ClassLoader parentparent = ParentAndChild.class.getClassLoader().getParent().getParent();
    System.out.println(parentparent);

    //null
    ClassLoader parentparentparent = ParentAndChild.class.getClassLoader().getParent().getParent().getParent();
    System.out.println(parentparent);

    /**输出结果*/
    //sun.misc.Launcher$AppClassLoader@18b4aac2
    //null
    //sun.misc.Launcher$ExtClassLoader@23fc625e
    //null
    //Exception in thread "main" java.lang.NullPointerException at com.example.demo.classloader.ParentAndChild.main(ParentAndChild.java:22)
  }
}

2、双亲委派:其工作原理的是,如果一个类加载器收到了类加载请求,并不会直接去加载,而是自下而上的向顶层类加载器查找是否已经被加载了,如果被加载就不用进行加载,如果未被加载过,则会自上而下的检查是否属于自己加载的范围,如果属于则加载,如果不属于则向下委托,直到类被加载进来才能叫做成功,如果加载不成功就会抛异常classnotfoundexeption,这就叫做双亲委派。

3、为什么要搞双亲委派模式?

主要是为了安全,这里可以使用反证法,如果任何类加载器都可以把class加载到内存中,我们就可以自定义类加载器来加载Java.lang.string。在打包时可以把密码存储为String对象,偷偷摸摸的把密码发送到自己的邮箱,这样会造成安全问题。

三、自定义类加载器

package com.example.demo.classloader;

public class ClassLoaderByHand {
  public static void main(String[] args) throws ClassNotFoundException {
    Class<?> clazz = ClassLoaderByHand.class.getClassLoader().
        loadClass("com.example.demo.threaddemo.juc_002.Account");
    String name = clazz.getName();
    System.out.println(name);

  }
}

  /**
  * 输出结果
  */
  //com.example.demo.threaddemo.juc_002.Account

代码运行结果可以看出,就是你要加载一个类你只要调用classLoader中的loadClass()方法就能把这个类加载到内存中,加载完成之后会给你返回一个Class类的对象。

在硬盘上找到这个类的源码,把它load到内存,与此同时生成一个Class对象,上述的小程序是通过ClassLoaderByHand 找到他的加载器AppClassLoader 然后调用它的loadClass()方法,让它帮我们把Account类加载进来,返回一个clazz对象,使用clazz.getName()方法正常返回Account类。

什么时候我们需要自己定义去加载一个类?

热部署时就是先把之前加载的类给干掉 ,然后使用的自定义类加载器来进行重新加载

spring的动态代理,一个新的class 当需要的时候就会把它load到内存中

我们还是来看一下源码吧,加载过程最主要的还是ClassLoader中的loaderClass()方法:

结合上面给的类加载过程的图解一起看会更容易一些;

protected Class<?> loadClass(String name, boolean resolve)
      throws ClassNotFoundException
  {
    synchronized (getClassLoadingLock(name)) {
      /**
       * 在加载之前先调用findLoadedClass()方法查看是否已经加载过此类
       * 若加载过 返回该对象
       * 如果未加载则返回null 进行下一步
       */
      // First, check if the class has already been loaded
      Class<?> c = findLoadedClass(name);
      if (c == null) {
        long t0 = System.nanoTime();
        try {
          //判断有无父加载器 如果不为空说明还未到顶层Bootstrap递归调用loadClass()
          if (parent != null) {
            c = parent.loadClass(name, false);
          } else {
            //如果没有父加载器说明调用的加载器为Bootstrap Class Loader, 在此加载器内存中查找是否已经加载
            c = findBootstrapClassOrNull(name);
          }
        } catch (ClassNotFoundException e) {
          // ClassNotFoundException thrown if class not found
          // from the non-null parent class loader
        }
        //若以上的操作都没成功加载此类
        if (c == null) {
          // If still not found, then invoke findClass in order
          // to find the class.
          long t1 = System.nanoTime();
          //调用自己的findClass()
          c = findClass(name);

          // this is the defining class loader; record the stats
          sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
          sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
          sun.misc.PerfCounter.getFindClasses().increment();
        }
      }
      if (resolve) {
        resolveClass(c);
      }
      return c;
    }
  }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。

[!--infotagslink--]

相关文章

  • Java实现经典游戏复杂迷宫

    这篇文章主要介绍了如何利用java语言实现经典《复杂迷宫》游戏,文中采用了swing技术进行了界面化处理,感兴趣的小伙伴可以动手试一试...2022-02-01
  • java 运行报错has been compiled by a more recent version of the Java Runtime

    java 运行报错has been compiled by a more recent version of the Java Runtime (class file version 54.0)...2021-04-01
  • 在java中获取List集合中最大的日期时间操作

    这篇文章主要介绍了在java中获取List集合中最大的日期时间操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
  • 用js的document.write输出的广告无阻塞加载的方法

    一、广告代码分析很多第三方的广告系统都是使用document.write来加载广告,如下面的一个javascript的广告链接。复制代码 代码如下:<script type="text/javascript" src="http://gg.5173.com/adpolestar/5173/;ap=2EBE5...2014-06-07
  • 教你怎么用Java获取国家法定节假日

    这篇文章主要介绍了教你怎么用Java获取国家法定节假日,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下...2021-04-23
  • Java如何发起http请求的实现(GET/POST)

    这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-31
  • 浅谈Java与C#的一些细微差别

    说起C#和Java这两门语言(语法,数据类型 等),个人以为,大概有90%以上的相似,甚至可以认为几乎一样。但是在工作中,我也发现了一些细微的差别...2020-06-25
  • PHP Fatal error: Cannot use object of type stdClass as array in错误

    下面一起来看看在php开发中碰到PHP Fatal error: Cannot use object of type stdClass as array in错误问题的解决办法吧。 普通的数组出现如下错误 代码...2016-11-25
  • vue3如何按需加载第三方组件库详解

    距离 Vue 3.0 正式版发布已经有一段时间了,关于vue3组件库相关的问题还是挺多人感兴趣的,这篇文章主要给大家介绍了关于vue3如何按需加载第三方组件库的相关资料,需要的朋友可以参考下...2021-06-02
  • jQuery页面加载初始化常用的三种方法

    当页面打开时我们需要执行一些操作,这个时候如果我们选择使用jquery的话,需要重写他的3中方法,自我感觉没什么区 别,看个人喜好了,第二种感觉比较简单明了: 第一种: 复制代码 代码如下: <script type="text/javas...2014-06-07
  • 解决Java处理HTTP请求超时的问题

    这篇文章主要介绍了解决Java处理HTTP请求超时的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-29
  • 解决IDEA插件市场Plugins无法加载的问题

    这篇文章主要介绍了解决IDEA插件市场Plugins无法加载的问题,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-10-21
  • java 判断两个时间段是否重叠的案例

    这篇文章主要介绍了java 判断两个时间段是否重叠的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • java 画pdf用itext调整表格宽度、自定义各个列宽的方法

    这篇文章主要介绍了java 画pdf用itext调整表格宽度、自定义各个列宽的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-31
  • 超简洁java实现双色球若干注随机号码生成(实例代码)

    这篇文章主要介绍了超简洁java实现双色球若干注随机号码生成(实例代码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-02
  • Java生成随机姓名、性别和年龄的实现示例

    这篇文章主要介绍了Java生成随机姓名、性别和年龄的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-01
  • AngularJS 实现按需异步加载实例代码

    AngularJS 通过路由支持多视图应用, 可以根据路由动态加载所需的视图, 在 AngularJS 的文档中有详细的介绍, 网上也有不少教程, 就不用介绍了!随着视图的不断增加,js文件会越来越多,而 AngularJS 默认需要把全部的js都一次性...2015-10-21
  • 几种延迟加载JS代码的方法加快网页的访问速度

    本文介绍了如何延迟javascript代码的加载,加快网页的访问速度。 当一个网站有很多js代码要加载,js代码放置的位置在一定程度上将会影像网页的加载速度,为了让我们的网页加载速度更快,本文总结了一下几个注意点...2013-10-13
  • java正则表达式判断前端参数修改表中另一个字段的值

    这篇文章主要介绍了java正则表达式判断前端参数修改表中另一个字段的值,需要的朋友可以参考下...2021-05-07