一.加载
加载分为三个步骤:
1.使用类加载通过一个类的全限定名来获取定义此类的二进制字节流并使其加载到内存当中(并没有指明要从一个Class文件中获取,可以从其他渠道,譬如:网络、动态生成、数据库等);
类加载器:1.类加载器+类构成类的唯一性标志,比如instanceof equals()方法的使用结果,不同的类加载其加载同一个类结果依然是 false;
2.分类有三:(1)启动类加载器:负责将存放在\lib目录中的,或被-Xbootclasspath参数指定的路径中的,并且是虚拟机识别 的类库加载到虚拟机中。如,rt.jar。名字不符合即使放在目录中也不被加载。如果需要把加 载请求委派给引导类加载器,直接使用null代替即可。
(2)扩展类加载器:由sum.misc.Launcher$ExtClassLoader实现,负责加载<Java_Home>\lib\ext目录中的,或 者被java.ext.dir系统变量所指定的路径中的所有类库。开发者可以直接使用扩展类加载器。
(3)应用程序类加载器:由sun.misc.Launcher$App-ClassLoader实现。是ClassLoader中的 getSystemClassLoader()方法的返回值,所以也称为系统类加载器。负责加载用户路 径(ClassPath)上所指定的类库,如果应用程序中没有自定义过自己的类加载器,这个 就是默认的加载器,开发人员可以直接使用这个类加载器。
3.双亲委派模型:
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,具体数据结构为明确规定;
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口,在hotspot虚拟机中会被放在方法区中;
加载阶段和连接阶段(Linking)的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,但这些夹在加载阶段之中进行的动作,仍然属于连接阶段的内容,这两个阶段的开始时间仍然保持着固定的先后顺序。二.验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 验证阶段大致会完成4个阶段的检验动作:
- 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以魔术0xCAFEBABE开头、主次版本号是否在当 前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
- 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java 语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
- 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
- 符号引用验证:确保解析动作能正确执行。
实际上,需要验证的远远不止这一点,还有很多
三.准备
类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:
public static int value=123;
- 1
那变量value在准备阶段过后的初始值为0而不是123.因为这时候尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clinit>()方法之中,这个方法会在初始化中讲到,所以把value赋值为123的动作将在初始化阶段才会执行。 至于“特殊情况”是指:public static final int value=123,即当类字段的字段属性是ConstantValue时,会在准备阶段初始化为指定的值,所以标注为final之后,value的值在准备阶段初始化为123而非0.