java 编程基础 类加载器
什么是类加载器
类加载器负责将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() ); } }
posted on 2021-05-15 11:16 zhangmingda 阅读(96) 评论(0) 编辑 收藏 举报