在Java中,类加载器把一个类装入Java虚拟机中,要经过三步来完成:加载、连接和初始化,其中连接又分为验证、准备和解析三个阶段。加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段可以在初始化阶段之后发生,也称为动态绑定或晚期绑定。各个步骤的主要工作如下:
而类加载的方式也有以下几种方式:
那么通过Class.forName()加载和通过ClassLoader.loadClass加载有啥区别呢?
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
可以看到底层调用的forName0(className, true, ClassLoader.getClassLoader(caller), caller),第二个参数initialize表示是否执行类中的静态代码块、对类中的静态变量赋值等初始化操作,默认给的true。
/**
* @param name fully qualified name of the desired class
* @param initialize if {@code true} the class will be initialized.
* See Section 12.4 of The Java Language Specification.
* @param loader class loader from which the class must be loaded
* @return class object representing the desired class
*
* @exception LinkageError if the linkage fails
* @exception ExceptionInInitializerError if the initialization provoked
* by this method fails
* @exception ClassNotFoundException if the class cannot be located by
* the specified class loader
*
* @see java.lang.Class#forName(String)
* @see java.lang.ClassLoader
* @since 1.2
*/
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
Class<?> caller = null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// Reflective call to get caller class is only needed if a security manager
// is present. Avoid the overhead of making this call otherwise.
caller = Reflection.getCallerClass();
if (sun.misc.VM.isSystemDomainLoader(loader)) {
ClassLoader ccl = ClassLoader.getClassLoader(caller);
if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader, caller);
}
官方给的参数注释写的也很清楚:@param initialize if {@code true} the class will be initialized. 如果给的true,类就会被初始化。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
###############萌萌哒分割线#########################
/**
* @param name
* The binary name of the class
*
* @param resolve
* If true then resolve the class
*
* @return The resulting Class object
*
* @throws ClassNotFoundException
* If the class could not be found
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
// 重点:这里根据参数resolve的值控制是否调用连接方法
if (resolve) {
resolveClass(c);
}
return c;
}
}
###############萌萌哒分割线#########################
/**
* Links the specified class. This (misleadingly named) method may be
* used by a class loader to link a class. If the class c has
* already been linked, then this method simply returns. Otherwise, the
* class is linked as described in the "Execution" chapter of
* The Java™ Language Specification.
*
* @param c
* The class to link
*
* @throws NullPointerException
* If c is null.
*
* @see #defineClass(String, byte[], int, int)
*/
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
根据注释resolveClass方法是类加载过程中的连接操作,loadClass方法的注释可以看出参数resolve是控制是否执行连接,根据前言部分类加载的几个步骤阶段顺序,没有连接也就不会有后面的初始化,而ClassLoader.loadClass()默认resolve传的false,所以也就不会执行初始化。
//测试类
public class LycClassLoadDemo {
static {
System.out.println(".....静态代码块.....");
}
private static String str = "ddd";
private static void testMethod() {
System.out.println("....静态方法....");
str = "testMethod";
}
}
public class TestClassForName {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("com.lyc.learing.LycClassLoadDemo");
System.out.println("...test main...");
}
}
上面Class.forName方式执行结果:
…静态代码块…
…test main…
public class TestClassLoader {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader.getSystemClassLoader().loadClass("com.lyc.learing.LycClassLoadDemo");
System.out.println("...test main...");
}
}
上面ClassLoader.loadClass方式执行结果:
…test main…
通过源码及测试结果可以知道:
Class.forName():把类的.class文件加载到JVM中,但是在对类进行加载的同时会执行类中的static静态代码块
ClassLoader.loadClass():把.class文件加载到JVM中,不会执行static代码块中的内容,只有在newInstance才会去执行
1、对于Class.forName的使用最熟悉的应该就是加载数据库驱动,下面是Mysql驱动的部分源码:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
可以看到mysql驱动注册到DriverManager的操作代码是写在静态代码块中的,而我们也是使用Class.forName方式加载驱动的。
参考文章:
[1] https://zhuanlan.zhihu.com/p/43845064