JVM 第一阶段--虚拟机类加载机制(2020-02)

类的初始化

类的加载、链接和初始化

加载:查找并加载类的二进制数据
链接:

验证:确保加载类的准确性
准备:为类的静态变量分配内存 并将其初始化为默认值
		private static int i = 128; (将i设置为0)
解析:把类中的符号引用转换为直接引用

初始化:把类中的静态变量赋给正确的初始值(一个类只会被初始化一次)

在这里插入图片描述

JAVA程序对类的使用方式

	主动使用:
		所有的jvm实现必须在每个类或接口被java程序“首次主动使用”才初始化他们
	1.创建类的实例
	2.访问某个类或接口的静态变量,或者对该静态变量赋值
	3.调用类的静态方法
	4.反射
	5.初始化一个类的子类 (child parent 初始化child的时候会对parent进行初始化)
	6.被表明为启动类的类@Test @springBootApplication
	7.JDK1.7提供的动态语言支持			

	被动使用:
		除了以上七种情况,其他情况都是类的被动使用,被动使用不会导致类的初始化

类的加载:
在这里插入代码片是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个这个类的Java.lang.Class对象,用来封装类在方法区类的对象。
-XX:+TraceClassLoading 用于追踪类的加载信息并打印出来
-XX:+ 表示开启option选项
-XX:- 表示开启option选项

被final修饰的为常量在编译时就会被放在调用这个常量的方法的常量池中,本质上调用类并没有直接饮用到定义常量的类,因此并不会出发定义常量的类的初始化

public class MyTest2 {
    public static void main(String[] args) {
        System.out.println(MyParent2.str);
    }
}
class MyParent2 {
    public static final String str = "str";
    static {
        System.out.println("MyParent");
    }
}

sout:str

反编译结果

public class com.cy.jvm.classLoader.MyTest2 {
  public com.cy.jvm.classLoader.MyTest2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()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 str
        // 3: sipush        128
       5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

ldc:表示将int,float或是string类型的常量值从常量池中推向栈顶
bipush:表示将单字节(-128-127)常量值推向栈顶
sipush:表示将一个短整形(-32768-32767)常量值推向栈顶
iconst_1:表示将int类型的(1-5)推送到栈顶(1-5) int i = 6.  i->bipush 

编译期间常量和运行期常量及数组创建本质分析

当编译期间不知道str2的值(不是编译器常量)	
则会导致对类的主动使用
常量是被final修饰的
public class MyTest2 {
    public static void main(String[] args) {
        System.out.println(MyParent2.str2);
    }
}
class MyParent2 {
    //这个UUID.randomUUID().toString()编译期间不知道str2的值(不是编译器常量)
    public static final String str2 = UUID.randomUUID().toString();
    static {
        System.out.println("MyParent");
    }
}

sout:   MyParent
		50e9fa56-86d7-4527-aef6-e441b85aec1b
对于数组实例来说,其类型是在JVM在运行期间动态生成的 
表示为class [Lcom.cy.jvm.classLoader.MyParent4; 
这种形式.动态生成的类型,其父类型就是object
public class MyTest3 {
    public static void main(String[] args) {
        MyParent4[] s = new MyParent4[1];
        System.out.println(s.getClass());
    }
}
class MyParent4 {

    static {
        System.out.println("MyParent");
    }
}

sout:class [Lcom.cy.jvm.classLoader.MyParent4;
MyParent4[] s = new MyParent4[1];
System.out.println(s.getClass());
int[] ints = new int[1];
System.out.println(ints.getClass());

sout:class [Lcom.cy.jvm.classLoader.MyParent4;
	 class [I

助记符

anewarray:表示创建一个引用类型的(User)数组,并将其引用值压入栈顶
newarray:表示创建一个基本类型的(int)数组,并将其引用值压入栈顶

接口初始化规则与类加载器准备阶段和初始化阶段的重要分析

接口的成员变量默认是final的 会被放到常量池中
只有当真正使用到父接口中,才会被初始化

public class MyTest4 {
    public static void main(String[] args) {
        System.out.println(MyChild5.b);
    }
}

interface MyParent5 {
    int a = 5;
}

interface MyChild5 extends MyParent5{
    int b = 6;
}
sout:6

变量初始化的顺序是按照对从上到下变量声明的顺序来初始化的

public class MyTest5 {

    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(Singleton.counter1);
        System.out.println(Singleton.counter2);
    }
}

class Singleton {
    public static int counter1;
    private static Singleton singleton = new Singleton();
    private Singleton() {
        counter1++;
        counter2++;
    }
    public static int counter2 = 0;

    public static Singleton getInstance() {
        return singleton;
    }
}
sout:	1
		0
--------------------------------------------------------
public class MyTest5 {

    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(Singleton.counter1);
        System.out.println(Singleton.counter2);
    }
}

class Singleton {
    public static int counter1;
    public static int counter2 = 0;
    private static Singleton singleton = new Singleton();
    private Singleton() {
        counter1++;
        counter2++;
    }

    public static Singleton getInstance() {
        return singleton;
    }
}
sout:1
	 1

例题:

public class MyTest5 {

    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(Singleton.counter1);
        System.out.println(Singleton.counter2);
    }
}

class Singleton {
    public static int counter1=1;

    private static Singleton singleton = new Singleton();

    private Singleton() {
        counter1++;
        counter2++;
        System.out.println(counter1);
        System.out.println(counter2);
    }

    public static int counter2 = 0;

    public static Singleton getInstance() {
        return singleton;
    }

分析:

扫描二维码关注公众号,回复: 9462019 查看本文章
准备阶段:	
	counter1=0,singleton=null,counter2=0
main主动使用初始化阶段:
	counter1=1,singleton是新的实例导致私有构造方法启动 counter1=2 counter2=1
然后 public static int counter2 = 0; counter2再次被赋值 导致counter2=0,
所以输出 2 1 2 0 

在这里插入图片描述

类加载器

在这里插入图片描述
在这里插入图片描述
调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

public class MyTest6 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> clazz = Class.forName("java.lang.String");
        System.out.println(clazz.getClassLoader());
        Class<?> clazz_c = Class.forName("com.cy.jvm.classLoader.C");
        System.out.println(clazz_c.getClassLoader());

    }
}
class C {

}

sout:	null
		sun.misc.Launcher$AppClassLoader@18b4aac2

反射----主动使用初始化类

public class MyTest9 {
    public static void main(String[] args) throws ClassNotFoundException {
        //ClassLoader来加载不是对类的主动使用
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        Class<?> loadClass = systemClassLoader.loadClass("com.cy.jvm.classLoader.Cl");
        System.out.println(loadClass);
        System.out.println("----------------");
        //反射 对类的主动使用
        Class<?> name = Class.forName("com.cy.jvm.classLoader.Cl");
        System.out.println(name);
    }
}

class Cl {
    static {
        System.out.println("Class CL");
    }
}

sout:	class com.cy.jvm.classLoader.Cl
		----------------
		Class CL
		class com.cy.jvm.classLoader.Cl

双亲委派机制

检验类加载器

@Slf4j
public class MyTest8 {
    public static void main(String[] args) {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();

        log.info(String.valueOf(classLoader));
        log.info("-----------------------------");
        while (null != classLoader) {
            classLoader = classLoader.getParent();
            log.info("{}",classLoader);
        }
    }
}

sout: 	sun.misc.Launcher$AppClassLoader@18b4aac2
		-----------------------------
		sun.misc.Launcher$ExtClassLoader@238e0d81
		null

获取ClassLoader的方式在这里插入图片描述

ClassLoader实例分析

public class MyTest11 {

    public static void main(String[] args) throws IOException {
        String[] strings = new String[3];
        System.out.println(strings.getClass().getClassLoader());

        MyTest[] myTests = new MyTest[3];
        System.out.println(myTests.getClass().getClassLoader());

        int[] ints = new int[3];
        System.out.println(ints.getClass().getClassLoader());
    }
}

null(在java runtime 时根加载器加载)
sun.misc.Launcher$AppClassLoader@18b4aac2
null(原生类型 没有类加载器)

在这里插入图片描述

//扩展类加载器和应用加载器都是由根加载器加载
System.out.println(Launcher.class.getClassLoader());  null 
//JVM根加载器会加载Java.lang下的内容 java.lang.String/ClassLoader
System.out.println(ClassLoader.class.getClassLoader()); null
System.out.println(MyTest.class.getClassLoader());
sun.misc.Launcher$AppClassLoader@18b4aac2

在这里插入图片描述

线程上下文类加载器分析与实现

				    			当前类加载器
每个类都会使用自己的类加载器来加载其他类(所依赖的类)
如果class x 引用 class y 那么class x的类加载器就会去加载class y(前提是class y尚未被加载)

	
					线程上下文类加载器(JDK1.2引入)
类Thread中的getContextClassLoader()与setContextClassLoader(ClassLoader cl)分别用来获取和设置上下文类加载器.
如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话 线程将继承其父线程的上下文类加载器 Java应用运行时的初始线程的上下文类加载器是系统加载器 在线程中运行的代码可以通过该类加载器来加载类与资源
					线程上下文类加载器的重要性(SPI 服务提供接口)
父ClassLoader可以使用当前线程Thread.currentThread().getContextLoader()所指定的classloader加载的类 这就改变了父ClassLoader不能使用子ClassLoader或是其他没有直接父子关系的ClassLoader加载的类的状况,即改变了双亲委派模型
线程上下文类加载器就是当前线程的Current ClassLoader 
在双亲委派模型下,类加载是由下至上的,即下层的类加载器会委托上层进行加载 但是对于SPI来说 又些解耦是Java核心库所提供的,而Java核心库是由根加载器来加载的 二这些接口的实现缺来自于不同的jar包 Java的启动类加载器是不会加载其他来源的jar包,这样传统双亲委派模型就无法满足SPI的要求 而通过给当前线程设置上下文类加载器 就可以由设置的上下文类加载器来实现对于接口实现类的加载
 public static void main(String[] args) {
        System.out.println(Thread.currentThread().getContextClassLoader());
        System.out.println(Thread.class.getClassLoader());
    }
  sout:	sun.misc.Launcher$AppClassLoader@18b4aac2
		null

在这里插入图片描述

ServiceLoader在SPI的重要分析

public class MyTest16 {

    public static void main(String[] args) {
        ServiceLoader<Driver> load = ServiceLoader.load(Driver.class);
        Iterator<Driver> iterator = load.iterator();
        while (iterator.hasNext()){
            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.class.getClassLoader());
    }
}
driver:class com.mysql.cj.jdbc.Driver//loader:sun.misc.Launcher$AppClassLoader@18b4aac2
当前线程上下文加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader的类加载器:null
Driver(I).class------->com.mysql.cj.jdbc.Driver(C)如何找到
 public static void main(String[] args) {
        ServiceLoader<Driver> load = ServiceLoader.load(Driver.class);
        Iterator<Driver> iterator = load.iterator();
        while (iterator.hasNext()){
            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.class.getClassLoader());
    }
driver:|class com.mysql.cj.jdbc.Driver//loader:sun.misc.Launcher$AppClassLoader@18b4aac2
当前线程上下文加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader的类加载器:null
   public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
   private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
运行main方式时 是通过系统加载器进行加载逐渐委托到启动类加载器 启动类加载器加载到ServiceLoader load方法也是由启动类加载器来进行加载 根据双亲委派 启动类加载器就加载不了mysql的驱动 就采用了线程上下文的加载器(App ClassLoader) 导致可以加载 ServiceLoader的构造方法 如果传入加载器则为传入加载器 否则为系统加载器
    public static void main(String[] args) {
        // Ext类加载器
        Thread.currentThread().setContextClassLoader(MyTest.class.getClassLoader().getParent());
        
        ServiceLoader<Driver> load = ServiceLoader.load(Driver.class);
        Iterator<Driver> iterator = load.iterator();
        while (iterator.hasNext()){
            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.class.getClassLoader());
    }
当前线程上下文加载器:sun.misc.Launcher$ExtClassLoader@279f2327
ServiceLoader的类加载器:null
如果将当前线程中的App类加载 这里换成Ext加载器 循环就便没有东西 因为Ext类加载器加载不了mysql的驱动 所以引入App类加载器来进行加载

通过JDBC驱动加载深刻理解线程上下文类加载器

public static void main(String[] args) throws Exception {
        System.out.println(System.getProperty("jdbc.drivers"));
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("", "", "");
    }
driver:class com.mysql.cj.jdbc.Driver//loader:sun.misc.Launcher$AppClassLoader@18b4aac2
当前线程上下文加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader的类加载器:null
 	Class.forName("com.mysql.cj.jdbc.Driver");
    @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }
    
 	@CallerSensitive
    public static native Class<?> getCallerClass(); //native方法

	static ClassLoader getClassLoader(Class<?> caller) {
        // This can be null if the VM is requesting it
        if (caller == null) {
            return null;
        }
        // Circumvent security check since this is package-private
        return caller.getClassLoader0();
    }

	 private final ClassLoader classLoader;

	 ClassLoader getClassLoader0() { return classLoader; }
通过反射获得当前类的Class对象, getClassLoader()加载当前类 caller此时不为空 所以
caller.getClassLoader0()方法返回一个classLoader classLoader==null 为根加载器
 public Driver() throws SQLException {
    }
    /** 上一步骤类加载器加载类时,执行了初始化方法,就是执行这里的static中的代码 */
    static {
        try {
            /** 将当前mysql的Driver注册到DriverManager中 */
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException {
    if(driver != null) {
        // 将mysql的driver封装成DriverInfo对象,并传入registeredDrivers链表中。
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        throw new NullPointerException();
    }
}
  	Connection connection = DriverManager.getConnection("", "", "");
这里是通过调用DriverManager的getConnection的静态方法来获取链接 与此同时也对DriverManager类进行了加载 
  static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
     
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }
这个地方:静态代码块也被初始化 然后loadInitialDrivers()加载初始化Driver
,这一段 drivers = AccessController.doPrivileged(new PrivilegedAction<String>()
....匿名内部类是创建driver的另外一种方式,但是System.getProperty("jdbc.drivers")==null,所以到下面通过字符串分割来来获取driver,再通过getSystemClassLoader()系统加载器来进行加载Driver
 @CallerSensitive
    public static Connection getConnection(String url,
        java.util.Properties info) throws SQLException {
        return (getConnection(url, info, Reflection.getCallerClass()));
    }
接着执行 driver.connect( ) 方法,之前我们看过 Driver 的源码
Driver extends NonRegisteringDriver implements java.sql.Driver
 所以 driver.connect 的方法,不是在 Driver 中就是在 NonRegisteringDriver 中。
 结果我们在 NonRegisteringDriver 中如愿以偿的找到了connect方法
public Connection connect(String url, Properties info) throws SQLException {
    ...
    // 重要代码在这里,获取具体的连接实例
    com.mysql.jdbc.Connection newConn = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url);
    return newConn;
    ...
}
所以在此获取连接

总结:

通过类加载器加载Driver并初始化,将Driver添加到DriverManager的registeredDrivers中;
通过registeredDrivers获取到Driver;
调用Driver的connect方法,获取Mysql中的MySQLConnection;

在这里插入图片描述

线程上下文类加载器:打破双亲委派机制

发布了19 篇原创文章 · 获赞 6 · 访问量 1980

猜你喜欢

转载自blog.csdn.net/qq_42252844/article/details/104321220
今日推荐