类加载概述
所谓类加载就是将类从磁盘或网络读到JVM内存,再交给执行引擎执行。
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三大步来实现对这个类进行初始化。
1)加载>将class文件读入内存,并为之创建class对象
2)连接>验证:是否有正确的内部结构,并和其他类协调一致
准备:负责为静态成员分配内存,并设置默认初始化值
解析:将类的二进制数据中的符号引用替换成直接引用
3)初始化>为静态变量赋予初始值,执行类的静态代码块
类加载基本步骤
1)通过一个类的全限定类名来获取其定义的二进制字节流
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在堆中生成一个代表这个类的java.lang.class对象,作为对方法区中这些数据的访问入口
JVM从何处可以加载到要使用的类?
1)JDK类库中的类
2)第三方类库中的类
3)应用程序类库中的类
类加载方式分析
隐式加载
a)访问类的静态成员(例如类变量,静态方法)
b)构建类的实例对象(使用new关键字构建对象或反射构建对象)
c)构建子类实例对象(构建子类的对象时首先会加载父类类型)
package com.jvm.loader;
//隐式加载
class Class1{
static int cap = 100;
static {
System.out.println("Class1.static");
}
static void doPrint(){
System.out.println("print class info");
}
}
class SubClass1 extends Class1{
static {
System.out.println("SubClass1.static");
}
}
public class TestClassLoader1 {
public static void main(String[] args) {
// System.out.println(Class1.cap);
// Class1.doPrint();
// new Class1();
new SubClass1();
}
}
显式加载
a)ClassLoader.loadClass...
b)Class.forName...
package com.jvm.loader;
//显式加载
class ClassA{
static {
System.out.println("ClassA");
}
}
public class TestClassLoader {
public static void main(String[] args) throws Exception {
ClassLoader loader = TestClassLoader.class.getClassLoader();
// loader.loadClass("com.jvm.loader.ClassA");
// Class.forName("com.jvm.loader.ClassA");
// initialize为false不会执行静态代码块,为true才执行
Class.forName("com.jvm.loader.ClassA",true,loader);
}
}
连接分析
连接阶段目的是为了保证Class文件的字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
验证阶段
验证阶段大致完成四个阶段的检验动作:
1)文件格式的验证
2)元数据验证
3)字节码合法性验证
4)符号引用验证
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以采用-Xverfify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间
准备阶段
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。
1)类变量(static)内存分配
2)按类型进行初始默认值分配(0,0L,null,false等)
假如一个类变量定义为:public static int value = 3;那么变量value在准备阶段过后初始值为0,而不是3,把value赋值为3的动作实在初始化阶段执行的
3)如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstantValue属性所指定的值
假如上面类变量value被final修饰:public static final int value = 3;编译时将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为3
解析阶段
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
1)符号引用:一组符号,如CONSTANT_FieLdref_info来描述目标,可以是任何字面量
2)直接引用:就是直接指向目标的指针,相对偏移量或一个间接定位到目标的句柄
相同的符号引用不同的JVM机器上对应的直接引用可能不同,直接引用一般对应已加载嗲内存中的一个具体对象
初始化分析
初始化阶段为类加载的最后一个阶段,这个阶段我们可以让自定义类加载器参与进来,其余阶段完全由JVM主导,JVM负责对类进行初始化,主要是类变量的初始化。
类变量进行初始值的设定有两种:
1)声明类变量时指定初始化值
2)使用静态代码块为类变量指定初始化值
程序对类的使用方式:
主动使用:会执行加载,连接,初始化静态域
被动使用:只执行架子啊,连接,不初始化类的静态域
如何理解被动使用?
通过子类引用父类的静态字段,被动使用子类去调用,不会导致子类初始化,如下代码:
package com.jvm.loader;
class A{
public static int a = 10;
static {
System.out.println("A.a="+a);
}
}
class B extends A{
static {
System.out.println("B");
}
}
public class TestClassLoader2 {
public static void main(String[] args) {
System.out.println(B.a);
}
}
可以看出控制台中子类和父类都执行了,但子类的静态初始化方法并没有执行,这种方式就为被动使用
类加载器简介
类加载器是在类运行时负责将类读到内存的一个对象,其类型为ClassLoader类型,此类型为抽象类型,通常以父类形式出现
类加载器对象常用方法:
1)getParent() 返回类加载器的父类加载器
2)loadClass(String name) 加载名称为name的类
3)findClass(String name) 查找名称为name的类
4)findLoadedClass(String name) 查找名称为name的已经被记载过的类
5)defineClass(String name,byte[] b,int off,int len) 把字节数组b中的内容转换成java类
Java中类加载器大致可分为两种,一类是系统提供,一类是自己定义,其层次结构如下:
类加载器加载类过程分析:
1)首先查看自己是否已加载过此类,有则返回,没有则将加载任务委托给父类加载器加载,依次递归
2)父类加载器无法完成此加载任务时,自己去加载
3)自己也无法完成加载时就会抛出异常
类加载时首先委托父类加载的这种机制称为双亲委派机制,基于这种机制实现了类加载时的优先级层次关系,同时也可以保证同一个类只被一个加载器加载,例如Object类只会被BootstraoClassLoader加载,这样有利于java程序的稳定运行
package com.jvm.loader;
import java.util.ArrayList;
public class TestClassLoader3 {
public static void method1(){
ClassLoader loader = ClassLoader.getSystemClassLoader();
System.out.println(loader);//AppClassLoader
System.out.println(loader.getParent());//ExtClassLoader
System.out.println(loader.getParent().getParent());//BootstrapClassLoader
}
public static void method2(){
ClassLoader loader = Thread.currentThread().getContextClassLoader();
System.out.println(loader);//AppClassLoader
System.out.println(loader.getParent());//ExtClassLoader
System.out.println(loader.getParent().getParent());//BootstrapClassLoader
}
public static void method3(){
System.out.println("Classloader of this class:" + TestClassLoader3.class.getClassLoader());//AppClassLoader
System.out.println("Classloader of ArrayList:" + ArrayList.class.getClassLoader());//BootstrapClassLoader
}
public static void main(String[] args) {
method1();
method2();
method3();
}
}
自定义类加载器
JVM自带的类加载器只能加载默认classpath下的类,如果需要加载应用程序之外的类文件,比如网络上某个文件,这种情况就需要使用自定义加载器了。自定义加载器可以自己指定类加载路径,可以实现系统在线升级等操作。在我们使用的tomcat服务器,spring框架,mybatis框架等其内部都自己定义了类加载器。
我们自己写类加载器,一般需要直接或间接继承ClassLoader类,再重写方法。
准备工作
在指定包中创建自己写的类
package pkg;
public class Search {
static {
System.out.println("search static");
}
public Search(){
System.out.println("search constructor");
}
}
基于ClassLoader创建
代码如下:
package pkg;
import java.io.*;
public class MyClassLoader1 extends ClassLoader {
private String baseDir;
public MyClassLoader1(String baseDir){
this.baseDir=baseDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException{
byte[] classData = new byte[0];
try {
classData = loadClassBytes(name);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (classData == null){
throw new ClassNotFoundException();
} else {
return defineClass(name,classData,0,classData.length);
}
}
private byte[] loadClassBytes(String className) throws FileNotFoundException {
String fileName = baseDir + className.replace('.', File.separatorChar)+".class";
System.out.println("fileName=" + fileName);
InputStream ins = null;
try{
ins = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = 0;
while ((length = ins.read(buffer)) != -1){
baos.write(buffer,0,length);
}
return baos.toByteArray();
} catch (IOException e){
e.printStackTrace();
throw new RuntimeException(e);
}finally {
if(ins != null)
try{
ins.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
定义测试方法,假如使用自定义类加载加载类,要求被加载的类应与当前类不在同一个命名空间范围内,否则可能直接使用AppClassLoader进行类加载
package pkg;
public class TestMyClassLoader1 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
String baseDir = "D:\\Idea\\WORKSPACE\\";
MyClassLoader1 classLoader1 = new MyClassLoader1(baseDir);
String pkgCls = "pkg.Search";
Class<?> testClass = classLoader1.loadClass(pkgCls);
Object object = testClass.newInstance();
System.out.println(object.getClass());
System.out.println(object.getClass().getClassLoader());
}
}
基于URLClassLoader创建
此类加载器可加载网络中的资源,URLClassLoader可以从指定目录,jar包,网络中加载指定的类资源
package pkg;
import java.net.URL;
import java.net.URLClassLoader;
public class MyClassLoader2 extends URLClassLoader {
public MyClassLoader2(URL[] urls){
super(urls,null);
}
}
编写测试类
package pkg;
import java.io.File;
import java.net.URI;
import java.net.URL;
public class TestMyClassLoader2 {
public static void main(String[] args) throws Exception {
File file = new File("D:\\Idea\\WORKSPACE\\");
URI uri = file.toURI();
URL[] urls = {uri.toURL()};
ClassLoader classLoader2 = new MyClassLoader2(urls);
Class<?> cls = classLoader2.loadClass("pkg.Search2");
System.out.println(classLoader2);
Object obj = cls.newInstance();
System.out.println(obj);
}
}
基于类加载器实现热替换
当项目运行需要实现在线升级,也就是我们常说的热替换,可以通过自定义加载实现,程序可运行期间可以将不需要的目标类删除,再将新的目标类放到原先的地方,以实现热替换操作。
package pkg;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashSet;
public class MyClassLoader3 extends ClassLoader {
private String basedir;
private HashSet<String> loadClasses;
public MyClassLoader3(String basedir,String[] classes) throws IOException {
super(null);
this.basedir = basedir;
loadClasses = new HashSet<String>();
customLoadClass(classes);
}
private void customLoadClass(String[] classes) throws IOException {
for (String classStr : classes){
loadDirectly(classStr);
loadClasses.add(classStr);
}
}
private void loadDirectly(String name) throws IOException {
StringBuilder sb = new StringBuilder(basedir);
String classname = name.replace('.', File.separatorChar) + ".class";
sb.append(File.separator).append(classname);
File classF = new File(sb.toString());
instantiateClass(name,new FileInputStream(classF),classF.length());
}
private void instantiateClass(String name, FileInputStream fileInputStream, long length) throws IOException {
byte[] raw = new byte[(int) length];
fileInputStream.read(raw);
fileInputStream.close();
defineClass(name,raw,0,raw.length);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> cls;
cls = findLoadedClass(name);
if(!this.loadClasses.contains(name) && cls==null)
cls = getSystemClassLoader().loadClass(name);
if (cls == null) throw new ClassNotFoundException(name);
if (resolve)resolveClass(cls);
return cls;
}
}
编写测试类
package pkg;
public class TestMyClassLoader3 {
public static void main(String[] args) throws Exception {
MyClassLoader3 loader3 = new MyClassLoader3("D:\\Idea\\WORKSPACE\\", new String[]{"pkg.Search"});
Class<?> cls = loader3.loadClass("pkg.Search");
System.out.println(cls.getClassLoader());
Object search = cls.newInstance();
System.out.println(search);
loader3 = new MyClassLoader3("D:\\Idea\\WORKSPACE\\",new String[]{"pkg.Search"});
cls = loader3.loadClass("pkg.Search");
System.out.println(cls.getClassLoader());
search = cls.newInstance();
System.out.println(search);
}
}