Class文件的装载流程
类装载的条件(主动使用)
创建一个实例,使用new,或者反射,序列化,克隆 当调用类的static方法,使用了invokestatic指令 使用了类或接口的static字段,使用了getstatic或putstatic指令,但final static的字段除外,这是常量池里的 使用反射,反射类的方法时 当初始化子类时,要先初始化其父类 有启动虚拟机的main()方法的那个类
被动使用
子类调用父类的static字段,会让父类初始化,子类不会被初始化。只有直接定义该字段的类,才会被初始化。而子类此时已经被加载了,只是没有被初始化。使用-XX:+TraceClassLoading能跟踪类的初始化。 final常量会直接放进常量池,javac在编译时候会将常量直接植入目标类,不再使用被引用类。
1.加载类
这是类装载的第一阶段。通过类的全名获取类的二进制流,解析二进制流为方法区的数据结构,创建Class对象,表示这个类。
val clazz = Class.forName("java.lang.String" )
val methods = clazz.declaredMethods
for (i in methods){
val mod = Modifier.toString(i.modifiers)
print ("$mod ${i.name} (" )
val clazzs = i.parameterTypes
if (clazzs.size == 0 ) print (")" )
for (j in clazzs){
print ("${j.simpleName} ," )
}
print (")" )
println("" )
}
2.验证类
3.准备
虚拟机会为这个类分配相应的内存空间,并设置初始值。如int被初始化为0,其中boolean类型比较特殊,内部实现还是int,boolean默认为0,即false。
4.解析
解析阶段就是将类,接口,字段,方法的符号引用转为直接引用。如果直接引用存在,则肯定系统中存在某类,如果只存在符号引用,则不一定存在该对象。
public class Test {
public static void main (String...args){
System.out .println("lalalalala" );
}
}
Compiled from "Test.java"
public class Test {
public Test ();
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 #3 // String lalalalala
5 : invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8 : return
}
其中invokevirtual使用类常量池#4,找到后,再沿着递归树往下找。
初始化
初始化是类加载的最后一个阶段。重要工作是执行类的初始化方法。会生成一个(clinit)函数的调用,虚拟机会保证其线程安全性,当多线程试图初始化同一个类时,只有一个线程可以进入clinit,其他线程必须等待。
所以有死锁的可能性
public class StaticA {
static {
try {
Thread.sleep(1000 );
}catch (Exception r){}
try {
Class.forName("StaticB" );
}catch (Exception r){}
System.out .println("StaticA init OK" );
}
}
public class StaticB {
static {
try {
Thread.sleep(1000 );
}catch (Exception r){}
try {
Class.forName("StaticA" );
}catch (Exception r){}
System.out .println("StaticB init OK" );
}
}
public class Main {
public static void main (String...args) {
new Thread(() -> {
try {
Class.forName("StaticA" );
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out .println("A finished" );
}).start();
new Thread(() -> {
try {
Class.forName("StaticB" );
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out .println("B finished" );
}).start();
}
}
线程1试图去初始化StaticA,线程2试图去初始化StaticB,在StaticA的初始化过程中,会去尝试初始化StaticB, 这种状况下会导致死锁。最坑的是,用jstack去看的时候,也不能发现死锁。
ClassLoader
ClassLoader主要作用是从系统外部获取Class二进制数据流,然后交给JVM进行连接,初始化等操作。因此ClassLoader在整个装载阶段只影响类的加载,而无法通过ClassLoader去改变类的连接和初始化行为。
loadClass(String name):给定类名,加载一个类,返回Class实例 defineClass(byte[] b):给定二进制流,定义一个类。只在ClassLoader的子类中才能使用。 findClass(String name):查找一个类,会在loadClass()时候用得到,用于自定义查找类的逻辑。 findLoadedClass(String name): 查找已经被加载的类,final方法
双亲委托机制
在加载的时候,系统会判断当前类是否已经被加载,如果已经被加载了,就会直接返回可用的类,否则就会请求委托双亲加载,如果双亲加载失败,则会自己加载。
protected Class<?> loadClass (String var1, boolean var2) throws ClassNotFoundException {
synchronized (this .getClassLoadingLock(var1)) {
Class var4 = this .findLoadedClass(var1);
if (var4 == null ) {
long var5 = System.nanoTime();
try {
if (this .parent != null ) {
var4 = this .parent.loadClass(var1, false );
} else {
var4 = this .findBootstrapClassOrNull(var1);
}
} catch (ClassNotFoundException var10) {
;
}
if (var4 == null ) {
long var7 = System.nanoTime();
var4 = this .findClass(var1);
PerfCounter.getParentDelegationTime().addTime(var7 - var5);
PerfCounter.getFindClassTime().addElapsedTimeFrom(var7);
PerfCounter.getFindClasses().increment();
}
}
if (var2) {
this .resolveClass(var4);
}
return var4;
}
}
双亲委派的弊端
即顶层的ClassLoader无法访问底层的ClassLoader加载的类。在一般情况下顶层的ClassLoader加载的系统类,应用类加载器加载应用类,应用类自然能访问系统类。但特殊情况下,系统类访问应用类就出现问题。比如,系统类提供一个接口,该接口由应用类自己实现,并绑定一个一个工厂方法,用于创建该接口的实例,而接口和工厂方法都在启动类加载器中,这时就会出现工厂方法无法创建由类加载器加载的应用实例的问题。(如JDBC)
从JDBC看破坏双亲委派
java中存在一种SPI(Service Provider Interface)机制,该机制将不同厂商的实现抽象成一种通用的规范接口或者模块,比如这里的JDBC模块,不同数据库厂商针对统一的JDBC规范进行定制化的实现,对于JVM来说JDBC相关的类文件在rt.jar中,典型的有java.sql.Drvier.class、java.sql.DriverManager.class等文件
如上面所说rt.jar是由引导类加载器BootStrap ClassLoader负责加载的,而我们引入的第三方实现比如com.mysql.jdbc.Driver.class却是由应用类加载器加载的,在程序运行时要对具体数据库实现进行操作时才能知道需要用的具体实现类,这就出现问题了,BootStrap是不能逆向加载Application中的实现类的,但具体操作时又必须使用具体的实现类
object JDBCUtil {
init {
Class.forName("com.mysql.jdbc.Driver" )
}
@Throws (SQLException::class)
fun getConnection(url: String): Connection {
return DriverManager.getConnection(url)
}
}
public static Connection getConnection (String var0) throws SQLException {
Properties var1 = new Properties();
return getConnection(var0, var1, Reflection.getCallerClass());
}
private static Connection getConnection (String var0, Properties var1, Class<?> var2) throws SQLException {
ClassLoader var3 = var2 != null ? var2.getClassLoader() : null ;
Class var4 = DriverManager.class;
synchronized (DriverManager.class) {
if (var3 == null ) {
var3 = Thread.currentThread().getContextClassLoader();
}
}
if (var0 == null ) {
throw new SQLException("The url cannot be null" , "08001" );
} else {
println("DriverManager.getConnection(\"" + var0 + "\")" );
SQLException var10 = null ;
Iterator var5 = registeredDrivers.iterator();
while (true ) {
while (var5.hasNext()) {
DriverInfo var6 = (DriverInfo)var5.next();
if (isDriverAllowed(var6.driver, var3)) {。。。。。
private static boolean isDriverAllowed (Driver var0, ClassLoader var1) {
boolean var2 = false ;
if (var0 != null ) {
Class var3 = null ;
try {
var3 = Class.forName(var0.getClass().getName(), true , var1);
} catch (Exception var5) {
var2 = false ;
}
var2 = var3 == var0.getClass();
}
return var2;
}
public static Class<?> forName (String var0, boolean var1, ClassLoader var2) throws ClassNotFoundException {
Class var3 = null ;
SecurityManager var4 = System.getSecurityManager();
if (var4 != null ) {
var3 = Reflection.getCallerClass();
if (VM.isSystemDomainLoader(var2)) {
ClassLoader var5 = ClassLoader.getClassLoader(var3);
if (!VM.isSystemDomainLoader(var5)) {
var4.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(var0, var1, var2, var3);
}
热替换
同一个类,只要是由不同的类加载器加载的,在JVM是两个Class对象。所以实现热部署的思路就是创建新的ClassLoader去加载改变的类。
自定义类加载器
class MyClassLoader (val fileName : String ) : ClassLoader () {
override fun findClass(className : String ?) : Class <*> {
var clazz: Class <*>? = this.findLoadedClass(className )
if (clazz == null ) {
val classFile = getClassFile(className ) ;
val fis = FileInputStream (classFile )
val fileC = fis.channel
val baos = ByteArrayOutputStream ()
val outC = Channels .newChannel(baos )
val buffer = ByteBuffer .allocateDirect(1024)
while (true ) {
val i = fileC.read(buffer )
if (i == 0 || i == -1) {
break
}
buffer.flip()
outC.write(buffer )
buffer.clear()
}
fis.close()
val bytes = baos.toByteArray()
clazz = defineClass(className ,bytes ,0,bytes .size )
}
return clazz!!
}
fun getClassFile(className : String ?) : String {
return className!!
}
}
要热加载的类
class HotA {
fun hot(){
println("I am hot A" )
}
}
入口文件
fun main(args: Array<String>){
while (true ){
val loader = MyClassLoader("src/main/" )
val clazz = loader.loadClass("HotA" )
val instance = clazz.newInstance()
val methodHot = instance.javaClass.getMethod("hot" )
methodHot.invoke(instance)
Thread.sleep(10000 )
}
}