一、前言
在Java中,类加载器把一个类装入Java虚拟机中,要经过三步来完成:加载、连接和初始化,其中连接又分为验证、准备和解析三个阶段。加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段可以在初始化阶段之后发生,也称为动态绑定或晚期绑定。各个步骤的主要工作如下:
- 加载:查找和导入类或接口的二进制数据;
- 连接:又可以分成校验、准备和解析三步,其中解析步骤是可以选择的;
- 验证:检查导入类或接口的二进制数据的正确性;
- 准备:给类的静态变量分配并初始化存储空间;
- 解析:将符号引用转成直接引用;
- 初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
而类加载的方式也有以下几种方式:
- 通过Class.forName()方法动态加载
- 通过ClassLoader.loadClass()方法
那么通过Class.forName()加载和通过ClassLoader.loadClass加载有啥区别呢?
二、分析
1、首先看下Class.forName()源码:
@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。
2、在Class类中还有另一个方法,可以自己传入initialize的值,源码如下:
/**
* @param name fully qualified name of the desired class
* @param initialize if {@code true} the class will be initialized.
* See Section 12.4 of <em>The Java Language Specification</em>.
* @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,类就会被初始化。
3、再看下ClassLoader.loadClass的源码:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
###############萌萌哒分割线#########################
/**
* @param name
* The <a href="#name">binary name</a> of the class
*
* @param resolve
* If <tt>true</tt> then resolve the class
*
* @return The resulting <tt>Class</tt> 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 <tt>c</tt> has
* already been linked, then this method simply returns. Otherwise, the
* class is linked as described in the "Execution" chapter of
* <cite>The Java™ Language Specification</cite>.
*
* @param c
* The class to link
*
* @throws NullPointerException
* If <tt>c</tt> is <tt>null</tt>.
*
* @see #defineClass(String, byte[], int, int)
*/
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
根据注释resolveClass方法是类加载过程中的连接操作,loadClass方法的注释可以看出参数resolve是控制是否执行连接,根据前言部分类加载的几个步骤阶段顺序,没有连接也就不会有后面的初始化,而ClassLoader.loadClass()默认resolve传的false,所以也就不会执行初始化。
4、测试验证
//测试类
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