您的位置:首页 > 编程语言 > Java开发

初探java虚拟机类加载机制(零)——概述

2017-10-12 23:17 225 查看
从网上看到的一题面试题开始

package com.xupk.jvm.demo01;

public class JVMDemo01 {

private static JVMDemo01 demo = new JVMDemo01();
public static int a;
public static int b = 0;

private JVMDemo01(){
a++;
b++;
}

public static JVMDemo01 getInstance() {
return demo;
}

}
package com.xupk.jvm.demo01.test;

import com.xupk.jvm.demo01.JVMDemo01;

public class JVMDemo01Test {
public static void main(String[] args) {
JVMDemo01 demo01 = JVMDemo01.getInstance();
System.out.println("a="+JVMDemo01.a);
System.out.println("b="+JVMDemo01.b);
}

}


        你们猜猜,输出会是什么呢?

        估计有很多人会跟我当初的答案一样:a=1    b=1

        很遗憾,这是错的,正确答案是:a=1    b=0

        不明所以?没关系,看完下面的java虚拟机类加载机制,你就能轻松解答出来

        虚拟机的类加载机制:虚拟机把描述类的数据,从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型

        类的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载。

        那各个阶段主要做了些什么呢?

加载:查找并获取二进制字节流,生成Class对象

        1)通过一个类的全限定名来获取定义此类的二进制字节流;

        2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;

        3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

         

验证:验证字节流

        验证的目的是为了确保加载阶段所加载的字节流信息符合虚拟机的要求,并且不会损坏虚拟机自身安全。它会完成4个阶段的验证工作:文件格式验证、元数据验证、字节码验证、符号引用验证。

准备:分配内存并赋初始值

        准备阶段的主要工作是:在方法区中,为类变量(被static修饰的变量)分配内存,并且设置类变量初始值。

        这里有几个要注意的地方,首先,分配内存是在方法区中进行的;然后,是为类变量分配,而不是实例变量;最后,赋初始值,说的是数据类型的零值,比如int型的是0,String类型的是null等,而不是程序代码里面设定的值,比如:static int a = 100; 由于a是被static修饰的类变量,所以在准备阶段会为a分配方法区的内存,并且会为a赋初始值0,而不是100;到了初始化阶段才会为a赋值100。

解析:符号转换

        主要是将常量池内的符号引用替换为直接引用。

初始化:执行静态初始化代码(块)

        主要是按顺序执行java类中的static变量赋值语句和static代码块。例如上面说的 static int a = 100;在准备阶段a的值是0,并没有真正执行这条语句;而到了初始化阶段,a的值才是100,所以这条语句的执行时在初始化阶段进行的。

注意:

1.加载-->验证-->准备-->初始化-->卸载,这几个阶段的开始顺序是固定的,也就是说,如果一个类要初始化,那必须要先经历加载、验证、准备这3个阶段。

2.类什么时候会初始化,有且只有这5种情况才会触发类的初始化:

1)new一个实例,或者操作一个static静态变量,或者调用static静态方法

2)初始化一个类时,其父类若没有初始化,则先触发父类的初始化

3)含有main方法的类会触发初始化

4)对类进行反射调用的时候会触发类的初始化

5)当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先触发其初始化

题目解析:

        有了上面的知识,再来看看开始的那道面试题,应该不难了

package com.xupk.jvm.demo01;

public class JVMDemo01 {

private static JVMDemo01 demo = new JVMDemo01();
public static int a;
public static int b = 0;

private JVMDemo01(){
a++;
b++;
}

public static JVMDemo01 getInstance() {
return demo;
}

}


        首先来看看入口的main方法

public static void main(String[] args) {
JVMDemo01 demo01 = JVMDemo01.getInstance();
System.out.println("a="+JVMDemo01.a);
System.out.println("b="+JVMDemo01.b);
}

        1.先执行的是JVMDemo01类调用getInstance()方法,由于调用的是static静态方法,结合上面的第2个注意点的第1小点,调用静态方法会触发一个类的初始化;而初始化前要对这个类进行加载、验证、准备等3个阶段;

        2.对类JVMDemo01进行加载、验证、准备;在准备阶段,会为类的静态变量赋初始值,即a=0,b=0;

        3.对类JVMDemo01初始化,这个阶段会按顺序执行类的静态变量声明语句和静态代码块:首先执行JVMDemo01的构造方法(a++;b++;),执行完这个方法后(a=1,b=1;);然后执行a和b的赋值语句,a没有赋值,所以a还是1;b赋值为0,所以b变成了0。

        所以最后的正确答案应该是:a=1   b=0

        你答对了没有?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: