类加载
在Java代码中,类型(不表示对象,可以理解为class)的加载、连接与初始化过程都是在程序运行期间完成的(类型大多数都是存在的,也有在运行期动态生成的,例如动态代理)
提供了更大的灵活性
类型的加载最常见的情况:将已经存在的类的class文件从磁盘加载到内存里。
连接:将类与类之间的关系确定好,对于字节码之间的处理如验证,校验
初始化:对于静态变量赋值
类加载器
加载类的工具
Java虚拟机与程序的生命周期
在如下几种情况下,Java虚拟机将结束生命周期
执行了System.exit()方法
程序正常执行结束
程序在执行过程中遇到了异常或者错误而异常终止
由于操作系统出现错误而导致Java虚拟机进程终止
转存失败重新上传取消
助记符
getstatic
putstatic
invokestatic
转存失败重新上传取消
转存失败重新上传取消
MyTest1
/** * 对于静态字段来说,只有直接定义了该字段的类才会被初始化 * 当一个类初始化时,要求其父类全部已经初始化完毕了 */ public class MyTest1 { public static void main(String[] args) { System.out.println(MyChilde1.str); } } class MyParent1{ public static String str ="hello world"; static{ System.out.println("MyParent1 static block"); } } class MyChilde1 extends MyParent1{ public static String str2 = "Welcome"; static{ System.out.println("MyChild static block"); } }
MyParent1 static block hello world
尽管是子类MyChild1调用了str但是确实父类定义的静态字段,所以只有MyParent1被初始化
public class MyTest1 { public static void main(String[] args) { System.out.println(MyChilde1.str2); } }
MyParent1 static block MyChild static block Welcome
初始化一个类的子类也属于主动使用,所以MyParent1先被初始化
MyTest2
-XX:+TraceClassLoading,用于追踪类的加载信息并打印出来
javap -c com.tang.jvm.classLoader.MyTest2 反编译
转存失败重新上传取消
转存失败重新上传取消
/** * 常量在编译阶段,常量就会被存入到调用常量的那个方法,所在的类的常量池(MyTest2)当中 * 本质上,调用类(MyTest2)并没有直接引用到定义常量的类,因此不会被触发 * 定义常量的类的初始化(所以MyParent2 static block不输出) * 注意:这里指的是将常量存放在MyTest2的常量池中,之后MyTest2与MyParent2就没有任何关系了 * 甚至我们可以将MyParent2的class文件删除 */ /** * 助记符 * ldc 表示将int,float或者是string类型的常量池中推送到栈顶 * bipush表示将单字节(-128~127)的常量值推送至栈顶 * sipush表示将短整型(-327689~32767)的常量值推送至栈顶 t_1表示将int类型1推送至栈顶 (iconst_m1~iconst_5) *iconst_m1 m为minus负数 */ public class MyTest2 { public static void main(String[] args) { System.out.println(MyParent2.m); } } class MyParent2{ public static final String str ="hello world"; public static final short s =7; public static final int i =128; public static final int m =6; static{ System.out.println("MyParent2 static block"); } }
MyTest3
/** * 当一个常量的值并非编译期间就可以确定的,那么其值就不会被放到调用类的常量池中, * 这是在程序运行时,会导致主动使用这个常量所在的类,显然会导致这个类被初始化。 */ public class MyTest3 { public static void main(String[] args) { System.out.println(MyParent3.str); } } class MyParent3{ public static final String str= UUID.randomUUID().toString(); static{ System.out.println("MyParent3 static code"); } }
MyParent3 static code 69d516f2-0b78-4c3a-98ac-6a187ddb7e74
MyTest4
/** * 对于数组实例来说,其数据类型是由JVM在运行期动态生成的,表示为[Lcom.tang.jvm.classLoader.MyParent4 * 这种形式。动态生成的类型,其父类型就是Object * * 对于数组来说,JavaDoc经常将构成数组元素为Component,实际上就是将数组降低一个维度后的类型 * * 助记符 * anewarray:表示创建一个引用类型(类,接口,数组)的数组,并将其引用值压入栈顶 * newarray:表示创建一个指定的原始类型(int、float、char等)的数组,并将其引用值压入栈顶 * */ public class MyTest4 { public static void main(String[] args) { // MyParent4 myParent4 = new MyParent4(); MyParent4[] myParent4s = new MyParent4[1]; System.out.println(myParent4s.getClass()); MyParent4[][] myParent4s1 = new MyParent4[1][1]; System.out.println(myParent4s1.getClass()); System.out.println(myParent4s.getClass().getSuperclass()); System.out.println(myParent4s1.getClass().getSuperclass()); System.out.println("======"); int[] ints=new int[1]; System.out.println(ints.getClass()); System.out.println(ints.getClass().getSuperclass()); char[] chars = new char[1]; System.out.println(chars.getClass()); boolean[] booleans = new boolean[1]; System.out.println(booleans.getClass()); short[] shorts = new short[1]; System.out.println(shorts.getClass()); byte[] bytes = new byte[1]; System.out.println(bytes.getClass()); } } class MyParent4{ static { System.out.println("MyParent4 static block"); } }
class [Lcom.tang.jvm.classLoader.MyParent4; class [[Lcom.tang.jvm.classLoader.MyParent4; class java.lang.Object
class java.lang.Object
class [I class java.lang.Object class [C class [Z class [S class [B
MyTest5
/** * 当一个接口在初始化时,并不要求其父接口都完成了初始化 * 只有在真正使用父接口的时候,(如引用接口中所定义的常量时),才会初始化 */ public class MyTest5 { public static void main(String[] args) { //System.out.println(MyChild5.b);//在初始化一个类时,并不会初始化他所实现的接口 System.out.println(MyParent5_1.thread1);//初始化一个接口时,并不会初始化他的父接口 } } interface MyParent5{ public static Thread thread=new Thread(){ { System.out.println("MyParent5 invoke"); } }; } /** * 在初始化一个类时,并不会初始化他所实现的接口 */ class MyChild5 implements MyParent5{ public static int b=3; } /** *初始化一个接口时,并不会初始化他的父接口 */ interface MyGrandPa5_1{ public static Thread thread1=new Thread(){ { System.out.println("MyGrandPa5_1 invoke"); } }; } interface MyParent5_1 extends MyGrandPa5_1{ public static Thread thread=new Thread(){ { System.out.println("MyParent5_1 invoke"); } }; }
[Loaded com.tang.jvm.classLoader.MyParent5 from file:/E:/Code/jdk8/out/production/classes/] [Loaded com.tang.jvm.classLoader.MyChild5 from file:/E:/Code/jdk8/out/production/classes/] [Loaded com.tang.jvm.classLoader.MyParent5$1 from file:/E:/Code/jdk8/out/production/classes/]
3
=======
[Loaded com.tang.jvm.classLoader.MyParent5_1 from file:/E:/Code/jdk8/out/production/classes/] [Loaded java.net.Inet6Address1 from file:/E:/Code/jdk8/out/production/classes/]
MyGrandPa5_1 invoke
$1意思是匿名内部类
MyTest6
public class MyTest6 { public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); System.out.println(Singleton.count1); System.out.println(Singleton.count2); } } class Singleton { public static int count1=1; private static Singleton singleton=new Singleton(); private Singleton(){ count1++; count2++;//准备阶段的重要意义 System.out.println(count1); System.out.println(count2); } public static int count2 = 0; public static Singleton getInstance(){ return singleton; } }
2 1 2 0
调用类的静态方法属于主动使用这个类,会初始化类,先准备再初始化,准备阶段,count1=0(默认值)
singleton=null,count2=0(默认值)count1++为1,count2++为1,在初始化count2为0;
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
详情看MyTest1中 MYChild1被加载但是没被实例化
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
类的初始化
初始化阶段就是执行类构造器
()方法的过程, ()并不是程序员在Java代码中直接编写的方法,它是Javac编译器的自动生成物(深入理解Java虚拟机 第三版)
/** *·()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的 语句合并产生的, * 编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问 到定义在静态语句块之前的变量, * 定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问 * */ public class InitTest1 { static { i=0; //Illegal forward reference 非法向前引用 System.out.println(i); } static int i=1; }
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
类加载器的父亲委托机制
转存失败重新上传取消
转存失败重新上传取消
MyTest7
public class MyTest7 { public static void main(String[] args) throws ClassNotFoundException { Class> clazz=Class.forName("java.lang.String"); System.out.println(clazz.getClassLoader()); Class> clazz2=Class.forName("com.tang.jvm.classLoader.C"); System.out.println(clazz2.getClassLoader()); } } class C{ }
null sun.misc.Launcher$AppClassLoader@18b4aac2
MyTest8
class FinalTest{ public static final int x=3; static { System.out.println("FinalTest static block"); } } public class MyTest8 { public static void main(String[] args) { System.out.println(FinalTest.x); } }
反编译
iconst_3
说明直接把3储存到MyTest8的常量池中啦
public static final int x=new Random().nextInt(3);
3: getstatic #3 // Field com/tang/jvm/classLoader/FinalTest.x:I
说明引用了FinalTest里面的x静态成员变量,因为在编译器无法确定x的值,只能在运行期的时候引用静态成员变量x,是对类的主动使用,导致类的初始化,静态代码块会执行,输出FinalTest static block
MyTest12
class CL{ static { System.out.println("Class CL"); } } /** * 反射会初始化类 * 调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化 */ public class MyTest12 { public static void main(String[] args) throws ClassNotFoundException { ClassLoader loader = ClassLoader.getSystemClassLoader(); Class> aClass = loader.loadClass("com.tang.jvm.classLoader.CL"); System.out.println(aClass); System.out.println("=============="); aClass= Class.forName("com.tang.jvm.classLoader.CL"); System.out.println("aClass = " + aClass); } }
MyTest13
public class MyTest13 { public static void main(String[] args) { ClassLoader loader = ClassLoader.getSystemClassLoader(); System.out.println(loader); while(null!=loader){ loader = loader.getParent(); System.out.println(loader); } } }
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@677327b6
null
MyTest14
public class MyTest14 { public static void main(String[] args) throws Exception { ClassLoader classLoader=Thread.currentThread().getContextClassLoader(); String resourceName="com/tang/jvm/classloader/MyTest13.class"; Enumerationurls=classLoader.getResources(resourceName); while (urls.hasMoreElements()){ URL url = urls.nextElement(); System.out.println(url); } } }
file:/E:/Code/jdk8/out/production/classes/com/tang/jvm/classloader/MyTest13.class
MyTest15
public class MyTest15 { public static void main(String[] args) { String[] strings = new String[1]; System.out.println(strings.getClass().getClassLoader()); System.out.println("------------"); MyTest15[] myTest15s=new MyTest15[1]; System.out.println(myTest15s.getClass().getClassLoader()); int[] ints=new int[1]; System.out.println(ints.getClass().getClassLoader()); } }
null
sun.misc.Launcher$AppClassLoader@18b4aac2 null
/** * A class loader is an object that is responsible for loading classes. The * class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to * locate or generate data that constitutes a definition for the class. A * typical strategy is to transform the name into a file name and then read a * "class file" of that name from a file system. * *Every {@link Class Class} object contains a {@link * Class#getClassLoader() reference} to the ClassLoader that defined * it. * *
Class objects for array classes are not created by class * loaders, but are created automatically as required by the Java runtime. * The class loader for an array class, as returned by {@link * Class#getClassLoader()} is the same as the class loader for its element * type; if the element type is a primitive type, then the array class has no * class loader. * *
Applications implement subclasses of ClassLoader in order to * extend the manner in which the Java virtual machine dynamically loads * classes. * *
Class loaders may typically be used by security managers to indicate * security domains. * *
The ClassLoader class uses a delegation model to search for * classes and resources. Each instance of ClassLoader has an * associated parent class loader. When requested to find a class or * resource, a ClassLoader instance will delegate the search for the * class or resource to its parent class loader before attempting to find the * class or resource itself. The virtual machine's built-in class loader, * called the "bootstrap class loader", does not itself have a parent but may * serve as the parent of a ClassLoader instance. * *
Class loaders that support concurrent loading of classes are known as * parallel capable class loaders and are required to register * themselves at their class initialization time by invoking the * {@link * #registerAsParallelCapable ClassLoader.registerAsParallelCapable} * method. Note that the ClassLoader class is registered as parallel * capable by default. However, its subclasses still need to register themselves * if they are parallel capable.
* In environments in which the delegation model is not strictly * hierarchical, class loaders need to be parallel capable, otherwise class * loading can lead to deadlocks because the loader lock is held for the * duration of the class loading process (see {@link #loadClass * loadClass} methods). * *Normally, the Java virtual machine loads classes from the local file * system in a platform-dependent manner. For example, on UNIX systems, the * virtual machine loads classes from the directory defined by the * CLASSPATH environment variable. * *
However, some classes may not originate from a file; they may originate * from other sources, such as the network, or they could be constructed by an * application. The method {@link #defineClass(String, byte[], int, int) * defineClass} converts an array of bytes into an instance of class * Class. Instances of this newly defined class can be created using * {@link Class#newInstance Class.newInstance}. * *
The methods and constructors of objects created by a class loader may * reference other classes. To determine the class(es) referred to, the Java * virtual machine invokes the {@link #loadClass loadClass} method of * the class loader that originally created the class. * *
For example, an application could create a network class loader to * download class files from a server. Sample code might look like: * *
* ** ClassLoader loader = new NetworkClassLoader(host, port); * Object main = loader.loadClass("Main", true).newInstance(); * . . . *The network class loader subclass must define the methods {@link * #findClass findClass} and loadClassData to load a class * from the network. Once it has downloaded the bytes that make up the class, * it should use the method {@link #defineClass defineClass} to * create a class instance. A sample implementation is: * *
* ** class NetworkClassLoader extends ClassLoader { * String host; * int port; * * public Class findClass(String name) { * byte[] b = loadClassData(name); * return defineClass(name, b, 0, b.length); * } * * private byte[] loadClassData(String name) { * // load the class data from the connection * . . . * } * } *Binary names
* *Any class name provided as a {@link String} parameter to methods in * ClassLoader must be a binary name as defined by * The Java™ Language Specification. * *
Examples of valid class names include: *
* * @see #resolveClass(Class) * @since 1.0 */* "java.lang.String" * "javax.swing.JSpinner$DefaultEditor" * "java.security.KeyStore$Builder$FileBuilder$1" * "java.net.URLClassLoader$3$1" *
MyTest16(自己写一个类加载器)
public class MyTest16 extends ClassLoader { private String classLoaderName; private String path; private String fileExtension = ".class"; public MyTest16(String classLoaderName) { super(); this.classLoaderName = classLoaderName; } public void setPath(String path) { this.path = path; } public MyTest16(ClassLoader parent, String classLoaderName) { super(parent); this.classLoaderName = classLoaderName; } @Override protected Class> findClass(String name) throws ClassNotFoundException { System.out.println("finalClass invoke:"+name); System.out.println("classloadername:"+classLoaderName); byte[] b = new byte[0]; try { b = this.loadClassDate(name); } catch (Exception e) { e.printStackTrace(); } return defineClass(name, b, 0, b.length); } private byte[] loadClassDate(String className) throws Exception { InputStream is = null; ByteArrayOutputStream baos = null; byte[] date = null; int ch; try { className=className.replace(".", "\\"); System.out.println(className); is = new FileInputStream(new File(this.path+className + this.fileExtension)); baos = new ByteArrayOutputStream(); while (-1 != (ch = is.read())) { baos.write(ch); } date = baos.toByteArray(); return date; } catch (FileNotFoundException e) { e.printStackTrace(); } finally { try { baos.close(); is.close(); } catch (Exception e) { e.getStackTrace(); } } return date; } @Override public String toString() { return "MyTest16{" + "classLoaderName='" + classLoaderName + '\'' + ", fileExtension='" + fileExtension + '\'' + '}'; } public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException { MyTest16 loader1 = new MyTest16("classLoader1"); loader1.setPath("d:\\360MoveData\\Users\\10136\\Desktop\\"); Class> aClass = loader1.loadClass("com.tang.jvm.classloader.MyTest2"); System.out.println(aClass.hashCode()); System.out.println(aClass); Object object = aClass.newInstance(); System.out.println(object.getClass().getClassLoader()); MyTest16 loader2 = new MyTest16("classLoader2"); loader2.setPath("d:\\360MoveData\\Users\\10136\\Desktop\\"); Class> aClass2 = loader2.loadClass("com.tang.jvm.classloader.MyTest2"); System.out.println(aClass2.hashCode()); System.out.println(aClass2); Object object2 = aClass2.newInstance(); System.out.println(object2.getClass().getClassLoader()); } }
finalClass invoke:com.tang.jvm.classloader.MyTest2 classloadername:classLoader1 com\tang\jvm\classloader\MyTest2 2133927002 class com.tang.jvm.classloader.MyTest2 MyTest16{classLoaderName='classLoader1', fileExtension='.class'} finalClass invoke:com.tang.jvm.classloader.MyTest2 classloadername:classLoader2 com\tang\jvm\classloader\MyTest2 1173230247 class com.tang.jvm.classloader.MyTest2 MyTest16{classLoaderName='classLoader2', fileExtension='.class'}
Class> aClass2 = loader2.loadClass("com.tang.jvm.classloader.MyTest2");
2133927002
2133927002
哈希值相同
转存失败重新上传取消
转存失败重新上传取消
转存失败重新上传取消
MyTest17_1
/** * 关于命名空间的重要说明 * 1.子加载器所加载的类能够访问父加载器所加载的类 * 2.父加载器所加载的类无法访问到子加载器所加载的类 */ public class MyTest17_1 { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { MyTest16 loader1=new MyTest16("classloader1"); loader1.setPath("d:\\360MoveData\\Users\\10136\\Desktop\\"); Class> aClass = loader1.loadClass("com.tang.jvm.classloader.MySample"); System.out.println(aClass.hashCode()); Object object = aClass.newInstance(); } }
public class MyCat { public MyCat(){ System.out.println("MyCat is loaded by:"+this.getClass().getClassLoader()); // System.out.println("from MyCat"+MySample.class); } }
public class MySample { public MySample(){ System.out.println("Mysample is loaded by:"+this.getClass().getClassLoader()); new MyCat(); System.out.println("from MySample:"+MyCat.class); } }
转存失败重新上传取消
转存失败重新上传取消
MyTest21
public class MyPerson { private MyPerson myPerson; public void setPerson(Object object){ this.myPerson=(MyPerson) object; } }
/** * 类加载器的双亲委托模型的好处 * * 1.可以确保Java核心库的类型安全,所有的Java应用都至少引用了java.lang.Object类,也就是说在运行期,java.lang.Object * 这个类会被加载到Java虚拟机中,如果这个加载过程是由Java应用自己的类加载器所完成的,那么很可能就会在JVM中存在多个版本的 * java.lang.Object类,而且这些类之间还是不兼容的,相互不可见的(正是命名空间在发挥着作用) * 借助双亲委托机制,Java核心类库中的类的加载工作都是由启动类加载器统一完成,从而确保了Java应用所使用的都是同一个版本的 * Java核心类库,他们之间是相互兼容的。 * * 2.可以确保Java核心类库所提供的类不会被自定义的类所代替。 * 3.不同的类加载器可以为相同名称(binary name)的类创建额外的命名空间。相同名称的类可以并存在Java虚拟机中,只需要不同的 * 类加载器来加载他们即可。不同类加载器所加载的类之间是不兼容的,这就相当于在Java虚拟机内部创建了一个又一个相互隔离的Java * 类空间,这类技术在很多框架中都得到了实际应用。 * * */ public class MyTest21 { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { MyTest16 loader1 = new MyTest16("loader1"); MyTest16 loader2 = new MyTest16("loader2"); loader1.setPath("d:\\360MoveData\\Users\\10136\\Desktop\\"); loader2.setPath("d:\\360MoveData\\Users\\10136\\Desktop\\"); Class> clazz1 = loader1.loadClass("com.tang.jvm.classloader.MyPerson"); Class> clazz2 = loader2.loadClass("com.tang.jvm.classloader.MyPerson"); System.out.println(clazz1==clazz2); Object object1 = clazz1.newInstance(); Object object2 = clazz2.newInstance(); Method method = clazz1.getMethod("setPerson",Object.class); //因为类加载器loader1和loader2命名空间不同,所以其生成Class对象是不能相互访问的 //通过反射实例化的对象自然也互相访问不到 method.invoke(object1, object2); } }
finalClass invoke:com.tang.jvm.classloader.MyPerson classloadername:loader1 com\tang\jvm\classloader\MyPerson finalClass invoke:com.tang.jvm.classloader.MyPerson classloadername:loader2 com\tang\jvm\classloader\MyPerson false Exception in thread "main" java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.tang.jvm.classloader.MyTest21.main(MyTest21.java:22) Caused by: java.lang.ClassCastException: com.tang.jvm.classloader.MyPerson cannot be cast to com.tang.jvm.classloader.MyPerson at com.tang.jvm.classloader.MyPerson.setPerson(MyPerson.java:6) ... 5 more
MyTest22
jar -cvf MyTest1.jar com\tang\jvm\classloader\MyTest1.class 因为拓展类加载器只加载jar包
java -Djava.ext.dirs=.\ com.tang.jvm.classloader.MyTest22
public class MyTest22 { static{ System.out.println("MyTest22 initializer"); } public static void main(String[] args) { System.out.println(MyTest22.class.getClassLoader()); System.out.println(MyTest1.class.getClassLoader()); } }
MyTest22 initializer
sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$ExtClassLoader@33909752
MyTest23
/** * 在运行期,一个Java类是由该类的完全限定名(binary name)和用于加载该类的定义类加载器(defining loader)所共同决定的 * 如果同样名字的类(即相同完全限定名)的类是由两个不同的加载器所加载,那么这些类就是不同的,即便.class字节码完全一样, * 并且从相同的位置加载亦如此。 */ import sun.misc.Launcher; /** * 在Oracle的HotSpot实现中,系统属性sun.boot.class.path如果修改错了 * (java -Dsun.boot.class.path=.\com.tang.jvm.classloader.MyTest23), * 则会出现运行时错误提示如下、 * Error occurred during initialization of VM * java/lang/NoClassDefFoundError: java/lang/Object */ public class MyTest23 { public static void main(String[] args) { System.out.println(System.getProperty("sun.boot.class.path")); System.out.println(System.getProperty("java.ext.dirs")); System.out.println(System.getProperty("java.class.path")); /** * 内建于JVM中的启动类加载器会加载java.lang.ClassLoader以及其他的Java平台类, * 当JVM启动时,一块特殊的机器码会运行,他会加载扩展类加载器与系统类加载器, * 这块特殊的机器码叫做启动类加载器(BootStrap)。 * * 启动类加载器并不是java类,而其他的加载器则都是Java类, * 启动类加载器是特定于平台的机器指令,它负责开启整个加载过程 * 所有类加载器(除了启动类加载器)都被实现为Java类。不过总归要有一个组件来加载第一个Java类加载器,从而 * 让整个加载过程能够顺利进行下去,加载第一个纯加载器就是启动类加载器的职责 * * 启动类加载器还会负责加载供JRE正常运行所需要的基本组件,这包括java.util与java.lang包中的类等等。 */ System.out.println(ClassLoader.class.getClassLoader()); //扩展类加载器和系统类加载器也是由启动类加载起加载的 System.out.println(Launcher.class.getClassLoader()); System.out.println("======================="); ClassLoader.getSystemClassLoader(); System.out.println(System.getProperty("java.system.class.loader")); System.out.println(MyTest23.class.getClassLoader()); System.out.println(MyTest16.class.getClassLoader()); System.out.println(ClassLoader.getSystemClassLoader()); } }
java -Djava.system.class.loader=com.tang.jvm.classloader.MyTest16 com.tang.jvm.classloader.MyTest23
null
null
com.tang.jvm.classloader.MyTest16 sun.misc.LauncherAppClassLoader@18b4aac2 com.tang.jvm.classloader.MyTest16@6d06d69c
转存失败重新上传取消
MyTest24
/** * 当前类加载器(Current Classloader) * * 每一个类都会使用自己的类加载器(即加载自身的类加载器)来去加载其他类(指的是所依赖的类), * 如果ClassX引用了ClassY,那么ClassX的类加载器就会去加载ClassY(前提是ClassY尚未被加载) * * 线程上下文类加载器(Context Classloader) * * 线程上下文类加载器是从JDK1.2开始引入,类Thread中的getContextClassLoader和类setContextClassLoader(ClassLoader c1) * 分别用来获得和设置类加载器 * * 如果没有通过setContextCl assLoader(ClassLoader c1)进行设置的话,线程将继承父线程的上下文类加载器 * Java应用运行时的初始线程的上下文类加载是系统类加载器。在线程中运行的代码可以通过该类加载器来加载类与资源, * * 线程上下文类加载器的重要性: * * SPI(Service Provider Interface) * * 父ClassLoader可以使用当前线程的Thread.currentThread().getContextClassLoader()所指定的classloader加载的类。 * 这就改变了父ClassLoader不能使用子Classloader或者是其他没有直接父子关系的ClassLoder加载的类的情况,即改变了双亲委托模型 * * 线程上下文类加载器就是当前线程的Current ClassLoader * * 在双亲委托模型下,类加载器是由下到上,即下层的类加载器会委托上层进行加载,但对于SPI来说,有些接口是Java核心库所提供的, * 而Java核心库是由启动类加载器来加载的,而这些接口的实现来自于不同的jar包(厂商提供),Java的启动类加载器是不会加载其他来源的 * jar包,这样传统的双亲委托模型就无法满足SPI的需要,而通过当前给当前线程设置上下文类加载器。,就可以由设置的上下文类加载器来实现 * 对于接口实现类的加载 * */ public class MyTest24 { public static void main(String[] args) { System.out.println(Thread.currentThread().getContextClassLoader()); System.out.println(Thread.class.getClassLoader()); } }
JDNI
JNDI全名为Java Naming and Directory Interface.JNDI主要提供应用程序所需要资源上命名与目录服务。在Java EE环境中,JNDI扮演了一个很重要的角色,它提供了一个接口让用户在不知道资源所在位置的情形下,取得该资源服务。就好比网络磁盘驱动器的功能一样。如果有人事先将另一台机器上的磁盘驱动器接到用户的机器上,用户在使用的时候根本就分辨不出现在的驱动器是存在本端,还是在另一端的机器上,用户只需取得资源来用,根本就不知道资源在什么地方。
API
(Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。
SPI
(Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。
MyTest26
public class MyTest26 { public MyTest26() { } public static void main(String[] args) { ServiceLoaderserviceLoader = ServiceLoader.load(Driver.class); Iterator iterator = serviceLoader.iterator(); while(iterator.hasNext()) { Driver driver = (Driver)iterator.next(); System.out.println("driver:" + driver.getClass() + "loader:" + driver.getClass().getClassLoader()); } System.out.println("当前线程上下文类加载器:" + Thread.currentThread().getContextClassLoader()); System.out.println("serviceLoader的类加载器:" + serviceLoader.getClass().getClassLoader()); } }
ServiceLoaderserviceLoader = ServiceLoader.load(Driver.class);
public staticServiceLoaderload(Classservice) { //这里需要调用当前上下文类加载器,因为MyTest26是AppClassLoader加载的,ServiceLoader是被 //被委托给启动类加载器加载的,所以这里面的代码也应该由启动类加载器加载,但是启动类加载器不能加在位 //于classpath下的mysql驱动,所以采用ContextClassLoader来获取当前线程上下文类加载器来加载驱动 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }