因为热爱所以坚持,因为热爱所以等待。熬过漫长无戏可演的日子,终于换来了人生的春天,共勉!!!
1.类加载器
-
①.ClassLoader的作用 1.ClassLoader是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过各种方式将Class信息的二进制数据流读入JVM内部,转换为一个与目标类对应的java.lang.Class对象实例。然后交给Java虚拟机进行链接、初始化等操作、因此,ClassLoader在整个装载(加载)阶段,只能影响到类的加载,而无法通过ClassLoader去改变类的链接和初始化行为。至于它是否可以运行,则由Execution Engine决定 2.类加载器最早出现在Java1.0版本中,那个时候只是单纯地为了满足Java Applet应用而研发出来。但如今类加载器却在OSGI(热部署)、字节码加密解密领域大放异彩。这主要归功于Java虚拟机的设计者当初在设计类加载器的时候,并没有考虑将它绑定在Jvm内部,这样做的好处就是能够更加灵活和动态地执行类加载操作 -
②. class文件的显式加载与隐式加载的方式是指JVM加载class文件到内存的方式(在日常开发以上两种方式一般会混合使用) 1.显式加载:指的是在代码中通过调用ClassLoader加载class对象,如直接使用Class.forName(name)或this.getClass().getClassLoader().loadClass()加载class对象 2.隐式加载:则是不直接在代码中调用ClassLoader的方法加载class对象,而是通过虚拟机自动加载到内存中,如在加载某个类的class文件时,该类的class文件中引用了另外一个类的对象,此时额外引用的类将通过JVM自动加载到内存中。比如 new User()
类的加载器分类
①. 分类
-
JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader) -
从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范并没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器 -
无论类加载器的类型如何划分,在程序中我们常见的类加载器如下所示: 除了顶层的启动类加载器外,其余的类加载器都应当有自己的"父类"加载器
启动(引导)类加载器 Bootstrap
①. 这个类加载使用C/C++语言实现的,嵌套在JVM内部
②. 它用来加载Java的核心类库(JAVA_HOME/jre/lib/rt.jar、resource.jar或sum.boot.class.path路径下的内容),用于提供JVM自身需要的类(String类就是使用的这个类加载器)
③. 由于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
④. 并不继承自java.lang.ClassLoader,没有父加载器
⑤. 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
扩展类加载器 Extension
①. Java语言编写,sum.music.Launcher$ExtClassLoader实现
②. 派生于ClassLoader类,父类加载器为启动类加载器
③. 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载
应用程序(系统)类加载器 AppClassLoader
①. java语言编写,由sum.misc.Launcher$AppClassLoader实现
②. 派生于ClassLoader类,父类加载器为扩展类加载器
③. 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
④. 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
⑤. 通过ClassLoader的getSystemClassLoader()方法可以获取到该类加载器
用户自定义类加载器
①. 在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们换可以自定义类加载器,来定制类的加载方式(自定义类加载器通常需要继承于 ClassLoader)
②. 体现Java语言强大生命力和巨大魅力的关键因素之一便是,Java 开发者可以自定义类加载器来实现类库的动态加载,加载源可以是本地的JAR包,也可以是网络上的远程资源
③. 自定义 ClassLoader 的子类时候,我们常见的会有两种做法:
-
重写loadClass()方法(不推荐,这个方法会保证类的双亲委派机制) -
重写findClass()方法 -->推荐 这两种方法本质上差不多,毕竟loadClass()也会调用findClass(),但是从逻辑上讲我们最好不要直接修改loadClass()的内部逻辑。建议的做法是只在findClass()里重写自定义类的加载方法,根据参数指定类的名字,返回对应的Class对象的引用。
手写一个简单的自定义加载器
public class UserClassLoader extends ClassLoader {
private String rootDir;
public UserClassLoader(String rootDir) {
this.rootDir = rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = ins.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String classNameToPath(String className) {
return rootDir + "\\" + className.replace('.', '\\') + ".class";
}
public static void main(String[] args) {
String rootDir = "D:\\code\\workspace_teach\\JVMdachang210416\\chapter02_classload\\src\\";
try {
UserClassLoader loader1 = new UserClassLoader(rootDir);
Class clazz1 = loader1.findClass("com.xiaozhi.java3.User");
UserClassLoader loader2 = new UserClassLoader(rootDir);
Class clazz2 = loader2.findClass("com.xiaozhi.java3.User");
System.out.println(clazz1 == clazz2);
System.out.println(clazz1.getClassLoader());
System.out.println(clazz2.getClassLoader());
Class clazz3 = ClassLoader.getSystemClassLoader().loadClass("com.xiaozhi.java3.User");
System.out.println(clazz3.getClassLoader());
System.out.println(clazz1.getClassLoader().getParent());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
|