JVM-从JDK源码级别剖析JVM类加载机制(1)

从Java.exe解释java类加载运行全过程

类加载宏观流程图:
在这里插入图片描述
java 指令在windows 系统下就是一个java.exe文件,在执行java 指令是会调用底层的jvm.dll(这个是动态连接库,是java虚拟机的实际操作处理所在)文件创建Java虚拟机(由C++实现的),创建完虚拟机后,会接着创建一个引导类加载器的实例(C++实现),然后C++会调用Java代码创建JVM启动器实例sun.misc.Launcher 该类由引导类加载器 (Java中以NULL标识) 负责加载,Launcher类中getLauncher方法中会初始化Launcher对象,Launcher初始化时会创建其他的类加载器(AppClassLoader也会创建),然后再加载代码所需要的Math类,加载完成后C++会调用Math.main()。

其中Java层面的loadClass的步骤有:
loadClass 步骤图:
在这里插入图片描述

1. 加载:
从磁盘中查找并通过IO读入字节码文件中,使用到类时才会加载,例如 调用main方法 new对象,在加载阶段会生成一个对应的.class文件,作为方法去对该类数据访问的入口
2. 验证
验证字节码的文件正确性,例如cafe baby
3. 准备
进行赋默认值 例如 int 类型赋值0 boolean 赋值为 false…
4. 解析
将符号引用变为直接引用,该阶段会把一些静态方法(符号引用、main())替换成指向数据所存内存的指针或者句柄(直接引用),这就是所谓的静态链接在(类加载期间完成),动态链接 指的是代码在运行期间完成符号引用替换成直接引用 例如 在运行期间调用了 compute ()方法,直接引用就是指向compute方法的内存地址
在这里插入图片描述
在这里插入图片描述

5. 初始化
对类的静态变量赋初始值,执行静态代码块。

类被加载到方法区中后主要包含 运行时常量池、类型信息、字段信息、方法信息、类加载器的 引用、对应class实例的引用等信息。
类加载器的引用
这个类到类加载器实例的引用
对应class实例的引用
类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的 对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。

类的加载机制(懒加载)

主类在运行过程中如果使用到其它类,会逐步加载这些类。 jar包或war包里的类不是一次性全部加载的,是使用到时才加载。

public class TestDynamicLoad { 
 static { 
System.out.println("*************loadTestDynamicLoad************"	); 
 } 
 public static void main(String[] args) { 
  new A(); 
  System.out.println("*************load test************"); 
  B b = null; //B不会加载,除非这里执行 new B()
   } 
  } 
  class A { 
  	static { 
  		System.out.println("*************load A************"); 
  	} 
  public A() { 
  	System.out.println("*************initial A************");
  }
  } 
  class B { 
  static { 
  System.out.println("*************load B************");
  } 
  public B() { 
  System.out.println("*************initial B************");
   }  
  } 
  运行结果: 
*************load TestDynamicLoad************  		
*************load A************
*************initial A************  
*************load test************

类加载器和双亲委派机制

上面的类加载过程主要是通过类加载器来实现的,Java里有如下几种类加载器

  1. 引导类加载器
    负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等

  2. 扩展类加载器
    负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR 类

  3. 应用程序类加载器
    负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类

  4. 自定义加载器
    负责加载用户自定义路径下的类包

类加载器创建源码:

  //sun.misc.Launcher#getLauncher 获取launcher对象
  public static Launcher getLauncher() {
        return launcher;
    }
public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
        	//生成ExtClassLoader(扩展类加载器)
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
       		 //生成AppClassLoader(应用类加载器)
       		// 注意:在ClassLoader父类中有一个parent参数也是在这里指定的 var1(涉及到类加载机制:双亲委派机制)
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

认识类加载器:

public class TestJDKClassLoader { 
    public static void main(String[] args) { 
    	System.out.println(String.class.getClassLoader()); 
    	System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName()); 
    	System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName()); 
    	System.out.println();
    	ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); 
    	ClassLoader extClassloader = appClassLoader.getParent(); 
    	ClassLoader bootstrapLoader = extClassloader.getParent();  
    	System.out.println("the bootstrapLoader : " + bootstrapLoader); 
    	System.out.println("the extClassloader : " + extClassloader); 
    	System.out.println("the appClassLoader : " + appClassLoader); 
    	System.out.println(); 
    	System.out.println("bootstrapLoader加载以下文件:"); 
    	URL[] urls = Launcher.getBootstrapClassPath().getURLs(); 
    	for (int i = 0; i < urls.length; i++) { 
    		System.out.println(urls[i]); 
    	} 
            System.out.println(); 
            System.out.println("extClassloader加载以下文件:"); 
            System.out.println(System.getProperty("java.ext.dirs")); 
            System.out.println(); 
            System.out.println("appClassLoader加载以下文件:"); 
            System.out.println(System.getProperty("java.class.path")); 
    } 
} 
运行结果: 
null 
sun.misc.Launcher$ExtClassLoader 
sun.misc.Launcher$AppClassLoader 
the bootstrapLoader : null 
the extClassloader : sun.misc.Launcher$ExtClassLoader@3764951d 
the appClassLoader : sun.misc.Launcher$AppClassLoader@14dad5dc 

bootstrapLoader加载以下文件: 
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/resources.jar 
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/rt.jar 
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/sunrsasign.jar 
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jsse.jar 
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jce.jar 
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/charsets.jar 
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jfr.jar 
file:/D:/dev/Java/jdk1.8.0_45/jre/classes52

extClassloader加载以下文件: 
D:\dev\Java\jdk1.8.0_45\jre\lib\ext;C:\Windows\Sun\Java\lib\ext 

appClassLoader加载以下文件: 
D:\dev\Java\jdk1.8.0_45\jre\lib\charsets.jar;D:\dev\Java\jdk1.8.0_45\jre\lib 
\deploy.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\access‐bridge‐64.jar;D:\dev\Java 
\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\dnsns.j 
ar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;D:\dev\Java\jdk1.8.0_45\jre\l 
ib\ext\jfxrt.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;D:\dev\Java 
\jdk1.8.0_45\jre\lib\ext\nashorn.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunec.j 
ar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;D:\dev\Java\jdk1.8.0_ 
45\jre\lib\ext\sunmscapi.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;D: 
ev\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\javaws. 
ar;D:\dev\Java\jdk1.8.0_45\jre\lib\jce.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jfr.j 
ar;D:\dev\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\js 
se.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\management‐ 
agent.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\plugin.jar;D:\dev\Java\jdk1.8.0_45\jre 
\lib\resources.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\rt.jar;D:\ideaProjects\projec 
t‐all\target\classes;C:\Users\zhuge\.m2\repository\org\apache\zookeeper\zookeepe 
r\3.4.12\zookeeper‐3.4.12.jar;C:\Users\zhuge\.m2\repository\org\slf4j\slf4j‐ 
api\1.7.25\slf4j‐api‐1.7.25.jar;C:\Users\zhuge\.m2\repository\org\slf4j\slf4j‐lo 
g4j12\1.7.25\slf4j‐log4j12‐ 
1.7.25.jar;C:\Users\zhuge\.m2\repository\log4j\log4j\1.2.17\log4j‐ 
1.2.17.jar;C:\Users\zhuge\.m2\repository\jline\jline\0.9.94\jline‐ 
0.9.94.jar;C:\Users\zhuge\.m2\repository\org\apache\yetus\audience‐ 
annotations\0.5.0\audience‐annotations‐0.5.0.jar;C:\Users\zhuge\.m2\repository\i 
o\netty\netty\3.10.6.Final\netty‐3.10.6.Final.jar;C:\Users\zhuge\.m2\repository 
\com\google\guava\guava\22.0\guava‐22.0.jar;C:\Users\zhuge\.m2\repository\com\go 
ogle\code\findbugs\jsr305\1.3.9\jsr305‐1.3.9.jar;C:\Users\zhuge\.m2\repository\c 
om\google\errorprone\error_prone_annotations\2.0.18\error_prone_annotations‐2.0. 
18.jar;C:\Users\zhuge\.m2\repository\com\google\j2objc\j2objc‐annotations\1.1\j2 
objc‐annotations‐1.1.jar;C:\Users\zhuge\.m2\repository\org\codehaus\mojo\animal‐ 
sniffer‐annotations\1.14\animal‐sniffer‐annotations‐1.14.jar;D:\dev\IntelliJ IDE 
A 2018.3.2\lib\idea_rt.jar 

双亲委派机制运行原理
在这里插入图片描述

首先由AppClassLoader 查询该类是否已经被加载,如果没有加载则会委托给ExtClassLoader,ExtClassLoader 也会查询该类是否已经被加载,如果没有则会委托给BootstrapClassLoader ,如果BootstrapClassLoader 在自己加载的范围中查找,没有该类的路径则会向下传递,接着由ExtClassLoader 从自己的范围中查找,没有该类的路径则会向下传递,接着由AppClassLoader 从自己的加载范围中查找找到了则进行加载。

源码解析

protected Class<?> loadClass(String name, boolean resolve) 
    throws ClassNotFoundException 
    { 
    	synchronized (getClassLoadingLock(name)) { 
    	// 检查当前类加载器是否已经加载了该类 
    	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(); 
    		//都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类 
    		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(); 
    	} 
}  
   	 if (resolve) { //不会执行 
    		resolveClass(c); 
    	} 
    	return c; 
    } 
} 

注意:
还有一种加载机制为全面委托加载机制,什么是全面委托加载机制?:比如 A类 中引用B类 ,如果A类由AppClassLoader加载的则B类的类加载器也是AppClassLoader。

为何要使用双亲委派机制?
1.沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心 API库被随意篡改

package java.lang; 
public class String { 
    public static void main(String[] args) { 
    System.out.println("**************My String Class**************"); 
    } 
} 
运行结果: 
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为: 
public static void main(String[] args) 

2.避免类的重复加载:当父亲已经加载了该类时,就没有必要ClassLoader再加载一次,保证被加载类的唯一性

如何打破双亲委派机制?
解决方案:自定义类加载器

自定义类加载器示例:
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。

public class MyClassLoaderTest { 
static class MyClassLoader extends ClassLoader { 
	private String classPath; 
	public MyClassLoader(String classPath) { 
		this.classPath = classPath; 
	} 
	private byte[] loadByte(String name) throws Exception { 
		name = name.replaceAll("\\.", "/"); 
		FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); 
		int len = fis.available(); 
		byte[] data = new byte[len]; 
		fis.read(data); 
		fis.close(); 
		return data; 
} 
	protected Class<?> findClass(String name) throws ClassNotFoundException { 
		try { 
			byte[] data = loadByte(name); 
			//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。 
			return defineClass(name, data, 0, data.length); 
		} catch (Exception e) { 
			e.printStackTrace(); 
		}
	} 
}
public static void main(String args[]) throws Exception {  
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader 
	MyClassLoader classLoader = new MyClassLoader("D:/test"); 
//D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录 
	Class clazz = classLoader.loadClass("com.tuling.jvm.User1"); 
	Object obj = clazz.newInstance(); 
	Method method = clazz.getDeclaredMethod("sout", null); 
	method.invoke(obj, null); 
	System.out.println(clazz.getClassLoader().getClass().getName()); 
	} 
} 

打破双亲委派机制

再来一个沙箱安全机制示例,尝试打破双亲委派机制,用自定义类加载器加载我们自己实现的

java.lang.String.class 
public class MyClassLoaderTest { 
	static class MyClassLoader extends ClassLoader { 
		private String classPath; 
		
		public MyClassLoader(String classPath) { 
			this.classPath = classPath; 
		} 
		
		private byte[] loadByte(String name) throws Exception { 
			name = name.replaceAll("\\.", "/");  
			FileInputStream fis = new FileInputStream(classPath + "/" + name 
+ ".class"); 
		int len = fis.available(); 
		byte[] data = new byte[len];  
		fis.read(data); 
		fis.close(); 
		return data; 
	} 
	
	protected Class<?> findClass(String name) throws ClassNotFoundException { 
		try {
			byte[] data = loadByte(name); 
			return defineClass(name, data, 0, data.length); 
		} catch (Exception e) { 
			e.printStackTrace(); 
			throw new ClassNotFoundException(); 
		} 
	} 

	/** 
	* 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载 
	* @param name 
	* @param resolve 
	* @return 
	* @throws ClassNotFoundException 
	*/ 
	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) { 
			// If still not found, then invoke findClass in order 
			// to find the class. 
			long t1 = System.nanoTime(); 
			//非自定义的类还是走双亲委派加载 
			if (!name.startsWith("com.tuling.jvm")){ 
				c = this.getParent().loadClass(name);  
			}else{ 
				c = findClass(name); 
			} 
			if (resolve) { 
				resolveClass(c);  
			} 
		return c; 
	} 
} 
} 
		public static void main(String args[]) throws Exception { 
		MyClassLoader classLoader = new MyClassLoader("D:/test"); 
		Class clazz = classLoader.loadClass("com.tuling.jvm.User1"); 
		Object obj = clazz.newInstance(); 
		Method method= clazz.getDeclaredMethod("sout", null); 
		method.invoke(obj, null); 
		System.out.println(clazz.getClassLoader()); 
		System.out.println(); 
		MyClassLoader classLoader1 = new MyClassLoader("D:/test1"); 
		Class clazz1 = classLoader1.loadClass("com.tuling.jvm.User1"); 
		Object obj1 = clazz1.newInstance(); 
		Method method1= clazz1.getDeclaredMethod("sout", null); 
		method1.invoke(obj1, null); 
		System.out.println(clazz1.getClassLoader()); 
	} 
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值