JVM运行与类加载

类加载机制

JVM把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成JVM可以直接使用的Java类型的过程。

类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)链接,如下图所示:

加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个
类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。
所有需要访问和使用类数据只能通过这个堆中的Class对象。这个加载的过程需要类加载器参与。

链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
  1)验证:确保加载的类信息符合JVM规范,没有安全方面的问题
  2)准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
  3)解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
初始化:
  1)执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中
的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
  2)当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
  3)虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步
public class TestClassLoading {
	public static void main(String[] args) {
		System.out.println(A.m);
	}
}
class A{
	static{
		m = 300;
	}
	static int m = 100;
}
//第二步:链接结束后m=0
//第三步:初始化后,m的值由<clinit>()方法执行决定
         这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并产生,类似于
         <clinit>(){
             m = 300;
             m = 100;
         }
public class TestClassLoading {
	public static void main(String[] args) {
		System.out.println(A.m);
	}
	
	static{
		System.out.println("main在的类");//最先加载
	}

}
class Father{
	static{
		System.out.println("父类被加载");
	}
}
class A extends Father{
	static{
		System.out.println("子类被加载");
		m = 300;
	}
	static int m = 100;
}

从哪里加载.class字节码

class字节码

1)从本地系统直接读取.class文件

2)通过网络下载.class文件或数据

3)从zip,jar等归档文件中加载.class文件

4)从专有数据库中提取.class数据

5)将Java源文件数据上传到服务器中动态编译为.class数据

什么时候会发生类初始化?

类的主动引用(一定会发生类的初始化)
  当虚拟机启动,先初始化main方法所在的类
  new一个类的对象
  调用类的静态成员(除了final常量)和静态方法
  使用java.lang.reflect包的方法对类进行反射调用
  当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类
类的被动引用(不会发送类的初始化)
  当访问一个静态域时,只有真正声明这个域的类才会被初始化
  当通过子类引用父类的静态变量,不会导致子类初始化
  通过数组定义类引用,不会触发此类的初始化
  引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
package com.reflection.classloader;

public class TestClassLoading {
	public static void main(String[] args) throws Exception{
		//主动引用:一定会导致A和Father的初始化
//		A a = new A();
//		System.out.println(A.m);
//		Class.forName("com.reflection.classloader.A");
		
		//被动引用
//		A[] array = new A[5];//不会导致A和Father的初始化
//		System.out.println(A.b);//只会初始化Father
//		System.out.println(A.M);//不会导致A和Father的初始化		
	}
	
	static{
		System.out.println("main在的类");
	}

}
class Father{
	static int b = 2;
	static{
		System.out.println("父类被加载");
	}
}
class A extends Father{
	static{
		System.out.println("子类被加载");
		m = 300;
	}
	static int m = 100;
	static final int M = 1;
}

类加载器

类加载器的作用:

类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个
代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口

类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。
不过JVM垃圾回收机制可以回收这些Class对象。

类加载器的层次结构(树状结构)

1.引导类加载器(bootstrap class loader):
   它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容),是用原生代码(C/C++)来实现的,并不继承自java.lang.ClassLoder。
   加载扩展类和应用程序类加载器。并指定为他们的父类加载器。
2.扩展类加载器(extensions class loader)
   用来加载Java的扩展库(JAVA_HOME/jre/ext/*.jar或java.ext.dirs路径下的内容)。Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java类
   由sun.misc.Launcher$ExtClassLoader实现
3.应用程序类加载器(application class loader)
   它根据Java应用的类路径(classpath,java.class.path)的类
   一般来说,Java应用的类都是由它来完成加载。
   由sun.misc.Launcher$AppClassLoader实现
4.自定义类加载器
   开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求

依次往下是父子关系,但这个父子关系不是通过继承实现的,而是通过组合实现的。(好比亲生与养父)
public abstract class ClassLoader {

    private static native void registerNatives();
    static {
        registerNatives();
    }

    // The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields must be added *after* it.
//代表父类加载器,其他字段必须添加在它的后面
private final ClassLoader parent;//组合的方式说明其父类加载器

//....
}
	public static void main(String[] args) {
		ClassLoader c = ClassLoader.getSystemClassLoader();
		System.out.println(c);//sun.misc.Launcher$AppClassLoader@c387f44
		
		ClassLoader cf = c.getParent();
		System.out.println(cf);//sun.misc.Launcher$ExtClassLoader@659e0bfd
		
		ClassLoader cff = cf.getParent();
		System.out.println(cff);//null  引导类加载器
	}

类加载器的代理模式

代理模式:交给其他类加载器来加载指定的类。
    双亲委托机制:就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯,
直到最高的引导类加载器,如果父类加载器可以完成类加载任务,就成功返回,只有父类加载器无法完成此加载任务时,才自己去加载。
    双亲委托机制是为了保证Java核心库的类型安全。例如,这种机制就保证不会出现用户自己
能定义java.lang.Object类的情况。类加载器除了用于加载类,也是安全的最基本的屏障。
    双亲委托机制是代理模式的一种,并不是所有的类加载器都采用双亲委托机制。tomcat服务器类加载器也是使用代理模式,所不
同的是它是首先尝试去加载某个类,如果找不到在交给父类加载器。这与一般类加载器的顺序是相反的。
验证方法之一:
//自己写一个java.lang.String
package java.lang;

public class String {
}
package com.reflection.classloader;

public class TestClassLoader {
	public static void main(String[] args) throws Exception {
		String str = new String();
		ClassLoader classLoader = str.getClass().getClassLoader();
		System.out.println(classLoader);//null  引导类加载器
		//说明还是加载的核心类库中的java.lang.String,不是你自己写的String
		System.out.println(str.getClass().getMethod("substring", int.class));
	}
}
//验证方法二:
package java.lang;

public class String {
	public static void main(String[] args)throws Exception {
		String str = new String();
		//运行错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
		//	   public static void main(String[] args)
	}
}

自定义类加载器:

自定义类加载器的流程:
1.继承java.lang.ClassLoader
2.首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经被装载,直接返回;
3.委派类加载器请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例;
4.调用本类加载器的findClass(...)方法,试图获取对应的字节码,如果获取的到,则调用defineClass(...)导入类型到方法区;
如果获取不到对应的字节码或其他原因失败,则异常,终止加载过程
注意:被两个类加载器加载的同一个类,JVM不认为是相同的类。
  1. 文件类加载器
package com.reflection.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

//继承java.lang.ClassLoader
public class FileSystemClassLoader extends ClassLoader{
	private String rootDir;
	public FileSystemClassLoader(String rootDir){
		this.rootDir = rootDir;
	}
	
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		//首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经被装载,直接返回;
		Class<?> c = findLoadedClass(name);
		if(c ==null){
			//委派类加载器请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例;
			ClassLoader parent = this.getParent();
			try {
				c = parent.loadClass(name);//加异常处理,父类加载不到,然后自己加载
			} catch (Exception e) {
//				e.printStackTrace();
			}
			
			//调用本类加载器的findClass(...)方法,试图获取对应的字节码,如果获取的到,则调用defineClass(...)导入类型到方法区;
			//如果获取不到对应的字节码或其他原因失败,则异常,终止加载过程
			if(c == null){
				byte[] classData = getClassData(name);
				if(classData == null){
					throw new ClassNotFoundException();
				}else{
					c = defineClass(name, classData, 0, classData.length);
				}
			}
		}
		return c;
	}
	
	//把.class文件的内容读取到一个字节数组中
	//为什么要读取的字节数组中,因为protected final Class<?> defineClass(String name,byte[] b,int off,int len)
	private byte[] getClassData(String name) {
		String path = rootDir + File.separator + name.replace(".", File.separator)+".class";
		InputStream is = null;
		ByteArrayOutputStream baos = null;
		try {
			is = new FileInputStream(path);
			baos =new ByteArrayOutputStream(); 
			byte[] buffer = new byte[1024];
			int len;
			while((len = is.read(buffer))!=-1){
				baos.write(buffer, 0, len);
			}
			return baos.toByteArray();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				if(is!=null){
					is.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
}

package com.reflection.classloader;

public class TestFileSystemClassLoader {

	public static void main(String[] args) throws ClassNotFoundException {
		FileSystemClassLoader fsc = new FileSystemClassLoader("D:/atguigu/android1020/code/day9/bin");
		Class clazz = fsc.loadClass("com.atguigu.shortcut.TestShortCut");
		System.out.println(clazz);
		
		//同一个类,第二次加载直接返回上一次的Class对象
		Class clazz2 = fsc.loadClass("com.atguigu.shortcut.TestShortCut");
		System.out.println(clazz == clazz2);
		
		//注意:被两个类加载器加载的同一个类,JVM不认为是相同的类。
		FileSystemClassLoader fsc2 = new FileSystemClassLoader("D:/atguigu/android1020/code/day9/bin");
		Class clazz3 = fsc2.loadClass("com.atguigu.shortcut.TestShortCut");
		System.out.println(clazz == clazz3);
		
		//也可以使用自定义类加载器加载系统类,但是实际上会委托给父类加载器
		Class c = fsc.loadClass("java.lang.String");
		System.out.println(c);
		System.out.println(c.getClassLoader());//委托给父类加载器...一直到引导类加载器
	}
}

2.网络类加载器

扫描二维码关注公众号,回复: 4031362 查看本文章
package com.reflection.classloader;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

//1、继承java.lang.ClassLoader
public class NetClassLoader extends ClassLoader{
	private String rootUrl;
	public NetClassLoader(String rootUrl){
		this.rootUrl = rootUrl;
	}
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		//2、首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经被装载,直接返回;
		Class<?> c = findLoadedClass(name);
	
		if(c ==null){
			//3、委派类加载器请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例;
			ClassLoader parent = this.getParent();
			try {
				c = parent.loadClass(name);//加异常处理,父类加载不到,然后自己加载
			} catch (Exception e) {
//				e.printStackTrace();
			}
			
			//4、调用本类加载器的findClass(...)方法,试图获取对应的字节码,如果获取的到,则调用defineClass(...)导入类型到方法区;
			//如果获取不到对应的字节码或其他原因失败,则异常,终止加载过程
			if(c == null){
				byte[] classData = getClassData(name);
				if(classData == null){
					throw new ClassNotFoundException();
				}else{
					c = defineClass(name, classData, 0, classData.length);
				}
			}
		}
		return c;
	}
	
	//把.class文件的内容读取到一个字节数组中
	//为什么要读取的字节数组中,因为protected final Class<?> defineClass(String name,byte[] b,int off,int len)
	private byte[] getClassData(String name) {
		String path = rootUrl + "/" + name.replace(".", "/")+".class";
		InputStream is = null;
		ByteArrayOutputStream baos = null;
		try {
			
			URL url = new URL(path);
			is = url.openStream();
			baos =new ByteArrayOutputStream(); 
			byte[] buffer = new byte[1024];
			int len;
			while((len = is.read(buffer))!=-1){
				baos.write(buffer, 0, len);
			}
			return baos.toByteArray();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				if(is!=null){
					is.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
}

猜你喜欢

转载自blog.csdn.net/weixin_43549578/article/details/83905171
今日推荐