JVM学习笔记:类加载、连接及初始化

类加载:

在JAVA代码中,类型的加载连接初始化都是在程序运行期间完成的。

类型:

指的是定义class,或interface或者枚举,并不涉及对象或实例。

为什么要在运行期间完成类型的加载,连接,初始化:

为了提供更灵活的使用方式,例如动态代理。

 

类型的加载:

最常见的情况的是把编译好的字节码文件从硬盘上加载到内存中。

 

类型的连接:

将类与类之间的关系处理好,并且对于字节码的相关处理也是在这一阶段完成的。

虚拟机处理的对象就是字节码。字节码有问题,虚拟机就会不执行。

另外,字节码虽然是大部分情况是编译器帮助产生的,但本身是可以被人为操纵的,因此有存在错误的可能,所以需要进行处理。

子步骤如下:

1、验证阶段:确保被加载的类的正确性。

2、准备阶段:为类的静态变量分配内存,并将其初始化为默认值。

为什么是静态变量?

类的静态变量或静态方法通常具有“全局”属性,在这时候类实例还没被创建。为了之后的创建类实例的过程使用静态变量,所以在这个时候初始化静态变量。静态变量在准备阶段的初始化赋初值是0或null。

public class Test{
    public static int a = 1;
}

在该类的加载中,在连接的准备阶段,JVM会为静态整型变量a开辟一段内存空间,并且赋的值是0 而不是1。1的赋值是在类型初始化这一阶段完成的。

3、解析阶段

把类的符号引用转化为直接引用:

符号引用:间接的符号引用

直接引用:指针的引用

例如:调用某方法a()为符号引用方式,而在直接引用则是将这个a()转为直接的指针引用关系。

 

类型的初始化:

这一阶段的主要工作是给初始化的静态变量赋初值

 

类加载器:

JAVA的每一个类型,最终的数据结构通过类加载器被纳入到JVM的管理的内存下。就是加载类的工具。

 

另外还有两个阶段:类的使用阶段类的卸载阶段

使用阶段:就是类在运行使用的过程

卸载阶段:类从内存中消除的过程

 

类的加载、连接、初始化过程中的细节

类的加载细节:

类的加载是指将.class文件加载至内存中,放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class对象(JVM规范并为说明这个对象放置在上面区域,hotspot放置在了方法区中),这个Class对象用于封装类在方法区内的数据结构。

(没错,反射使用的class对象就是这个class对象)

加载Class的方式:

1、通过类路径加载(文件系统加载)

2、通过网络下载.class文件

3、从zip,jar等归纳文件中加载.class文件

4、从专有的数据库中提取.class文件

5、将java源文件动态编译成.class文件(动态代理,运行时编译)

 

类的初始化细节

JAVA在类的初始化阶段,由于不同的使用方式分为两种

1、主动使用:

所有的JVM的实现必须要在每个类或者接口在JAVA程序首次主动使用才进行初始化

内涵:主动,只有程序主动调用才会进行初始化。首次,也就是类型的初始化只会调用一次

大概包含7种情况:

1、创建类的实例

2、访问某个接口或者类的静态变量或者修改静态变量的值

3、访问静态方法

4、反射

5、当初始化一个类的时候,如果发现其父类没有进行过初始化,则首先触发父类初始化

6、JAVA虚拟机启动时被标明为启动类的类(包含main方法的类)

7、从JDK1.7后提供的动态语言支持

 

总之把握一个原则:某个类在首次使用时才会被初始化

 

主动使用例子:

/**
 * -XX:+TraceClassLoading jvm参数 追踪类的加载的信息并打印
 * -XX:+<option> 表示开启option选项
 * -XX:-<option> 表示关闭option选项
 * -XX:<option> = <value> 表示将option的选项设置为value
 */
class SuperClass{
    public static String str1 = "SuperClass";
    static {
        System.out.println("初始化SuperClass");
    }
}

class SubClass extends SuperClass{
    public static String str2 = "SubClass";
    static {
        System.out.println("初始化SubClass");
    }
}

public class Test1 {
    public static void main(String[] args) {
        System.out.println(SubClass.str2);
    }
}

结果:

初始化SuperClass
初始化SubClass
SubClass

虽然调用的子类的静态字段,由于符合主动使用的第五种情况即“当初始化一个类的时候,如果发现其父类没有进行过初始化,则首先触发父类初始化”,所以先对父类进行初始化,执行父类的静态代码块,然后再初始化子类

 

利用JVM参数,观察类的加载

可以发现,最先加载的类都是java.lang.Object这个类

加载顺序是 父类-子类

 

2、被动使用:

除了上述7种,其他的使用都是被动使用,并不会导致类的初始化。而加载和连接是有可能的。

class SubClass2 {
    public final  static String str = "SubClass2";
    static {
        System.out.println("SubClass2 block");
    }
}


public class Test2 {
    public static void main(String[] args) {
        System.out.println(SubClass2.str);
    }
}

输出结果:

SubClass2

静态常量在编译时就会被放入调用类的常量池中,因此不会使用到定义常量的类,不符合主动使用的任一情况,因此不会触发类的初始化。

常量池中的常量与调用类没有任何关系了,你甚至可以在编译完后将调用类的.Class字节码文件删除,然后也可以输出该静态常量的值

当然,如果把final关键字去掉,就会主动调用SubClass,这种情况就变成了主动使用,因此会对该类进行初始化。

反编译之后的代码:

ldc助记符表示:将int,float或者string类型的常量值从常量池推至栈顶

 

JAVA虚拟机与程序的生命周期:

在如下几种情况下,JAVA虚拟机将结束生命周期:

1、显示调用System.exit()方法

2、程序正常执行结束(最常见)

3、程序在执行过程当中遇到错误或者异常导致异常终止

4、由于操作系统出现的错误导致的JVM终止

 

posted @ 2019-06-06 14:41  小钟233  阅读(560)  评论(0编辑  收藏  举报