使用类的准备任何程序都需要加载到内存中才能与CPU通信,bytecode.class文件也不例外。类只能在加载到内存中时被实例化。ClassLoader的任务是预先将.class类文件加载到内存中,加载类时使用ParentsDelegationModel(可追溯性委托模型)。Java的类加载器是一个运行时核心基础模块,主要在启动之初加载、链接和初始化类:Java类加载过程Load-loading是由类加载器完成的。读取class文件(通常在classpath指定的路径下查找,但classpath不是必须的),查找字节码,生成二进制流,转换成特定的数据结构。初步验证cafebabe幻数、常量池、文件长度、是否有父类等,然后创建对应类的java.lang.Class实例。Link——Link将一个类已经读入内存的二进制数据合并到JVM运行环境中。包括以下三个步骤:校验,保证加载类的正确性。验证类中的字节码是比较详细的验证,比如final是否合规,类型是否正确,静态变量是否合理。准备为类的静态字段分配内存,并设置初始默认值,并对类和方法进行分析,确保类间相互引用的正确性,完成内存结构布局分析,必要时,所有对其他类的引用该类创建的引用将被解析,常量池中的符号引用将被转换为直接引用。Init——初始化并执行类构造函数。如果是通过其他类的静态方法赋值,它会立即解析alternative,在JVM栈中通过执行后的返回值进行赋值。类加载是将.class字节码文件实例化为Class对象并进行相关初始化的过程。在这个过程中,JVM会初始化继承树上所有没有被初始化的父类,并会执行这个链接上所有未执行的静态代码块、静态变量赋值语句等。有些类在使用的时候也可以由类加载器按需加载。全小写的class是一个关键字,用来定义一个类,大写字母的Class是所有类的类。这句话很难理解。类已经是现实世界中某些事物的抽象。为什么这个抽象仍然不同?类类的对象?示例代码如下:第一条说明:Class类下的newInstance()在JDK9中已经被设置为obsolete。用getDeclaredConstructor().newInstance()来解释new和newInstance的区别。new是强类型验证,可以调用任何构造方法。使用new操作时,这个类可能还没有被加载,Class类下的newInstance是弱类型,只能调用无参构造方法。如果没有默认构造方法,则会抛出InstantiationException;如果此构造函数没有访问权限,则会抛出IllegalAccessException。Java通过类加载器将类的实现与其定义解耦,因此是面向接口编程和依赖倒置的必然选择。第二条语句可以用类似的方式获取其他声明,比如注解、方法等。第三条语句:私有成员可以在类外修改吗?通过setccessible(true),可以使用Class类的set方法修改它的值。如果没有这一步,会抛出如下异常:1加载“loading”的位置是“类加载”(ClassLoading)过程的第一步。1.1加载过程JVM主要做了以下几件事:通过类的完全限定名(保证全局唯一)获取类的二进制字节流(class文件)。当它被加载并满足类初始化的条件时,它根据要初始化的类的完全限定名找到该类的二进制字节流,并开始加载过程。将类加载阶段“通过类的全限定名获取类的二进制字节流”的动作交给虚拟机外的类加载器的好处是类加载器可以自己实现加载其他格式。类,只要是二进制字节流,就大大增强了加载器的灵活性。将这个字节流的静态存储结构转换成方法区的运行时数据结构,创建一个java.您可以调用getClass方法。程序运行过程中对类的所有访问都是通过这个类对象,即Class对象是提供给外界访问类的接口。一般是从编译好的本地类文件中读取二进制字节流。另外,可以从以下地方读取Jar、War、Ear等zip包,生成对应的JSP文件生成的Class类。数据库中的二进制字节流存储在数据库中,加载时再从数据库中读取。一些中间件会这样做实现集群间的代码分发网络从网络中获取二进制字节流,比如Applet运行时动态计算生成动态代理技术,使用PROxyGenerator.generateProxyClass生成代理类二进制字节流1.3类和数组加载过程的区别数组也有类型,称为“数组类型”,如:String[]str=newString[10];这个数组的数组类型是Ljava.lang.String,String只是这个数组的元素类型。当程序在运行过程中遇到new关键字创建数组时,JVM直接创建数组类,然后类加载器在数组中创建元素类型。普通类的加载是由类加载器创建的。它既可以使用系统提供的引导类加载器,也可以通过用户自定义的类加载器来完成(即改写类加载器的loadClass()方法)。1.4加载过程中的注意点加载方法区中存储的数据结构类后,二进制字节流以特定的数据结构存储在方法区中,但存储的数据结构是由虚拟机自己定义的,并且虚拟机规范没有指定JVM规范,也没有指定Class对象的存放位置。二进制字节流以特定格式存储到方法区后,JVM会创建一个java.lang.Class类的对象作为该类的对外访问接口。既然是对象,就应该存放在Java堆中。但是,JVM规范并没有给出限制。不同的虚拟机根据自己的需要存储这个对象。HotSpot将Class对象存储在方法区中。加载阶段和链接阶段是跨类加载过程中每一步的开始顺序。有严格的限制,但对每一步的结束顺序没有限制。也就是说,类加载过程中,必须按照如下顺序开始:Load->Link->Initialize但结束顺序无所谓,所以由于每一步的处理时间长短不同,会导致一些步骤cross2.验证验证阶段比较耗时。它很重要但不一定必须(因为它对程序运行时没有影响)。如果运行的代码已经被反复使用验证过,那么可以使用-Xverify:none参数关闭,以缩短类加载时间2.1验证的目的是保证二进制字节流中的信息符合虚拟机规范,不存在安全问题2.2验证的必要性虽然Java语言是一种安全的语言,它可以保证程序员不能访问超出数组边界的内存,避免将对象转换为任何类型,避免跳转到非-现有的代码行。也就是说,Java语言的安全性是由编译器来保证的。但是我们知道,编译器和虚拟机是两个独立的东西。虚拟机只识别二进制字节流,并不关心获取的二进制字节流来自哪里。当然,如果是编译器给它的,那么它是相对安全的,但是如果是通过其他方式获得的,那么就没办法保证二进制字节流是安全的。从上面可以看出,二进制字节流的来源在虚拟机规范中是没有限制的。在字节码层面,上述Java代码无法做到的事情是可以实现的,至少在语义上是这样。节流存在安全问题,需要验证!2.3验证过程文件格式验证验证字节流是否符合Class文件格式的规范,是否可以被当前虚拟机处理。该验证阶段基于二进制字节流。只有通过了这个阶段的验证,才允许存放到方法区。校验后的三个校验阶段都是基于方法区的存储结构,不会直接操作字节流。从上面可以看出,在加载开始之前,二进制字节流还没有进入方法区,而加载完成后,二进制字节流已经存放在方法区中了。在文件格式校验之前,二进制字节流还没有进入方法区。文件格式校验通过后,进入方法区。也就是说,加载开始后,会立即开始文件格式验证。本阶段校验通过后,将二进制字节流转换成特定的数据结构存放在方法区,然后开始下一阶段的校验和Class对象的创建。这个过程确认:加载和验证是交叉处理的元数据验证,对字节码描述的信息进行语义分析,确保符合Java语法规范。字节码验证验证过程中最复杂的阶段。该阶段对数据流和控制流(主要是方法体)进行语义分析。字节码验证会对类的方法进行验证和分析,确保验证后的方法在运行时不会做任何危害虚拟机的事情。如果一个类方法体的字节码没有通过字节码验证,那它肯定是有问题的,但是如果一个方法通过了验证,并不代表它就一定是安全的。符号引用验证发生在JVM将符号引用转换为直接引用时。这个转换动作发生在解析阶段,匹配和验证类本身以外的信息,以确保解析能够正常进行。
