当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、验证、准备、初始化4个步骤来对该类进行初始化。通过java命令执行代码流程如下:
其他loadClass的类加载过程有几步:
加载:加载class文件,使用到类的时候才会加载,列如调用main方法或者new对象等等
验证:校验字节码文件的正确性
准备:给类的静态变量分配内存,并赋予默认初始值
初始化:对静态变量进行初始化指定值,执行静态代码块
类加载到方法区中主要是包含了运行时常量、类型信息、字段信息、类加载器的引用、对应的class实例引用等相关信息
二、类加载器java有如下几种类加载器:
引导类加载器:负责加载支撑jvm运行的位于JRE的lib目录下的核心类库,例如rt.jar、charsets.jar
扩展类加载器:负责加载支撑jvm运行的位于JRE的lib目录下的ext扩展目录中的jar包
应用程序类加载器:负责加载classPath路径下的类包,通常就是加载我们自己写的类
自定义类加载器:负责加载用户指定目录下的类或包
类加载器初始化过程:首先会创建jvm启动实例sun.misc.Launcher。Launcher初始化使用的是单例设计模式,从而保证jvm虚拟机内存中只会存在一个Launcher实例。在Launcher内部构造方法会创建两个类加载器,分别是ExtClassLoader(扩展类加载器)、AppClassLoader(应用类加载器)。Launcher构造方法代码如下
//构造方法 public Launcher() { Launcher.ExtClassLoader var1; try { //构造扩展类加载器,在构造的过程会把对应的父加载器设置为null var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { //构造应用类加载器,在构造的过程会把对应的父加载器设置为ExtClassLoader //AppClassLoader加载器一般都是用加载我们自己写的应用程序 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } 。。。。。//省略 }三、双亲委派模式
jvm的类加载器是有对应的亲子模式如图下:
这里的类加载其实就是一个双亲委派机制。加载某个类是会先委派父加载器寻找目标类,如果找不到则会再委派上一层父加载器寻找目标类。当所有父加载器都没有找到对应的类时,则会在自己类加载路径中查找并加载类 。
列如加载一个Test类,最开始是找到应用程序类加载器,会在应用程序类加载器中先委托扩展类加载器,在扩展类加载器中会委托引导类加载器。如果Test类在引导类加载器没有找到对应的类,则会向下退回加载Test,当扩展类加载器接收到回退,则会在自己的类加载器路径中加载,在扩展类加载器没有加载到Test,则会回退到应用程序类加载器,于是会在应用程序类加载器的路径下找到对应的Test类,找到后则会进行加载
为什么会设计双亲委派机制?
- 沙箱安全机制:防止核心API库被随意篡改
- 避免类重复加载:当父类已经加载了该类时,没有必要在子类再进行一次ClassLoader再加载一次,保证被加载类的唯一性
自定义类加载器需要实现java.lang.ClassLoader类,该类由两个核心的方法,一个是loadClass(String name, boolean resolve)该方法实现了委派双亲模式,另外一个是findClass方法默认实现是空的方法。而实现自定义类加载器则需要重新loadClass方法。代码如下:
package com.cn.jvm; import java.io.FileInputStream; import java.lang.reflect.Method; public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } private byte[] loadByte(String name) throws Exception { name = name.replace(".", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } @Override protected Class> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。 return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class> c = findLoadedClass(name); if (c == null) { //非自定义类走委派双亲模式 if (!name.startsWith("com.cn.jvm")) { c = this.getParent().loadClass(name); } else { c = findClass(name); } long t0 = System.nanoTime(); // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); // 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; } } } public static void main(String[] args) throws Exception { // User user = new User(); //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader MyClassLoader classLoader = new MyClassLoader("D:/test"); //D盘创建 test/com/cn/jvm几级目录,将User类的复制类User1.class丢入该目录 Class clazz = classLoader.loadClass("com.cn.jvm.User1"); Object obj = clazz.newInstance(); Method method = clazz.getDeclaredMethod("sout", null); method.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); } }
package com.cn.jvm; public class User { private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void sout() { System.out.println("=======自己的加载器加载类调用方法======="); } }
以上是最近学习jvm类加载及类加载器的相关记录如有错误请多多指教。如有侵权请联系。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)