java类加载器ClassLoader详解
获得ClassLoader的途径
1. 获得当前类的ClassLoader
clazz.getClassLoader()
2. 获得当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader();
3. 获得系统的ClassLoader
ClassLoader.getSystemClassLoader()
4. 获得调用者的ClassLoader
DriverManager.getCallerClassLoader
ClassLoader源码解析
(推荐学习:Java视频教程)
概述
类加载器是用于加载类的对象,ClassLoader是一个抽象类。如果我们给定了一个类的二进制名称,类加载器应尝试去定位或生成构成定义类的数据。一种典型的策略是将给定的二进制名称转换为文件名,然后去文件系统中读取这个文件名所对应的class文件。
每个Class对象都会包含一个定义它的ClassLoader的一个引用。
数组类的Class对象,不是由类加载器去创建的,而是在Java运行期JVM根据需要自动创建的。对于数组类的类加载器来说,是通过Class.getClassLoader()返回的,与数组当中元素类型的类加载器是一样的;如果数组当中的元素类型是一个原生类型,数组类是没有类加载器的【代码一】。
应用实现了ClassLoader的子类是为了扩展JVM动态加载类的方式。
类加载器典型情况下时可以被安全管理器所使用去标识安全域问题。
ClassLoader类使用了委托模型来寻找类和资源,ClassLoader的每一个实例都会有一个与之关联的父ClassLoader,当ClassLoader被要求寻找一个类或者资源的时候,ClassLoader实例在自身尝试寻找类或者资源之前会委托它的父类加载器去完成。虚拟机内建的类加载器,称之为启动类加载器,是没有父加载器的,但是可以作为一个类加载器的父类加载器【双亲委托机制】。
支持并发类加载的类加载器叫做并行类加载器,要求在初始化期间通过ClassLoader.registerAsParallelCapable 方法注册自身,ClassLoader类默认被注册为可以并行,但是如果它的子类也是并行加载的话需要单独去注册子类。
在委托模型不是严格的层次化的环境下,类加载器需要并行,否则类加载会导致死锁,因为加载器的锁在类加载过程中是一直被持有的。
通常情况下,Java虚拟机以平台相关的形式从本地的文件系统中加载类,比如在UNIX系统,虚拟机从CLASSPATH环境所定义的目录加载类。
然而,有些类并不是来自于文件;它们是从其它来源得到的,比如网络,或者是由应用本身构建【动态代理】。定义类(defineClass )方法会将字节数组转换为Class的实例,这个新定义类的实例可以由Class.newInstance创建。由类加载器创建的对象的方法和构造方法可能引用其它的类,为了确定被引用的类,Java虚拟机会调用最初创建类的类加载器的loadClass方法。
二进制名称:以字符串参数的形式向CalssLoader提供的任意一个类名,必须是一个二进制的名称,包含以下四种情况
- "java.lang.String" 正常类
- "javax.swing.JSpinner$DefaultEditor" 内部类
- "java.security.KeyStore\(Builder\)FileBuilder$1" KeyStore的内部类Builder的内部类FileBuilder的第一个匿名内部类
- "java.net.URLClassLoader$3$1" URLClassLoader类的第三个匿名内部类的第一个匿名内部类
代码一:
public class Test12 { public static void main(String[] args) { String[] strings = new String[6]; System.out.println(strings.getClass().getClassLoader()); // 运行结果:null Test12[] test12s = new Test12[1]; System.out.println(test12s.getClass().getClassLoader()); // 运行结果:sun.misc.Launcher$AppClassLoader@18b4aac2 int[] ints = new int[2]; System.out.println(ints.getClass().getClassLoader()); // 运行结果:null } }
loadClass方法
loadClass的源码如下, loadClass方法加载拥有指定的二进制名称的Class,默认按照如下顺序寻找类:
a)调用findLoadedClass(String)检查这个类是否被加载
b)调用父类加载器的loadClass方法,如果父类加载器为null,就会调用启动类加载器
c)调用findClass(String)方法寻找
使用上述步骤如果类被找到且resolve为true,就会去调用resolveClass(Class)方法
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) { 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 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; } }
findClass方法
findClass的源码如下,findClass寻找拥有指定二进制名称的类,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass方法调用,默认返回ClassNotFoundException异常。
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
defineClass方法
defineClass的源码如下,defineClass方法将一个字节数组转换为Class的实例。
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError { protectionDomain = preDefineClass(name, protectionDomain); String source = defineClassSourceLocation(protectionDomain); Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); postDefineClass(c, protectionDomain); return c; }
自定义类加载器
/** * 继承了ClassLoader,这是一个自定义的类加载器 * @author 夜的那种黑丶 */ public class ClassLoaderTest extends ClassLoader { public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); Class<?> clazz = loader.loadClass("classloader.Test01"); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); } //------------------------------以上为测试代码--------------------------------- /** * 类加载器名称,标识作用 */ private String classLoaderName; /** * 从磁盘读物字节码文件的扩展名 */ private String fileExtension = ".class"; /** * 创建一个类加载器对象,将系统类加载器当做该类加载器的父加载器 * @param classLoaderName 类加载器名称 */ private ClassLoaderTest(String classLoaderName) { // 将系统类加载器当做该类加载器的父加载器 super(); this.classLoaderName = classLoaderName; } /** * 创建一个类加载器对象,显示指定该类加载器的父加载器 * 前提是需要有一个类加载器作为父加载器 * @param parent 父加载器 * @param classLoaderName 类加载器名称 */ private ClassLoaderTest(ClassLoader parent, String classLoaderName) { // 显示指定该类加载器的父加载器 super(parent); this.classLoaderName = classLoaderName; } /** * 寻找拥有指定二进制名称的类,重写ClassLoader类的同名方法,需要自定义加载器遵循双亲委托机制 * 该方法会在检查完父类加载器之后被loadClass方法调用 * 默认返回ClassNotFoundException异常 * @param className 类名 * @return Class的实例 * @throws ClassNotFoundException 如果类不能被找到,抛出此异常 */ @Override protected Class<?> findClass(String className) throws ClassNotFoundException { byte[] data = this.loadClassData(className); /* * 通过defineClass方法将字节数组转换为Class * defineClass:将一个字节数组转换为Class的实例,在使用这个Class之前必须要被解析 */ return this.defineClass(className, data, 0 , data.length); } /** * io操作,根据类名找到对应文件,返回class文件的二进制信息 * @param className 类名 * @return class文件的二进制信息 * @throws ClassNotFoundException 如果类不能被找到,抛出此异常 */ private byte[] loadClassData(String className) throws ClassNotFoundException { InputStream inputStream = null; byte[] data; ByteArrayOutputStream byteArrayOutputStream = null; try { this.classLoaderName = this.classLoaderName.replace(".", "/"); inputStream = new FileInputStream(new File(className + this.fileExtension)); byteArrayOutputStream = new ByteArrayOutputStream(); int ch; while (-1 != (ch = inputStream.read())) { byteArrayOutputStream.write(ch); } data = byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new ClassNotFoundException(); } finally { try { if (inputStream != null) { inputStream.close(); } if (byteArrayOutputStream != null) { byteArrayOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return data; } }
以上是一段自定义类加载器的代码,我们执行这段代码
classloader.Test01@7f31245a sun.misc.Launcher$AppClassLoader@18b4aac2
可以看见,这段代码中进行类加载的类加载器还是系统类加载器(AppClassLoader)。这是因为jvm的双亲委托机制造成的,private ClassLoaderTest(String classLoaderName)
将系统类加载器当做我们自定义类加载器的父加载器,jvm的双亲委托机制使自定义类加载器委托系统类加载器完成加载。
改造以下代码,添加一个path属性用来指定类加载位置:
public class ClassLoaderTest extends ClassLoader { public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); } //------------------------------以上为测试代码--------------------------------- /** * 从指定路径加载 */ private String path; ...... /** * io操作,根据类名找到对应文件,返回class文件的二进制信息 * @param className 类名 * @return class文件的二进制信息 * @throws ClassNotFoundException 如果类不能被找到,抛出此异常 */ private byte[] loadClassData(String className) throws ClassNotFoundException { InputStream inputStream = null; byte[] data; ByteArrayOutputStream byteArrayOutputStream = null; className = className.replace(".", "/"); try { this.classLoaderName = this.classLoaderName.replace(".", "/"); inputStream = new FileInputStream(new File(this.path + className + this.fileExtension)); byteArrayOutputStream = new ByteArrayOutputStream(); int ch; while (-1 != (ch = inputStream.read())) { byteArrayOutputStream.write(ch); } data = byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new ClassNotFoundException(); } finally { try { if (inputStream != null) { inputStream.close(); } if (byteArrayOutputStream != null) { byteArrayOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return data; } public void setPath(String path) { this.path = path; } }
运行一下
class:class classloader.Test01 classloader.Test01@7f31245a sun.misc.Launcher$AppClassLoader@18b4aac2
修改一下测试代码,并删除工程下的Test01.class文件
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/桌面/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); }
运行一下
class:class classloader.Test01 classloader.Test01@135fbaa4 classloader.ClassLoaderTest@7f31245a
分析
改造后的两块代码,第一块代码中加载类的是系统类加载器AppClassLoader,第二块代码中加载类的是自定义类加载器ClassLoaderTest。是因为ClassLoaderTest会委托他的父加载器AppClassLoader加载class,第一块代码的path直接是工程下,AppClassLoader可以加载到,而第二块代码的path在桌面目录下,所以AppClassLoader无法加载到,然后ClassLoaderTest自身尝试加载并成功加载到。如果第二块代码工程目录下的Test01.class文件没有被删除,那么依然是AppClassLoader加载。
再来测试一块代码
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance(); System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest("loader"); loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/"); Class<?> clazz2 = loader2.loadClass("classloader.Test01"); System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance(); System.out.println(object2.getClass().getClassLoader()); }
结果显而易见,类由系统类加载器加载,并且clazz和clazz2是相同的。
class:2133927002 sun.misc.Launcher$AppClassLoader@18b4aac2 class:2133927002 sun.misc.Launcher$AppClassLoader@18b4aac2
再改造一下
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/桌面/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance(); System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest("loader2"); loader2.setPath("/home/fanxuan/桌面/"); Class<?> clazz2 = loader2.loadClass("classloader.Test01"); System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance(); System.out.println(object2.getClass().getClassLoader()); }
运行结果
class:325040804 classloader.ClassLoaderTest@7f31245a class:621009875 classloader.ClassLoaderTest@45ee12a7
ClassLoaderTest是显而易见,但是clazz和clazz2是不同的,这是因为类加载器的命名空间的原因。
我们可以通过设置父类加载器来让loader和loader2处于同一命名空间
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/桌面/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance(); System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2"); loader2.setPath("/home/fanxuan/桌面/"); Class<?> clazz2 = loader2.loadClass("classloader.Test01"); System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance(); System.out.println(object2.getClass().getClassLoader()); }
运行结果
class:325040804 classloader.ClassLoaderTest@7f31245a class:325040804 classloader.ClassLoaderTest@7f31245a
扩展:命名空间
1. 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成
2. 在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
3. 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类
php中文网,大量的免费Java入门教程,欢迎在线学习!
Atas ialah kandungan terperinci java类加载器ClassLoader详解. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

Video Face Swap
Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas





Cara menyelesaikan konflik pemuat kelas dalam pembangunan Java Pengenalan: Dalam pembangunan Java, konflik pemuat kelas adalah masalah biasa. Apabila pemuat kelas yang berbeza digunakan untuk memuatkan kelas atau fail sumber yang sama, konflik akan berlaku, menyebabkan program gagal berjalan dengan betul. Artikel ini menerangkan maksud konflik pemuat kelas dan cara menyelesaikannya. 1. Apakah konflik pemuat kelas? Mekanisme pemuatan kelas di Java menggunakan model delegasi induk Setiap pemuat kelas mempunyai pemuat kelas induk, dan pemuat kelas induk terakhir ialah pemuat kelas permulaan. apabila diperlukan

Semasa pembangunan di Java, kami sering menghadapi ralat ClassLoader, yang disebabkan oleh pemuat kelas tidak mencari atau memuatkan kelas yang betul. Ralat seperti ini akan menyebabkan program gagal dilaksanakan seperti biasa dan membawa kesulitan kepada pembangunan. Oleh itu, kita perlu memahami punca ralat ClassLoader dan penyelesaiannya untuk mengoptimumkan program Java kami dengan lebih baik. 1. Punca ralat ClassLoader: Kelas dimuatkan melalui ClassLoader dalam Java, ClassLoad

Latar Belakang Baru-baru ini, kod perniagaan utama telah disulitkan untuk rangka kerja syarikat untuk mengelakkan kod kejuruteraan daripada mudah dipulihkan melalui alat penyahkompilasi seperti jd-gui Konfigurasi dan penggunaan skim pengeliruan yang berkaitan adalah agak rumit dan terdapat banyak masalah untuk projek springboot, jadi fail kelas disulitkan dan kemudiannya Peloder kelas tersuai dinyahsulit dan dimuatkan Penyelesaian ini tidak benar-benar selamat Ia hanya meningkatkan kesukaran penyahkompilasian Ia menghalang lelaki tetapi bukan penjahat dalam rajah di bawah. Pemalam maven menyulitkan kompilasi menggunakan pemalam maven tersuai Fail kelas yang ditentukan disulitkan, dan fail kelas yang disulitkan disalin ke laluan yang ditentukan.

Prinsip dan kaedah pemuatan kelas menggunakan fungsi ClassLoader dalam Java sentiasa menjadi salah satu fokus pembangun Java. Fungsi ClassLoader ialah sebahagian daripada pustaka kelas Java Fungsi utamanya ialah memuatkan fail kelas Java ke dalam Mesin Maya Java (JVM) supaya atur cara boleh berjalan dengan normal. Fungsi ClassLoader ialah teras pemuatan kelas Java Dalam persekitaran masa jalan Java, ia bertanggungjawab untuk mencari dan memuatkan kod bait kelas Java, jadi fahami dan kuasainya.

Fungsi ClassLoader dalam Java boleh memuatkan kelas secara dinamik pada masa jalan, yang sangat berguna dalam beberapa aplikasi yang memerlukan penggunaan fleksibel dan pengubahsuaian kod. Melalui fungsi ClassLoader, mekanisme pemalam boleh dilaksanakan untuk meningkatkan kebolehskalaan dan fleksibiliti sistem. Artikel ini akan memperkenalkan cara menggunakan fungsi ClassLoader untuk pemuatan kelas dinamik. 1. Peranan ClassLoader Apabila memulakan Java Virtual Machine (JVM), tiga ClassLoader akan dicipta:

Gambaran keseluruhan pemuatan automatik PHP Pemuatan automatik PHP bermakna apabila menggunakan kelas, PHP akan memuatkan fail definisi kelas secara automatik. Ini biasanya dicapai melalui pemuat kelas. Pemuat kelas ialah program yang bertanggungjawab untuk memuatkan fail definisi kelas Ia boleh terbina dalam atau disesuaikan. Jenis pemuat kelas Terdapat dua jenis pemuat kelas terbina dalam PHP: Pemuat kelas Zend: Ini ialah pemuat kelas lalai PHP, yang memuatkan fail definisi kelas yang terletak dalam perpustakaan terbina dalam PHP. Pemuat kelas PSR-4: PSR-4 ialah standard pemuatan automatik yang mentakrifkan satu set peraturan untuk memuatkan fail definisi kelas. Pemuat kelas PSR-4 memuatkan fail definisi kelas mengikut piawaian PSR-4. Selain itu, pemuat kelas boleh disesuaikan. Pemuat kelas tersuai boleh disesuaikan mengikut mereka sendiri

Autoloading ialah teknik yang memuatkan kelas PHP secara automatik tanpa perlu memasukkan setiap fail secara manual. Ia memudahkan pembangunan dan penyelenggaraan aplikasi, meningkatkan prestasi dan kebolehselenggaraan. Artikel ini akan meneroka teknologi pemuatan automatik yang biasa digunakan dalam PHP. SPLAutoloaderSPL (Perpustakaan PHP Standard) mengandungi mekanisme pemuatan auto terbina dalam yang dipanggil fungsi spl_autoload_reGISter(). Fungsi ini membolehkan anda mendaftarkan fungsi pemuat yang akan dipanggil apabila percubaan dibuat untuk memuatkan kelas yang tidak wujud. Contoh berikut menunjukkan cara menggunakan SPLAutoloader: spl_autoload_register(function($class){

Peranan pemuat kelas: Memuatkan: Baca fail kelas daripada sumber yang ditentukan. Pengesahan: Sahkan bahawa fail kelas mematuhi spesifikasi. Penyediaan: memperuntukkan memori dan memulakan pembolehubah statik. Menghuraikan: Menghuraikan rujukan simbol. Permulaan: Kaedah panggilan, laksanakan blok permulaan statik dan peruntukkan objek kelas.
