Java架构师必备技能之类加载机制

Owen Jia 2021年03月01日 558次浏览

对于架构师很多做技术的人都十分向往,也默默超这个方向去努力,在技术这条线架构师已经是处在顶尖一群人。

我们该如何成为架构师呢?除了机缘,技术基本功是必须够硬的。

owen jia

架构师一定要在一个语言体系中成长起来,特别是前期阶段要专注一个语言体系深入细节,打磨基本功。对于Java架构师是一定要具备构建工具的能力,在这之上就是构建框架能力,这些能力的基础就是jvm的类加载机制

在jdk1.8基础上来聊“虚拟机类加载机制”。

一般我们在idea中编写*.java文件,以project方式大家这些java文件组织关系,在用maven这样工具build成class文件,大多情况下这些*.class文件封装在jar包里面,最终有jvm来加载这些jar也就是class文件。类加载就是jvm解析class这些字节码文件的过程,这是核心,包括在java文件中的逻辑关系、对象、还有内存。

类加载

一个类在jvm中分为7个阶段,分别是“加载、验证、准备、解析、初始化、使用、卸载”,其中“验证、准备、解析”又称为连接,7个阶段的顺序是固定的,必须按部就班进行,这就是规范。

加载

这个阶段jvm主要完成三件事:

  • 通过一个类的全路径名获取此类的二进制字节流。就是按位置读取class文件。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口。

验证

确保class文件的字节流中包含的信息符合《java虚拟机规范》的全部约束,保证代码运行后不会危害虚拟机的安全。先验证class文件的格式规范,再对字节码描述信息进行语义分析,之后进一步进行数据流分析和控制流分析,最后是符合引用转化直接引用(该类是否被禁止访问它依赖的某些外部类等等)。

准备

正式为类中的静态(static修饰)变量分配内存并设置类变量的初始值,一般都放在虚拟机方法区内。

解析

将常量池内的符号引用替换为直接引用,主要是“类、接口、字段、类方法、接口方法、方法类型、方法句柄、调用电限定符”。

这里说明一下“符号引用”是啥?class在前面几步中不是直接的内存引用,而是一些特征性字符串代替的,如“CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info”等等,解析的过程就是替换指针的过程。这个过程还存在一些检测,比如“java.lang.NoSuchMethodError”就是这里报出的。

初始化

前面阶段都是jvm自己主导,自定义类加载器可以局部参与。到了初始化阶段就是开始执行类中编写的java程序代码,这个时候是应用程序开始主导,这个时候按编码逻辑开始初始化变量和其他资源。

直接些说这个初始化过程就是执行类构造器<clinit>()方法的过程,而这个类构造器不是自己编写的类构造器,是由javac编译器自动生成的。

类加载器

类加载器就是“通过一个类的全限定名来获取描述该类的二进制字节流”的代码,这块代码在jdk中。

比较两个类是否“相等”,只有在这两个类由同一个类加载器加载的前提下才有意义,否则它们必定不相等。

三层类加载器

  • Bootstrap Class Loader:启动类加载器,负责加载存放在<java_home>\lib目录,或者-Xbootclasspath参数指定路径的jar,并且是虚拟机可识别的jar(白名单)。这个启动加载器是顶级加载器,应用不可控,只能通过委托方式给它加载。
  • Extension Class Loader:扩展类加载器,加载<java_home>\lib\ext目录,或者java.ext.dirs参数指定路径的jar。
  • Application Class Loader:应用程序类加载器,加载应用classpath上所有jar,如何没有自定义默认是这个类在加载class文件。

双亲委派模型

双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有自己的父类加载器,它不是通过extends方式控制,而是写在代码中通过判断逻辑实现的。

双亲委派的整个过程:如果一个类加载器收到类加载的请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去加载完成,每个层次的加载器都是如此,所以所有加载器的加载请求最终都会传送到最顶层(启动类)加载器中,只有当父类加载器表示无法完成这个请求是(返回null),子加载器才会自己尝试完成加载。

java.lang.ClassLoader的loadClass()方法代码:

protected sychronized Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException{
	Class c = findLoadedClass(name);
	if(c==null){
		try{
			if(parent != null){
				c = parent.loadClass(name,false);
			} else {
				c = findBootstrapClassOrNull(name);
			}
		} catch(ClassNotFoundException e){
			//...
		}
		if(c==null){
			c = findClass(name);
		}
	}
	if(resolve){
		resolveClass(c);
	}
	return c;
}

技术之路永无止尽