类的初始化
类的加载、链接和初始化
加载:查找并加载类的二进制数据
链接:
验证:确保加载类的准确性
准备:为类的静态变量分配内存 并将其初始化为默认值
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;
}
分析:
准备阶段:
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;
线程上下文类加载器:打破双亲委派机制