实现由spi内置于jdk核心包,由启动类加载器进行加载,但是实现类却由应用类加载器加载,在程序的运行中通过thread.currentThead.getContextClassloader 改变类的加载器
违背了双亲模型 因为双亲模型中子类由某个类加载,则父类也由它加载
加载:查找并加载类的二进制数据到内存
连接:
1)验证:确保被加载的类符合jvm的规范
2)准备:为类的静态变量分配内存,并将其初始化为默认值(int置为0,Boolean置为false)
3)解析:将类中的符号引用转化为直接引用
初始化:为类的静态变量赋予正确的初始化值
public class Test {
/*
在准备阶段a为默认值0,
在初始化阶段初始化为1
* */
public static int a=1;
}
符号引用:字符串,能根据这个字符串定位到制定数据,比如java/lang/StringBuilder
直接引用:指针
方法区:jvm中定义的一个概念,用于存储类信息,常量池,静态变量等信息。永久代是hotspot中的方法区的实现。
public class Test {
/*
初始化后仍旧为0
* */
public static int a;
}
java程序对类的使用方式分为两种
1)主动使用
2)被动使用
所有的java虚拟机实现必须在每个类或者接口被java程序“首次主动使用”时才初始化它们
1)首次
2)主动使用
虽然不初始化但是可能会加载
类的加载是指将类的.class文件中的二进制数据读入内存中,将其放在运行时数据区的方法区内,然后再内存中创建一个java.lang.Class对象(规范并未说明Class对象放在哪里,hotspot虚拟机将其放在了方法区中)用来封装类在方法区内的数据结构
加载.class文件的方式
1)从本地文件系统中直接加载
2)通过网路下载.class文件
3)从zip,jar等归档文件中加载.class文件
4)从专用数据库中提取.class文件
5)将java源文件动态编译为class文件(动态代理)
public class Test1 {
/*
* System.out.println(MyChild.str1);只会触发父类的初始化,子类不会初始化
* System.out.println(MyChild.str);父类没有初始化会触发父类的初始化
* -XX:+TraceClassLoading
* System.out.println(MyChild.str1);可以触发子类的加载
* -XX:+
public static void main(String[] args) {
// System.out.println(MyChild.str);
System.out.println(MyChild.str1);
}
}
class MyChild extends MyParent{
public static String str = "hello";
static {
System.out.println("MyChild");
}
}
class MyParent{
public static String str1 = "parent";
static {
System.out.println("MyParent");
}
}
public class MyTest2 {
/*
* public static final String str="hello";常量在编译器会被保存在调用这个常量的类的(MyTest2)常量池中,此时可以将
* 本质上,调用并不会调用直接引用的定义常量的类,所以不会触发初始化。
* 之后MyParent2和MyTest2没有任何关系,删除编译后的MyParent2.class文件,程序仍旧可以正常运行
* ldc表示将int,float或者string类型的常量值从常量池中取出推送至栈顶
* bipush表示将单字节(-128~127)内的常量值推送至栈顶
* sipush表示将短整型常量值(-32768~32767)
* iconst_1表示将int类型的1推送至栈顶(iconst_1~iconst_5)
* 这些助记符可以在jdk中找到相应的定义
* */
public static void main(String[] args) {
System.out.println(MyParent2.str);
}
}
class MyParent2 {
public static final String str="hello";
static {
System.out.println("MyParent2");
}
}
javap jdk自带的反编译工具
G:\code\jvm\out\production\jvm\com\jvm\study>javap -c MyTest2.class
Compiled from "MyTest2.java"
public class com.jvm.study.MyTest2 {
public com.jvm.study.MyTest2();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4 // String hello
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
源码如下:
public class MyTest2 {
/*
* public static final String str="hello";常量在编译器会被保存在调用这个常量的类的(MyTest2)常量池中,此时可以将
* 本质上,调用并不会调用直接引用的定义常量的类,所以不会触发初始化。
* 之后MyParent2和MyTest2没有任何关系,删除编译后的MyParent2.class文件,程序仍旧可以正常运行
* */
public static void main(String[] args) {
System.out.println(MyParent2.str);
}
}
public class MyTest3 {
/*
* 当一个常量的值并非在编译期间可以确定的,那么其值就不会被找到并放到常量池中
* 这是在程序运行时,会导致主动使用这个常量所在的类,显然会导致这类的初始化
* */
public static void main(String[] args) {
System.out.println(MyParent3.str);
}
}
class MyParent3{
static final String str= UUID.randomUUID().toString();
static {
System.out.println("MyParent3");
}
}
public class MyTest4 {
/*
对于数组来书,其类型实jvm在运行期间动态生成的,表示为class [Lcom.jvm.study.MyParent4
这种形式,动态生成的类型,其夫类型就是object
对于数组来说,javaDoc经常将构成数组的元素为component,实际上就是将数组降低一个维度后的类型
此时不会触发MyParent4的初始化
助记符:
anewarray:表示创建一个引用类型的(如类,接口,数组)数组,并将其压入栈顶
newarray:表示创建一个原始的(如int,char,short,boolean,byte等)数组,并将其压入栈顶
*/
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());
int[] ints = new int[0];
System.out.println(ints.getClass());
System.out.println(ints.getClass().getSuperclass());
char[] chars = new char[0];
System.out.println(chars.getClass());
System.out.println(chars.getClass().getSuperclass());
short[] shorts = new short[0];
System.out.println(shorts.getClass());
System.out.println(shorts.getClass().getSuperclass());
byte[] bytes = new byte[0];
System.out.println(bytes.getClass());
System.out.println(bytes.getClass().getSuperclass());
}
}
class MyParent4{
static {
System.out.println("MyParent4");
}
}
此时并不会触发数组类对象的加载
[Loaded com.jvm.study.MyTest4 from file:/G:/code/jvm/out/production/jvm/]
[Loaded sun.launcher.LauncherHelper$FXHelper from C:\Program Files\Java\jdk1.8.0_202\jre\lib\rt.jar]
[Loaded java.lang.Class$MethodArray from C:\Program Files\Java\jdk1.8.0_202\jre\lib\rt.jar]
[Loaded java.lang.Void from C:\Program Files\Java\jdk1.8.0_202\jre\lib\rt.jar]
[Loaded com.jvm.study.MyParent4 from file:/G:/code/jvm/out/production/jvm/]
class [Lcom.jvm.study.MyParent4;
class [[Lcom.jvm.study.MyParent4;
class java.lang.Object
class java.lang.Object
class [I
class java.lang.Object
class [C
class java.lang.Object
class [S
class java.lang.Object
class [B
class java.lang.Object
public class MyTest5 implements MyParent5{
/*
* 当一个接口在初始化时,并不要求父类也初始化
* 是由在真正使用父接口时才会初始化
*
* 接口中的变量默认添加final
*
* */
static String str = "hello";
public static void main(String[] args) {
// System.out.println(MyParent5.parent);
// System.out.println(MyTest5.str);
System.out.println(MyChild5.uuid);
// System.out.println(MyChild5.string);
// System.out.println(MyChild5.child);
}
}
interface MyParent5{
public static Thread parent=new Thread(){
{
System.out.println("MyParent5");
}
};
}
interface MyChild5 extends MyParent5{
String string = "hello child";
String uuid = UUID.randomUUID().toString();
public static Thread child=new Thread(){
{
System.out.println("MyChild5");
}
};
}
public class MyTest7 {
/*
* string 为rt包下,有bootstrapclassload进行加载,所以为空
* C在classpath路径下,有系统类加载器(应用加载器)
* */
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(C.class.getClassLoader());
}
}
class C{
}
//输出
null
sun.misc.Launcher$AppClassLoader@18b4aac2
public class MyTest20 {
public static void main(String[] args) throws Exception {
CustomerClassLoader customerClassLoader1 = new CustomerClassLoader("classloader");
CustomerClassLoader customerClassLoader2 = new CustomerClassLoader("classloader");
customerClassLoader1.setPath("C:\\Users\\xingkong\\Desktop\\jvm");
customerClassLoader2.setPath("C:\\Users\\xingkong\\Desktop\\jvm");
Class<?> class1 = customerClassLoader1.loadClass("com.jvm.study.MyPerson");
Class<?> class2 = customerClassLoader2.loadClass("com.jvm.study.MyPerson");
System.out.println(class1 == class2);
Object objec1=class1.newInstance();
Object objec2=class2.newInstance();
Method methods = class1.getMethod("setMyPerson", Object.class);
methods.invoke(objec1, objec2);
}
}
结果输出为:
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.jvm.study.MyTest20.main(MyTest20.java:26)
Caused by: java.lang.ClassCastException: com.jvm.study.MyPerson cannot be cast to com.jvm.study.MyPerson
at com.jvm.study.MyPerson.setMyPerson(MyPerson.java:20)
... 5 more
// SPI
Class.forName("com.mysql.jdbc.Drive");
Connection connection = Driver.getConnection();
Statement statement = connection.getStatement();
当前类加载器(current class loader)
每个类都会使用自己的类加载器(即加载自身的类加载)来气加载其他类(指的是所依赖的类),
线程上下文类加载(context classloader)
线程上下文类加载是从jdk1.2开始引入,类thread中的getContextClassLoader()与
setContextClassloader()分别用来获取和设置上下文类加载器
如果没有通过setContextClassLoader(classLoader c1)运行设置的话,线程将继承其类线程的上下文类加载器,
java应用运行时的和初始线程上下文类加载器是系统类加载器,在线程中运行的代码可以通过该类加载器来加载类与资源
线程上下文类加载的重要性:
SPI(services provider Interface)
父classloader可以使用当前的线程thread.currentThread(),getContextClassLoader()所指定的classLoader
加载的类,这就改变了父classLoader不能使用子classloader或是其他没有直接父子类关系的classloader加载的
类的情况,即改变了双亲委托模型
线程上下文类加载器就是当前线程的current classloader,
在双亲委托模型下,类加载是由下至上的,即下层的类加载器会委托上进行加载,但是对于spi来说,有些接口
是java核心类库所提供的,而java核心类库是由启动类加载器来加载的,而这些就接口的实现却来自与不同的jar包,
(厂商提供),java的启动类加载是不回家在其他来源的jar包,这样传统的双亲委托模型就无法满足spi的需求,
而通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载来实现对于接口类型类的加载。
线程上下文类加载器的一般使用模式(获取-使用-还原)
classLoader c1=Thread.currentThread().getContextClassLoader();
try{
Thread.currentThread().setContextClassLoader(targetTest1);
}finally{
Thread.currentThread().setContextClassLoader(targetTest1);
}
myMethod里面则调用了Thread.currentThread().getContextClassLoader(),
获取当前线程的的上下文类加载器做某些事情,如果一个类由类加载器A加载,那么
这个类的依赖类也是由相同的类加载加载的(如果该依赖之前没有加载过)
contextClassLoader的作用就是为了破坏java的类加载器委托机制
当高层提供了统一的接口让底层去实现,同时又要在高层加载(或实例化)
底层的类时,就必须通过线程上下文类加载器来帮助高层classloader找到
并加载该类。