什么是类加载器

类加载器负责将class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象。Java开发中无须过分关心类加载机制,但所有的编程人员都应该了解其工作机制,明白如何做才能让其更好地满足我们的编程需要。
 
 

细说类加载机制

1,一个类只会被加载一次:
类加载器负责加载所有的类,系统为所有被载入内存中的类生成java.lang.Class实例。只要一个类被载入JVM中,同一个类就不会被再次载入了。现在的问题是,怎么样才算"同一个类"?
我们的对象实例是不是都有一个hashCode来作为其唯一的标识?在JVM中一个类也有其对应的唯一标识。这个唯一标识使用全限定类名(包名全路径+类名)来作为标识的。
 
 

三种类加载器:

JVM 启动时,会形成由三个类加载器组成的初始类加载器层次结构。
  • Bootstrap ClassLoader: 根类加载器, 平台加载器的父加载器
  • Platform ClassLoader: 平台类加载器,系统加载器的父加载器。在Java8和之前,这个加载器应该叫做扩展加载器(ExtClassLoader)
  • System ClassLoader: 系统类加载器。
 

1,根类加载器:

Bootstrap ClassLoader被称为引导加载器,有些书上也叫做原始类加载器或者根类加载器。它负责加载Java的核心类。这个加载器是由JVM自己实现的(C/C++)实现,所以我们没办法直接使用到对应的类。
这个加载器加载的类的路径是jdk安装目录下面对应jre/lib目录下的核心库

2,平台类加载器:

如果是java8以及之前,这个对应的是扩展加载器。加载的是jre/lib/ext目录下的扩展包。而在java8之后,平台类加载器只是为了向后兼容而保留,而不会加载任何东西了。

3,系统加载器:

加载的是对应的入口程序所在的包。
编写代码查看类加载器路径:

package com.vgxit.classloeader;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
public class ClassLoaderPropTest {
public static void main(String[] args) throws IOException {
//首先获取对应的系统加载器
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统加载器:" + systemLoader);
//打印系统加载器的路径
Enumeration<URL> enumeration = systemLoader.getResources("");
while (enumeration.hasMoreElements()) {
System.out.println(URLDecoder.decode(enumeration.nextElement().toString(), "utf-8"));
}
//获取系统加载器的父加载器(平台类加载器)
ClassLoader platformLoader = systemLoader.getParent();
System.out.println("平台类加载器" + platformLoader);
//打印一下平台类加载器的路径
Enumeration<URL> enumeration1 = platformLoader.getResources("");
while (enumeration1.hasMoreElements()) {
System.out.println(URLDecoder.decode(enumeration1.nextElement().toString(), "utf-8"));
}
//获取根加载器
ClassLoader bootstrapLoader = platformLoader.getParent();
System.out.println("根加载器是:" + bootstrapLoader);
}
}
可以看出:
系统加载器AppClassLoader的加载路径就是当前程序的运行的路径 。
平台类加载器是PlatformClassLoader。如果是在java8下面运行打印出来是ExtClassLoader。平台类加载器的路径没有(这个和Java8是有区别的)。
跟加载器返回的是null。这个是因为根加载器不是用java实现的,并没有继承我们的ClassLoader抽象类。所以说我们获取不到。但是实际上根类加载器就是我们平台类加载器的父加载器。

三种类加载机制:

  • 全盘负责:所谓全盘负责,就是当一个类加载器负责加载某C1ass时,该class所依赖的和引用的其他class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入
  • 父类委托:所谓父类委托,是先让parent(父)类加载器试图加载该class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
  • 缓存机制:缓存机制将会保证所有加载过的C1ass都会被缓存,当程序中需要使用某个class时,类加载器先从缓存区中搜寻该C1ass,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成C1ass对象,存入缓存区中。这就是为什么修改了代码后,必须重新启动JVM程序所做的修改才会生效的原因。

类加载器工作步骤

自定义类加载器

JVM中除根类加载器之外的所有类加载器都是ClassLoader的子类的实例,开发者可以通过扩展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的加载器。
 
ClassLoader 类有如下两个关键方法:
  • (1), loadClass(String name, boolean resove) 该方法是ClassLoader的入口点,根据指定名称来加载。系统就是调用ClassLoader的该方法来获取指定类Class对象。
  • (2), findClass(String name): 根据指定名称来加载类。
 
如果需要实现自定义的ClassLoader,则可以重写这两个方法来实现,通常推荐重写 findClass方法,而不是重写loadClass方法。loadClass方法的执行步骤如下:
  • 用findLoadedClass(String name)来检查是否己经加载类,如果已经加载则直接返回。
  • 在父类加载器上调用loadClass方法。 果父类加载器为null,则使用根类加载器来加载
  • 调用findClass(String)方法查找类
    package com.zmd.myclassloader;
    
    import java.io.*;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method; /**
    * @ClassName MyClassLoader
    * @projectName: object1
    * @author: Zhangmingda
    * @description: 自定义加载器,继承ClassLoader类
    * date: 2021/5/15.
    */
    public class MyClassLoader extends ClassLoader{
    /**
    * @param name 重写findClass 方法 name为类名称
    * @return 返回加载好的类
    * @throws ClassNotFoundException 异常
    */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    //定义好要返回的类
    Class<?> cls = null;
    //替换类名路径.为目录/
    String filePath = name.replace('.','/');
    String javaFileName = filePath + ".java";
    String classFileName = filePath + ".class";
    File javaFile = new File(javaFileName);
    File classFile = new File(classFileName);
    System.out.println(javaFileName);
    if (!javaFile.exists() && !classFile.exists()){
    throw new ClassNotFoundException("类:" + name + "不存在");
    }else if (javaFile.exists()){
    if (! classFile.exists() || classFile.lastModified() < javaFile.lastModified()){
    try {
    if (! complie(javaFile) || ! classFile.exists()){
    throw new ClassNotFoundException("类:" + name + "编译失败");
    }
    } catch (IOException e) {
    e.printStackTrace();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    //编译成功,读取二进制文件,加载到内存中,转化成对应的Class对象
    try (FileInputStream fileInputStream = new FileInputStream(classFile)) {
    //定义存储二进制数据的byte数组
    byte[] classBytes = new byte[ (int) classFile.length()];
    int readLen = fileInputStream.read(classBytes);
    //转化成Class对象
    cls = defineClass(name,classBytes,0, readLen);
    } catch (FileNotFoundException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    return cls;
    }
    /**
    * 编译动作
    */
    private boolean complie(File javaFile) throws IOException, InterruptedException {
    System.out.println("开始编译:" + javaFile.getPath());
    //操作系统执行编译动作
    try {
    Process process = Runtime.getRuntime().exec("javac " + javaFile);
    process.waitFor();
    int result = process.exitValue();
    System.out.println("result:" + result);
    return result == 0;
    }catch (InterruptedException e) {e .printStackTrace();}
    //等待当前进程完成
    return false;
    }
    /**
    * 入口main方法测试
    */
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    if (args.length < 1){
    System.err.println("没有指定目标类");
    System.err.println(" java com.zmd.myclassloader.MyClassLoader com.zmd.myclassloader.Test  hh haa");
    return;
    }
    String className = args[0];
    String[] runArgs = new String[args.length -1];
    System.out.println("className:"+ className);
    System.arraycopy(args,1, runArgs,0, runArgs.length);
    //构建加载器,加载类
    MyClassLoader myClassLoader = new MyClassLoader();
    Class<?> cls = myClassLoader.loadClass(className);
    //通过反射执行
    Method method = cls.getMethod("main",runArgs.getClass());
    method.invoke(null,new Object[]{runArgs});
    }
    }

    Test 类

    package com.zmd.myclassloader;
    
    /**
    * @ClassName Test
    * @projectName: object1
    * @author: Zhangmingda
    * @description: XXX
    * date: 2021/5/15.
    */
    public class Test {
    public static void main(String[] args) {
    System.out.println("This is Test class ,args :" + args.toString() );
    }
    }

java 编程基础 类加载器的更多相关文章

  1. Java中的类加载器--Class loader

    学习一下Java中的类加载器,这个是比较底层的东西,好好学习.理解一下.  一.类加载器的介绍 1.类加载器:就是加载类的工具,在java程序中用到一个类,java虚拟机首先要把这个类的字节码加载到内 ...

  2. 黑马程序员——【Java高新技术】——类加载器

    ---------- android培训.java培训.期待与您交流! ---------- 一.概述 (一)类加载器(class loader) 用来动态加载Java类的工具,它本身也是Java类. ...

  3. Java中的类加载器

    转载:http://blog.csdn.net/zhangjg_blog/article/details/16102131 从java的动态性到类加载机制   我们知道,Java是一种动态语言.那么怎 ...

  4. Java中的类加载器以及Tomcat的类加载机制

    在加载阶段,虚拟机需要完成以下三件事情: 1.通过一个类的全限定名来获取其定义的二进制字节流. 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构. 3.在Java堆中生成一个代表这个类 ...

  5. Java入门——(1)Java编程基础

    Java入门--(1)Java编程基础 第二章 Java编程基础   JAVA 代码的基本格式: 修饰符 class 类名{ 程序代码 }   2.1关键字:赋予了特殊含义的单词.   2.2标识符: ...

  6. Java开发知识之Java编程基础

    Java开发知识之Java编程基础 一丶Java的基础语法 每个语言都有自己的语法规范.例如C++ 入口点是main. 我们按照特定格式编写即可. Java也不例外. Java程序的语法规范就是 Ja ...

  7. java编程基础二进制

    0.java编程基础 01.二进制(原码,反码,补码) 02.位运算 03.移位运算符 二进制 原码,反码,补码 1.基本概念 二进制是逢2进位的进位制,0,1是基本算符. 现在的电子计算机技术全部使 ...

  8. Java编程基础-面向对象(中)

    本章承接Java编程基础-面向对象(上)一文. 一.static关键字 在java中,定义了一个static关键字,它用于修饰类的成员,如成员变量.成员方法以及代码块等,被static修饰的成员具备一 ...

  9. Java编程基础——数组和二维数组

    Java编程基础——数组和二维数组 摘要:本文主要对数组和二维数组进行简要介绍. 数组 定义 数组可以理解成保存一组数的容器,而变量可以理解为保存一个数的容器. 数组是一种引用类型,用于保存一组相同类 ...

随机推荐

  1. 洛谷 P6199 - [EER1]河童重工(点分治+虚树)

    洛谷题面传送门 神仙题. 首先看到这样两棵树的题目,我们肯定会往动态树分治的方向考虑.考虑每次找出 \(T_2\) 的重心进行点分治.然后考虑跨过分治中心的点对之间的连边情况.由于连边边权与两棵树都有 ...

  2. 洛谷 P3647 [APIO2014]连珠线(换根 dp)

    题面传送门 题意: 桌子上有 \(1\) 个珠子,你要进行 \(n-1\) 次操作,每次操作有以下两种类型: 拿出一个新珠子,并选择一个桌子上的珠子,在它们之间连一条红线 选择两个由红线相连的珠子 \ ...

  3. 【豆科基因组】鹰嘴豆Chickpea (Cicer arietinum L.)429个自然群体重测序2019NG

    目录 一.来源 二.结果 材料测序.变异检测.群体结构和LD衰减 驯化后经历选择的候选基因组区域 起源中心.迁移路线和多样性 GWAS 一.来源 Resequencing of 429 chickpe ...

  4. DAS,NAS,SAN,简介

    根据服务器类型分为:封闭系统的存储和开放系统的存储,封闭系统主要指大型机,开放系统指基于Windows.UNIX.Linux等操作系统的服务器;开放系统的存储分为:内置存储和外挂存储;外挂存储根据连接 ...

  5. FASTA/Q序列处理神器---seqkit

    该软件对于处理FASTA/Q十分方便,省去自己编写脚本 安装 1 conda install seqkit 使用 序列操作(seq) 1 ## 取方向序列 2 seqkit seq test.fa - ...

  6. Excel-vlookup(查找值,区域范围,列序号,0)如何固定住列序列号,这样即使区域范围变动也不受影响

    突然,发现VLOOKUP的列序列号并不会随着区域范围的改变而自动调节改变,只是傻瓜的一个数,导致V错值.所有,就想实现随表格自动变化的列序号. 方法一:在列序号那里,用函数得出永远想要的那个列在区域范 ...

  7. 零基础学习java------day9------多态,抽象类,接口

    1. 多态 1.1  概述: 某一个事务,在不同环境下表现出来的不同状态 如:中国人可以是人的类型,中国人 p = new  中国人():同时中国人也是人类的一份,也可以把中国人称为人类,人类  d  ...

  8. 【leetcode】153. Find Minimum in Rotated Sorted Array

    Suppose an array of length n sorted in ascending order is rotated between 1 and n times. For example ...

  9. 前端两大框架 vue 和 react 的区别

    1. 设计思想 vue: vue的官网介绍说vue是一种渐进式框架,采用自底向上增量开发的设计: react: 采用函数式编程,推崇纯组件,数据不可变,单向数据流: 2. 编写语法 vue: 采用单文 ...

  10. Java易错小结

    String 相关运算 String使用是注意是否初始化,未初始化的全部为null.不要轻易使用 string.isEmpty()等,首先确保string非空. 推荐使用StringUtils.isN ...