类加载的过程
类加载机制:虚拟机把Class文件加载到内存,并对数据进行验证、解析、准备和初始化,最终形成被虚拟机使用的Java类型。
ps:Java的类加载过程都是在程序运行期间完成的
类加载方式
这里的类加载不是指类加载阶段,而是指整个类加载过程,即类加载阶段到初始化完成。
- 静态加载(在编译期确定)
通过new关键字来创建实例。
/**
* 静态加载类
*/
Test test = new Test();
- 动态加载
a. 通过反射加载类,然后通过newInstance()方法实例化对象;
PS: Class.forName(String name, boolean initialize,ClassLoader loader),可以指定是否初始化,指定类加载器。
/**
* 动态加载类
*/
Class Test = Class.forName("Test");
Object test = Test.newInstance();
b.通过classLoader实例的loadClass()方法加载,然后通过newInstance()方法实例化对象;
/**
* 动态加载类
*/
Class Test = classLoader.loadClass("Test");
Object test = Test.newInstance();
加载方式区别:
1. new()和Class.forName()使用的都是当前的类加载器(即:this.getClass.getClassLoader);
2. Class.forName()若想加载当前包名以外的类,需要加上包的全限定名,如:“java.lang.String”;
3. Class.forName()默认初始化,classLoader不会初始化,且JDBC连接时有一段静态代码的注册要执行,因此必须初始化,只能用Class.forName();
new关键字和newInstance()方法的区别:
1. newInstance:弱类型,低效率,只能调用无参构造;
2. new:强类型,相对高效,能调用任何Public构造。
1. 加载
1. 通过一个类的全限定名来获取Class文件(二进制字节码文件);
2. 将字节流按虚拟机运行的数据结构存储在方法区;
3. 在内存中生成代表这个类的java.lang.Class对象,作为方法区这个类的数据访问入口。
2. 验证
为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,是否危害虚拟机。
1. 文件格式验证;
2. 元数据验证;
3. 字节码验证;
4. 符号引用验证;
3. 准备
准备阶段是正式为类变量(静态变量,static)分配内存并设置初始值(数据类型的零值)的阶段。带有final的常量直接初始化。
4. 解析
解析阶段是将常量池中的符号引用转换成直接引用的过程。
符号引用:用来定位到目标的一组符号描述,在编译阶段编译器并不知道引用对象的地址所以只能用符号引用来定位目标。
直接引用:可以直接指向目标的指针、相对偏移量或能间接定位到目标的句柄。
5. 初始化
初始化是执行类构造器方法去初始化类变量和其他资源的过程,包括静态变量和静态代码块。
静态语句块只能访问定义在它之前的变量,对于定义在它之后的变量,前面的语句块可以赋值,但不能访问。
public class Test{
static {
i=0;//可以赋值,正常通过
System.out.print(i);//编译器提示“非法向前”
}
static int i=1;
}
初始化时会保证父类的初始化已被执行。
程序一般执行顺序是:静态变量—>静态语句块—>静态方法
因此初始化或者继承时的顺序:父类的静态相关—>子类的静态相关—>父类的非静态相关—>子类的非静态相关
类加载器
定义:通过一个类的全限定名来获取描述此类的二进制字节流
PS:不同类加载器加载的类是不同的(包括代表类的Class对象的equals()方法、isAssignableFrom()、 isInstance()方法的返回结果)。
启动类加载器:虚拟机自身的一部分,用于加载<JAVA_HOME>\lib目录中或者被-Xbootclasspath参数指定的路径的类库到虚拟机内存中。
扩展类加载器:由sun.misc.Launcher$ExtClassLoader实现,加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的类库。(开发者可使用)
应用程序类加载器:由sun.misc.Launcher$App-ClassLoader实现,加载用户类路径(ClassPath)上的类库(开发者直接使用,若无自定义,这就是默认的类加载器)
类加载器的双亲委任模型
双亲委派模型:类加载器收到类加载的请求,会委派给父类去加载,如果父类无法加载才会自己加载。
优点:类加载器有了一种优先级关系,保证底层的例如Object类,在各种加载器环境中都是一个类。
PS: 当运行一个程序的时候,JVM启动,运行bootstrap classloader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。
类加载器的三种获取方式:
- 当前类.Class.getClassLoader(),获取当前类加载器,可能是启动类、扩展类或者系统类,取决于当前类是用什么加载器加载的;
//输出null,代表启动类加载器
String.class.getClassLoader();
- getContextClassLoader(),获取当前线程上下文的类加载器(可设置);
Thread.currentThread().getContextClassLoader();
//获取到扩展类加载器,并设置到线程上下文中,再获取线程上下文类加载器就是扩展类加载器了
ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();
Thread.currentThread().setContextClassLoader(classLoader);
- 获取系统类加载器。
//输出AppClassLoader---系统类加载器
ClassLoader.getSystemClassLoader();