今天你'累'了吗?此处累通Class类。
写这篇文章之前想了好多许多命题,比如分布式事务 , Redis存储,代码优化 , JAVA虚拟机等等 , 许多命题我知道可能大家也知道 , 比我可能了解的更深,因此就不在这里献丑了 , 这次挑了JAVA虚拟机的一个分支来讲 , 从开始学习JAVA到现在我们一直都在编写各种类,但是你真的对类都了解很深吗 ? 所以我认为大家有必要深入了解类的文件格式,类的分类 , 类的编译过程 , 类的加载 , 存储 , 以及卸载 。 下文详述了这些内容 , 让大家更清晰的了解一下类,不知众位仙家还满意吗 ?
每天都在编写代码,编写各种需要的类 , 但是你真的了解类吗 ? 下面会讲解一下类的文件格式,类的种类以及类与类之间的关系。
Java文件被编译后生成了Class文件,这种二进制格式文件不依赖于特定的硬件和操作系统。每一个Class文件中都对应着唯一一个类或者接口的的定义信息,但是类或者接口并不一定定义在文件中,比如类和接口可以通过类加载器来直接生成。
ClassFile的文件结构如下所示。
ClassFile { u4 magic; //魔数,固定值为0xCAFEBABE,用来判断当前文件是能被Java虚拟机处理的Class文件 u2 minor_version; //副版本号 u2 major_version; //主版本号 u2 constant_pool_count; //常量池计数器 cp_info constant_pool[constant_pool_count-1]; //常量池 u2 access_flags; //类和接口层次的访问标志 u2 this_class; //类索引 u2 super_class; //父类索引 u2 interfaces_count; //接口计数器 u2 interfaces[interfaces_count]; //接口表 u2 fields_count; //字段计数器 field_info fields[fields_count]; //字段表 u2 methods_count; //方法计数器 method_info methods[methods_count]; //方法表 u2 attributes_count; //属性计数器 attribute_info attributes[attributes_count]; //属性表 }
Java文件的种类主要有4大种类 , 如下图所示 :
在设计模式中类与类之间的关系主要有5种:依赖(Dependency)、关联(Association)、聚合(Aggregation)、组合(Composition)、继承(Inheritance),它们之间的耦合度依次增加。
单向,表示一个类依赖于另一个类的定义,其中一个类的变化将影响另外一个类,是一种“use a”的关系。如果A依赖于B,则B表现为A的局部变量,方法参数,静态方法调用等等。
单向或双向(通常我们需要避免使用双向关联关系),是一种"has a"关系,如果A单向关联B,则可以说A has a B,通常表现为全局变量。
是关联关系的一种,与关联关系之间的区别是语义上的,关联的两个对象通常是平等的,聚合则一般不平等,有一种整体和局部的感觉,实现上区别不大 , 整体和另一个整体是平等关系 , 一个消亡不会影响另一个。
是一种强依赖的特殊聚合关系 , 由部分组成整体 , 如果整体生命周期没了 , 部分也将消亡。
类继承抽象类,类继承父类,对应的是extends关键字;类实现接口类,对应的implements 关键字,都是最强的依赖!
上面知道了类 , 那么我们是如何编译类的,类又是如何进行加载的,加载的过程中又用到了哪些加载器,加载又有哪些场景以及什么场景不加载类?可能大家被我一连串的问号问的有些蒙,刚开始我是很蒙的,通过深入的了解才弄懂,众仙家听我慢慢讲来。
刚开始只知道代码的大体执行过程是这样的
可能有的人不知道从java源文件编译生成Class字节码文件的过程是怎样的 ? 流程如下图:
Class字节码文件已经生成了,那么是如何在JVM里执行的呢 ? 流程图如下
图中说的类装载子系统就是下面将要说的
类加载器子系统通过多种类加载器来查找和加载Class文件到 Java 虚拟机中。Java虚拟机有两种类加载器:系统加载器和用户自定义加载器。其中系统加载器包括以下三种 :
引导类加载器是用C/C+ +代码实现的加载器,用以加载Java虚拟机运行时所需要的系统类,这些系统类在{JRE_HOME}/lib目录下。Java虚拟机的启动就是通过引导类加载器创建一个初始类来完成的。由于类加载器是使用平台相关的底层C/C+ +语言实现的, 所以该加载器不能被Java代码访问到。但是,我们可以查询某个类是否被引导类加载器加载过。引导类装载器并不继承java.lang.ClassLoader。
扩展类加载器是用于加载 Java 的拓展类 ,拓展类一般会放在 {JRE_HOME}/lib/ext/ 目录下,用来提供除了系统类之外的额外功能。
该类加载器是用于加载用户代码,是用户代码的入口。应用类加载器将拓展类加载器当成自己的父类加载器,当尝试加载类的时候,首先尝试让拓展类加载器加载,如果拓展类加载器加载成功,则直接返回加载结果Class instance,如果加载失败,则会询问引导类加载器是否已经加载了该类,如果没有,应用类加载器才会尝试自己加载。
用户自定义加载器是通过继承 java.lang.ClassLoader类的方式来实现自己的类加载器。类加载器子系统除了要加载Class文件类到 Java 虚拟机中,还必须负责验证被导入的Class类的正确性,为类变量分配并初始化内存,以及帮助解析符号引用
那么加载子系统是如何加载类的呢,如图
1) 创建对象实例:new 对象的时候,会对类进行初始化(前提是这个类没有被初始化);
2) 通过class文件反射创建对象;
3) 调用类的静态属性,静态方法或静态属性赋值;
4) 初始化一个类的子类的时候,在使用子类的时候,先初始化父类;
5) Java虚拟机启动时被标记为启动类的的类,比如main所在的类;
1) 在同一个虚拟机中,一个类只能被加载一次,如果已经被初始化的一个类不会再被加载;
2) 在编译时,能确定下来的静态变量,不会对类进行初始化;
既然了解了类的加载过程,那是否也应该知道卸载过程呢?我帮大家补一下知识点
由Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的。但是由用户自定义的类加载器加载的类是可以被卸载的。
例子
loader1变量和obj变量间接应用代表Sample类的Class对象,而objClass变量则直接引用它。
如果程序运行过程中,将上图左侧三个引用变量都置为null,此时Sample对象结束生命周期,MyClassLoader对象结束生命周期,代表Sample类的Class对象也结束生命周期,Sample类在方法区内的二进制数据被卸载。
当再次有需要时,会检查Sample类的Class对象是否存在,如果存在会直接使用,不再重新加载;如果不存在Sample类会被重新加载,在Java虚拟机的堆区会生成一个新的代表Sample类的Class实例(可以通过哈希码查看是否是同一个实例)。
由此可见
被开发者自定义的类加载器实例加载的类型只有在很简单的上下文环境中才能被卸载,而且一般还要借助于强制调用虚拟机的垃圾收集功能才可以做到 。 可以预想,稍微复杂点的应用场景中(尤其很多时候,用户在开发自定义类加载器实例的时候采用缓存的策略以提高系统性能),被加载的类型在运行期间也是几乎不太可能被卸载的(至少卸载的时间是不确定的)
很高兴众仙家能认真阅读我的文章,在我深入了解类的种类,加载,卸载过程,对自己所写的代码有了更深入的了解,写这篇文章也把我所知道的有关类的知识分享给大家,给大家查缺补漏,如有不足的地方还希望大家指出,我会耐心听取意见。
本篇文章参考了一些其他文章,感谢众仙家耐心阅读,我在这里由衷的感谢!!!
《深入理解Java虚拟机 第二版》
《Java虚拟机规范(Java SE7版)》
https://www.ibm.com/developerworks/cn/java/j-lo-classloader
https://www.jianshu.com/p/eda2cbe51961
https://www.cnblogs.com/blueskyli/p/8589870.html