深入Java虚拟机JVM类加载初始化学习

来源:互联网 发布:美国地缘政治知乎 编辑:程序博客网 时间:2024/06/11 20:57

1. Classloader的作用,概括来说就是将编译后的class装载、加载到机器内存中,为了以后的程序的执行提供前提条件。

2. 一段程序引发的思考:

风中叶老师在他的视频中给了我们一段程序,号称是世界上所有的Java程序员都会犯的错误。

诡异代码如下:

package test01;

class Singleton {

public static Singleton singleton = new Singleton();

public static int a;

public static int b = 0;

private Singleton() {

super();

a++;

b++;

}

public static Singleton GetInstence() {

return singleton;

}

}

public class MyTest {

/**

 * @param args

 */

public static void main(String[] args) {

Singleton mysingleton = Singleton.GetInstence();

System.out.println(mysingleton.a);

System.out.println(mysingleton.b);

}

}

一般不假思索的结论就是,a=1,b=1。给出的原因是:ab都是静态变量,在构造函数调用的时候已经对ab都加1了。答案就都是1。但是运行完后答案却是a=1,b=0

下面我们将代码稍微变一下

public static Singleton singleton = new Singleton();

public static int a;

public static int b = 0;

的代码部分替换成

public static int a;

public static int b = 0;

public static Singleton singleton = new Singleton();

效果就是刚才预期的a=1,b=1

为什么呢,这3句无非就是静态变量的声明、初始化,值的变化和声明的顺序还有关系吗?Java不是面向对象的吗?怎么和结构化的语言似地,顺序还有关系。这个就是和Java虚拟机JVM加载类的原理有着直接的关系。

3. 类在JVM中的工作原理

要想使用一个Java类为自己工作,必须经过以下几个过程

1):类加载load:从字节码二进制文件——.class文件将类加载到内存,从而达到类的从硬盘上到内存上的一个迁移,所有的程序必须加载到内存才能工作。将内存中的class放到运行时数据区的方法区内,之后在堆区建立一个java.lang.Class对象,用来封装方法区的数据结构。这个时候就体现出了万事万物皆对象了,干什么事情都得有个对象。就是到了最底层究竟是鸡生蛋,还是蛋生鸡呢?类加载的最终产物就是堆中的一个java.lang.Class对象。

2):连接:连接又分为以下小步骤

验证:出于安全性的考虑,验证内存中的字节码是否符合JVM的规范,类的结构规范、语义检查、字节码操作是否合法、这个是为了防止用户自己建立一个非法的XX.class文件就进行工作了,或者是JVM版本冲突的问题,比如在JDK6下面编译通过的class(其中包含注解特性的类),是不能在JDK1.4JVM下运行的。

准备:将类的静态变量进行分配内存空间、初始化默认值。(对象还没生成呢,所以这个时候没有实例变量什么事情)

解析:把类的符号引用转为直接引用(保留

3):类的初始化: 将类的静态变量赋予正确的初始值,这个初始值是开发者自己定义时赋予的初始值,而不是默认值。

4. 类的主动使用与被动使用

以下是视为主动使用一个类,其他情况均视为被动使用!

1):初学者最为常用的new一个类的实例对象(声明不叫主动使用)

2):对类的静态变量进行读取、赋值操作的。

3):直接调用类的静态方法。

4):反射调用一个类的方法。

5):初始化一个类的子类的时候,父类也相当于被程序主动调用了(如果调用子类的静态变量是从父类继承过来并没有复写的,那么也就相当于只用到了父类的东东,和子类无关,所以这个时候子类不需要进行类初始化)。

6):直接运行一个main函数入口的类。

所有的JVM实现(不同的厂商有不同的实现,有人就说IBM的实现比Sun的要好……)在首次主动调用类和接口的时候才会初始化他们。

5. 类的加载方式

1):本地编译好的class中直接加载

2):网络加载:java.net.URLClassLoader可以加载url指定的类

3):从jarzip等等压缩文件加载类,自动解析jar文件找到class文件去加载util

4):从java源代码文件动态编译成为class文件

6. 类加载器

JVM自带的默认加载器

1):根类加载器:bootstrap,由C++编写,所有Java程序无法获得。

2):扩展类加载器:由Java编写。

3):系统类、应用类加载器:由Java编写。

用户自定义的类加载器:java.lang.ClassLoader的子类,用户可以定制类的加载方式。每一个类都包含了加载他的ClassLoader的一个引用——getClass().getClassLoader()。如果返回的是null,证明加载他的ClassLoader是根加载器bootstrap

如下代码

public static void main(String[] args) throws ClassNotFoundException {

Class clazz = Class.forName("java.lang.String");

System.out.println(clazz.getClassLoader());

}

结果是null,证明java.lang.String是根类加载器去加载的。

public static void main(String[] args) {

Singleton mysingleton = Singleton.GetInstence();

System.out.println(mysingleton.getClass().getClassLoader());

}

结果是sun.misc.Launcher$AppClassLoader@19821f,证明是AppClassLoader(系统类、应用类加载器)去加载的。像jrert.jar下面的java.lang.*都是默认的根类加载器去加载这些运行时的类。

7. 解释类连接阶段的准备

类的如下代码片段

public static int a;

public static int b = 10;

在这个阶段,加载器会按照结构化似的,从上到下流程将静态变量int类型分配4个字节的空间,并且为其赋予默认值0,而像b = 10这段代码在此阶段是不起作用的,b仍然是默认值0

8. 解释类连接阶段的解析

这里面的指针就是C++的指针

9. 回顾那个诡异的代码

从入口开始看

Singleton mysingleton = Singleton.GetInstence();

是根据内部类的静态方法要一个Singleton实例。

这个时候就属于主动调用Singleton类了。

之后内存开始加载Singleton类

1):对Singleton的所有的静态变量分配空间,赋默认的值,所以在这个时候,singleton=nulla=0b=0。注意b0是默认值,并不是咱们手工为其赋予的的那个0值。

2):之后对静态变量赋值,这个时候的赋值就是我们在程序里手工初始化的那个值了。此时singleton = new Singleton();调用了构造方法。构造方法里面a=1b=1。之后接着顺序往下执行。

3):

public static int a;

public static int b = 0;

a没有赋值,保持原状a=1b被赋值了,b原先的1值被覆盖了,b=0。所以结果就是这么来的。类中的静态块static块也是顺序地从上到下执行的。

10. 编译时常量、非编译时常量的静态变量

如下代码

package test01;

class FinalStatic {

public static final int A = 4 + 4;

static {

System.out.println("如果执行了,证明类初始化了……");

}

}

public class MyTest03 {

/**

 * @param args

 */

public static void main(String[] args) {

System.out.println(FinalStatic.A);

}

}

结果是只打印出了8,证明类并没有初始化。反编译源码发现class里面的内容是

public static final int A = 8;

也就是说编译器很智能的、在编译的时候自己就能算出4+48,是一个固定的数字。没有什么未知的因素在里面。

将代码稍微改一下

public static final int A = 4 + new Random().nextInt(10);

这个时候静态块就执行了,证明类初始化了。在静态final变量在编译时不定的情况下。如果客户程序这个时候访问了该类的静态变量,那就会对类进行初始化,所以尽量静态final变量尽量没什么可变因素在里面1,否则性能会有所下降。

11. ClassLoader的剖析

ClassLoaderloadClass方法加载一个类不属于主动调用,不会导致类的初始化。如下代码块

ClassLoader classLoader = ClassLoader.getSystemClassLoader();

Class<?> clazz = classLoader.loadClass("test01.ClassDemo");

并不会让类加载器初始化test01.ClassDemo,因为这不属于主动调用此类。

ClassLoader的关系:

根加载器——》扩展类加载器——》应用类加载器——》用户自定义类加载器

加载类的过程是首先从根加载器开始加载、根加载器加载不了的,由扩展类加载器加载,再加载不了的有应用加载器加载,应用加载器如果还加载不了就由自定义的加载器(一定继承自java.lang. ClassLoader)加载、如果自定义的加载器还加载不了。而且下面已经没有再特殊的类加载器了,就会抛出ClassNotFoundException,表面上异常是类找不到,实际上是class加载失败,更不能创建该类的Class对象。

若一个类能在某一层类加载器成功加载,那么这一层的加载器称为定义类加载器。那么在这层类生成的Class引用返回下一层加载器叫做初始类加载器。因为加载成功后返回一个Class引用给它的服务对象——也就是调用它的类加载器。考虑到安全,父委托加载机制。

ClassLoader加载类的原代码如下

    protected synchronized Class<?> loadClass(String name, boolean resolve)

throws ClassNotFoundException

    {

// First, check if the class has already been loaded

Class c = findLoadedClass(name);

if (c == null) {

    try {

if (parent != null) {

    c = parent.loadClass(name, false);

else {

    c = findBootstrapClassOrNull(name);

}

    } catch (ClassNotFoundException e) {

                // ClassNotFoundException thrown if class not found

                // from the non-null parent class loader

            }

            if (c == null) {

        // If still not found, then invoke findClass in order

        // to find the class.

        c = findClass(name);

    }

}

if (resolve) {

    resolveClass(c);

}

return c;

    }

初始化系统ClassLoader代码如下

    private static synchronized void initSystemClassLoader() {

if (!sclSet) {

    if (scl != null)

throw new IllegalStateException("recursive invocation");

            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();

    if (l != null) {

Throwable oops = null;

scl = l.getClassLoader();

        try {

    PrivilegedExceptionAction a;

    a = new SystemClassLoaderAction(scl);

                    scl = (ClassLoader) AccessController.doPrivileged(a);

        } catch (PrivilegedActionException pae) {

    oops = pae.getCause();

            if (oops instanceof InvocationTargetException) {

        oops = oops.getCause();

    }

        }

if (oops != null) {

    if (oops instanceof Error) {

throw (Error) oops;

    } else {

        // wrap the exception

        throw new Error(oops);

    }

}

    }

    sclSet = true;

}

    }

它里面调用了很多native的方法,也就是通过JNI调用底层C++的代码。

12. 当一个类被加载、连接、初始化后,它的生命周期就开始了,当代表该类的Class对象不再被引用、即已经不可触及的时候,Class对象的生命周期结束。那么该类的方法区内的数据也会被卸载,从而结束该类的生命周期。一个类的生命周期取决于它Class对象的生命周期。由Java虚拟机自带的默认加载器(根加载器、扩展加载器、系统加载器)所加载的类在JVM生命周期中始终不被卸载。所以这些类的Class对象(我称其为实例的模板对象)始终能被触及!而由用户自定义的类加载器所加载的类会被卸载掉!


小结:其实这些应该是Java对象的生命周期,数据类型在内存中的情况。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 出车祸了报警警察不管怎么办 高中的孩子不好好上学怎么办 和老公消费观念不合拍怎么办 去医院没带现金怎么办 微信读书下架了怎么办 24岁血压有点高怎么办 吃鸡鼠标弹出来怎么办 电脑分辨率调错了怎么办 猎豹sc9打不开门怎么办 苹果手机卡顿反应慢怎么办 金立手机卡顿反应慢怎么办 20天宝宝黄疸219怎么办? 智慧树选修挂科怎么办 军人被纠察抓了怎么办 我家的小孩很凶怎么办 10岁儿童老挤眼怎么办? 脚趾长鸡眼好痛怎么办 月加班超过36小时怎么办 月经老提前怎么办才好 20岁例假不规律怎么办 农村的医保门诊封顶了怎么办 保研夏令营被拒怎么办 小学生去英国游学怎么办签证 医生说我湿气重怎么办 喉咙痛有来医生感冒喉咙痛怎么办 暴马丁香叶子卷怎么办 房产权40年以后怎么办 面试回答不了考官的问题怎么办 轻伤事故对方要钱太多我怎么办 退休后有房子住公积金怎么办 江苏副高评过不聘怎么办 抗环瓜氨酸肽抗体高怎么办 脚被磨破了好痛怎么办 脚磨破皮肿了怎么办 脚磨破皮发炎了怎么办 3个月婴儿流口水怎么办 beats耳机被偷了怎么办 beats X耳机丢了怎么办 头戴耳机戴着热怎么办 长时间戴耳机耳朵疼怎么办 手表秒针走得声音很大怎么办