热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ie不加载jre_Java类加载过程

类加载过程简介类的加载过程主要包括:加载阶段、连接阶段、初始化阶段加载阶段:加载类的二进制数据文件(class文件)连接阶段:验证:确保类文件的正确性准备:类的静态文件分配内存&#

类加载过程简介

类的加载过程主要包括: 加载阶段、连接阶段、初始化阶段

d7741e9aa2b788853bceb987602a17f7.png

加载阶段: 加载类的二进制数据文件(class 文件)

连接阶段:

验证: 确保类文件的正确性
准备: 类的静态文件分配内存,并且初始化默认值
解析: 类中的符号引用转换为直接引用

初始化阶段: 为类的静态变量赋予正确的初始值

在Java 程序启动之后,可能会包含很多的类,当然并不是每一个类都不会被初始化,JVM 对类的初始化是一个延迟的机制,也就是lazy 的。

类的主动使用和被动使用

主动使用: 会使得类加载并初始化

  • new 关键字创建一个类
  • 访问类的静态变量包括读取和更新都会使得类初始化
  • 访问类的静态方法包括读取和更新都会使得类初始化
  • 对类进行反射操作的时候
  • 初始化子类导致父类的初始化
  • 当一个类中包括main 函数的时候,当这个main 函数得到启动的时候,这个类会初始化
被动使用: 不会使得类加载并初始化
  • 问类的静态常量(final 修饰的变量)不会使得类初始化
  • 构建数组的时候并不会使得类初始化

Simple[] simples = new Simple[10];

类的初始化阶段

类的初始化过程是整个类加载过程中最后一个阶段,在这个阶段做的做主要的一件事情就是执行() 方法,在这个方法中所有的类变量都会被赋予正确的值,也就是编写的时候指定的值。这个方法中包含所有类变量的赋值动作和静态语句块的执行代码,

另外() 方法和类的构造函数有所不同,它不需要显示的调用父类的构造器,虚拟机会保证父类的 () 方法最先执行,因此父类的静态变量总是能够得到优先赋值。

当主动使用触发了类的初始化之后就会调用这个方法。

static {try {System.out.println("static code will be inviked");TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {IntStream.range(0,5).forEach(i -> new Thread(DeadLock::new));}

执行上面的代码片段,在同一个世界,只能有一个线程执行到静态代码块中的内容,并且静态代码块中仅仅会被执行一次,JVM 保证了 () 方法在多线程的执行环境下的同步

类的加载器

类的加载器负责类的加载职责,任何一个对象的class 在JVM 中都只存在唯一的一份。

JVM 有三大类加载器,不同的加载器负责将不同的类加载到JVM 内存之中,并且它们之中严格遵守这父委托机制。

c6c4a2653b6a767cf671ecac08958fe7.png

Bootstrap

根加载器,为最顶层的加载器,主要负责虚拟机核心类库的加载如Java.lang库的加载,可以通过-Xbootclasspath 来指定根加载器的路径,也可以通过类属性来得知当前Jvm 的根加载器都加载类哪些资源。

System.out.println("Bootstrap" + String.class.getClassLoader());null
System.out.println(System.getProperty("sun.boot.class.path"));/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar

第一个获取的classloader 会是null,是因为根类加载器是获取不到引用的。

第二个输出更加在的加载路径

ExtClassLoader

扩展类加载器,这个类加载器是根加载器,它主要是加载JAVA_HOME 下的jre/lb/ext 目录里面的类库

System.out.println(System.getProperty("java.ext.dirs"));

获取扩展类加载资源的路径

ApplicationClassLoader

系统类加载器是一种常见的类加载器,其负责加载 classpath 下的类库资源,

System.out.println(System.getProperty("java.class.path"));
System.out.println(DeadLock.class.getClassLoader());

第一个打印出当前系统类加载器资源的路径,

第二个打印出当前类的类加载器,会输出

sun.misc.Launcher$AppClassLoader@18b4aac2

自定义类加载器

当然也是可以进行自定义的类加载器的

package thread.chapt1;public class MyClassLoader extends ClassLoader {private final static Path DEFAULT_CLASS_DIR = Paths.get("/Users/xuxliu/Ifoods/Java");// 默认的class 路径public MyClassLoader() {super();this.classDir = DEFAULT_CLASS_DIR;}private final Path classDir;// 指定父类加载器// 允许传入指定路径的 class 路径public MyClassLoader(String classDir, ClassLoader parent) {super(parent);this.classDir = Paths.get(classDir);}// 指定加载特定的目录public MyClassLoader(String classDir) {super();this.classDir = Paths.get(classDir);}// 重写父类的 findClass 这是非常重要的@Overrideprotected Class findClass(String name) throws ClassNotFoundException {// 读取二进制数据byte[] classBytes = new byte[0];try {classBytes = this.readClassBytes(name);} catch (IOException e) {e.printStackTrace();}if (classBytes == null || classBytes.length == 0) {throw new ClassNotFoundException("can not load the class");}// 调用 defineClass 方法定义class,读取class 文件return this.defineClass(name, classBytes, 0, classBytes.length);}// class 文件读入内存private byte[] readClassBytes(String name) throws ClassNotFoundException, IOException {// 包名分隔符专为文件路径String classPath = name.replace(".", "/");Path classFullPath = classDir.resolve(Paths.get(classPath) + name);if (!classFullPath.toFile().exists()){throw new ClassNotFoundException("the class " + name + " not found");}try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {Files.copy(classFullPath, baos);return baos.toByteArray();}}@Overridepublic String toString() {return "MyClassLoader";}public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {MyClassLoader myClassLoader = new MyClassLoader();Class aClass = myClassLoader.loadClass("thread.chapt1.HelloWorld");System.out.println(aClass.getClassLoader());Object helloWorld = aClass.newInstance();System.out.println(helloWorld);Method welcome = aClass.getMethod("welcome");String result = (String) welcome.invoke(helloWorld);System.out.println(result);}
}上面的代码先对程序进行编译,然后将编译后的class 文件夹复制到 /Users/xuxliu/Ifoods/Java 路径下
这里还需要删除掉 .java 文件,因为父委托机制会破坏我们的测试代码
程序在执行过程中,主要步骤是
1. myClassLoader.loadClass("thread.chapt1.HelloWorld");
2. 执行 readClassBytes 读取 class 文件
3. findClass 找到class
4. 在 findClass 方法中最后执行 defineClass 就定义好了,HelloWorld 类
5. Object helloWorld = aClass.newInstance(); 创建 HelloWorld 类对象
6. Method welcome = aClass.getMethod("welcome"); 反射获取方法
7. String result = (String) welcome.invoke(helloWorld); 反射执行方法

双亲委托机制

类加载中最重要的机制,双亲委托机制又称为是父委托机制,一个类加载器调用LoadClass 之后,它并不会直接将其加载,而是先交给当前父加载器尝试加载直到最顶层的父加载器。然后再依次向下加载。

5dcda29eedc434c1898357d65ef0e2d0.png

上面的例子中,为了防止双亲委托机制干扰到我们的测试,所以在执行的时候,将 .java 文件删除了。当然也是有不用删除就能让双亲委托机制不干扰测试代码的,也就是Load class 的时候,使用自定义的类。

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}if (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;}}

以上代码是LoadClass 中的源码,从源码中加载类的方式是

1. 当前类加载器中根据类的全路径名查询是否存在该类,如果存在则直接返回
2. 如果不存在,查看当前类是否存在父类加载器,存在,则直接调用父类加载器加载类
3. 如果当前父类不存在加载器,那么直接调用根类加载器对类进行加载
4. 如果当前类的所有父类加载器都没有成功加载class,则尝试调用当前类加载器的findClass 方法加载,这个方法就是我们定义加载器需要重写的方法

所以有两种方式可以绕过系统类加载器,直接讲扩展类加载器作为MyClassLoader 的父加载器,也就是上面我们另外的一个构造函数

public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {ClassLoader classLoader = MyClassLoader.class.getClassLoader();// 这个是原来的构造函数
// MyClassLoader myClassLoader = new MyClassLoader();// 这个是新的指定加载器的构造函数MyClassLoader myClassLoader = new MyClassLoader("/Users/xuxliu/Ifoods/Java/", classLoader);// 这也也可以破坏,双委托机制
// MyClassLoader myClassLoader = new MyClassLoader("/Users/xuxliu/Ifoods/Java/", null);Class aClass = myClassLoader.loadClass("thread.chapt1.HelloWorld");System.out.println(aClass.getClassLoader());Object helloWorld = aClass.newInstance();System.out.println(helloWorld);Method welcome = aClass.getMethod("welcome");String result = (String) welcome.invoke(helloWorld);System.out.println(result);}

上面的加载机制都还是没有避免一层一层的委托,也可以绕过这种机制,那么就是重写loadClass 方法。



推荐阅读
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
author-avatar
DalianLiLing_143
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有