(58)Java基础 --Java类加载机制

目录

一、类加载的原理

二、类加载时机

三、类加载器 ClassLoader

1、什么是类加载器

2、类加载器的分类

3、全盘负责委托机制

4、自定义类加载器(了解)

5、实现类的热部署(了解)


一、类加载的原理

当一个class文件被加载进内存时,在JVM中将形成一份描述该class文件结构的元信息对象Class,通过该对象可以获知class文件的结构信息:如构造器,字段,方法等。虚拟机把描述类的数据从class文件

加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

简而言之:class文件被虚拟机加载进内存生产Class对象的过程就是类加载机制。class文件-->Class对象的过程。

类加载机制的流程

当程序要使用到某个类时,如果该类还未被加载进内存中,则系统会通过加载,连接,初始化三个步骤来对该类进行初始化操作。

1加载:查找和导入class文件

      类加载是指将类的class文件(字节码文件)载入到内存中,并为之创建一个java.lang.Class对象,我们称之为字节码对象。

2连接:把类的二进制数据合并到Java运行环境JRE(Java Runtime Environment))中

      1>:验证:检测被加载的类是否有正确的内部结构.

      2>:准备:负责为类的static变量分配内存,并设置默认值.

      3>:解析:把类的二进制数据中的符号引用替换为直接引用(深入分析JVM).

(需要说明的是:这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中;并且这里所说的初始值“通常情况”是数据类型的

零值,例如:public static int value = 123;value在准备阶段过后的初始值为0而不是123,而给value赋值的指令将在初始化阶段才会被执行)

3初始化:将符号引用转成直接引用。在此阶段,JVM负责对类进行初始化,主要就是对static变量进行初始化

      类的初始化一个类包含以下几个步骤:

           1>:如果该类还未被加载和连接,则程序先加载并连接该类.

           2>:如果该类的直接父类还未被初始化,则先初始化其父类.

           3>:如果类中有初始化语句(静态代码块),则系统依次执行这些初始化语句

(扩展:符号引用:符号引用是一个字符串,它给出了被引用的内容的名字并且可能会包含一些其他关于这个被引用项的信息——这些信息必须足以唯一的识别一个类、字段、方法。这样,对于其他类的符号

引用必须给出类的全名。)

二、类加载时机

下列几种情况能够触发类的初始化:

1、使用new关键字实例化对象的时候

2、操作类的静态字段的时候(被final修饰、已在编译期把结果放入常量池的静态字段除外)

3、调用类的静态方法的时候。

4、初始化子类的时候,先初始化其父类。

5、用户指定运行一个主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

6、直接使用java.exe命令来运行某个主类

7、使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

三、类加载器 ClassLoader

1、什么是类加载器

类的加载过程(也就是加载.class文件)由类加载器(ClassLoader)完成;

也就是说ClassLoader是用来动态的加载class文件到JVM中并转换成java.lang.class类的一个实例。

注意:

class文件可以是本地class文件,jar包中的class文件,来自网络的class文件等

程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制来动态加载某个class文件到内存中。

类加载器通常有JVM提供,当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构。当然我们也通过自定义类加载器,来指定一个类的加载过程。

2、类加载器的分类

1、Bootstrap ClassLoader 根类加载器/引导类加载器

负责Java核心类的加载

jdk安装目录\jre\lib目录下rt.jar文件中

比如System,String等。

(这个加载器的是非常特殊的,它实际上不是ClassLoader的子类,而是由C/C++实现的。)

 

2、Extension ClassLoader 扩展类加载器

负责JRE的扩展目录中jar包的加载。

jdk安装目录\jre\lib\ext目录下

 

3、System ClassLoader 系统类加载器/应用类加载器

负责在JVM启动时加载我们自己写的Java类编译成的class文件和导入的jar包中的class文件

可以通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。

import java.net.URL;
import org.junit.Test;
import sun.net.spi.nameservice.dns.DNSNameService;

public class ClassLoaderDemo {
	
	public static void test1() throws Exception {
		// 加载自己创建的类
		ClassLoader loader1 = ClassLoaderDemo.class.getClassLoader();
		// sun.misc.Launcher$AppClassLoader@63b5e16d
		System.out.println(loader1);

		// 加载ext里面内容
		ClassLoader loader2 = DNSNameService.class.getClassLoader();
		// sun.misc.Launcher$ExtClassLoader@63b5e16d
		System.out.println(loader2);

		// 加载rt.jar里面内容
		ClassLoader loader3 = String.class.getClassLoader();
		// null
		System.out.println(loader3);

		// 查看bootstrap classloader加载了那些核心类库
		URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
		for (int i = 0; i < urls.length; i++) {
			System.out.println(urls[i].toExternalForm());
		}
	}
}

JRE中并不是所有类,在自己定义的类中都可以直接加载。例如在加载ext目录下的jar包中的类 就不能直接使用。

3、全盘负责委托机制

上面学习的三个类加载器没有继承关系,只有加载顺序的关系:

引导类加载器 > 扩展类加载器 > 应用类加载器

 

那么我们自己创建的一个类是如何加载的?是不是就是按照顺序加载的呢?

首先使用引导类加载器,但是看到不是自己做的事情,向下给扩展类加载器,但是扩展类加载器一看,也不是自己做的事情,继续向下给应用类加载器。

这个过程其实就是:全盘委托机制

 

1、全盘负责:

即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的其它Class通常也由这个classloader负责载入。

比如:A类如果要使用B类(不存在),A类加载器必须负责加载B类。

 

2、委托机制:

需要查找类或资源时,ClassLoader实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。先让parent(父)类加载器 寻找,只有在parent找不到的时候才从自己的类路径中去

寻找。

也就是说一个类如果加载了,将直接使用。如果没有加载就先去加载再使用。

比如:A类加载器如果要加载资源B,必须询问父类加载是否加载。

 

3 cache(缓存):

类加载还采用了cache机制:如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么修改了Class但是必须重新启动JVM才能生效,并且类只加

载一次的原因。

注意:

      全盘负责委托机制保证了一个class文件只会被加载一次,形成一个Class对象。

      如果一个class文件,被两个类加载器加载,将是两个对象。

      (自定义类加载,可以将一个class文件加载多次)

4、自定义类加载器(了解)

通过自定义类加载器,我们可以自定义一个类的加载过程。

步骤:

1、编写一个类继承ClassLoader

2、重写findClass方法

3、在findClass方法中调用父类的defineClass方法

原理:

将.class文件通过流读取到,得到一个byte[]数组。通过defineClass方法直接生成Class对象

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader {
	private String rootDir;
	public MyClassLoader(String rootDir) { 
		this.rootDir = rootDir;
	}
	//参数是类的全名
	@Override
	public Class<?> findClass(String name) throws ClassNotFoundException {
		String extname = name.replace(".", "\\");  
		String filename = rootDir + "\\" + extname + ".class"; 
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		try {
			InputStream is = new FileInputStream(filename);
			int len = -1;
			byte[] b = new byte[1024];
			while ((len = is.read(b)) != -1) {
				baos.write(b, 0, len);
			}
			baos.flush();
			baos.close();
			is.close();
			byte[] data = baos.toByteArray();
			return super.defineClass(name, data, 0, data.length);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}

5、实现类的热部署(了解)

什么是类的热部署:

所谓热部署,就是在应用正在运行的时候升级软件,不需要重新启用应用。

对于Java应用程序来说,热部署就是运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程中,类加载器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,

都支持热部署。

类加载器不能重新载入一个已经载入的类,但只要使用一个新的类加载器实例,就可以将类再次装入一个正在运行的应用程序。

如何实现Java类的热部署

  前面的分析,我们已经知道,JVM在加载类之前会检查请求的类是否已经被加载过来,也就是要调用findLoadedClass方法查看是否能够返回类实例。如果类已经加载过来,再调用loadClass会导致类冲突。

但是,JVM判断一个类是否是同一个类有两个条件:一是看这个类的完整类名是否一样(包括包名),二是看加载这个类的ClassLoader加载器对象是否是同一个

所以,要实现类的热部署可以创建不同的ClassLoader的实例对象,然后通过这个不同的实例对象来加载同名的类。

上一篇:(57)Java基础 --多线程 --线程的生命周期

下一篇:(59)Java基础 --反射-reflect

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值