在经过了前面的加载  和 连接分析之后,这一节我们进入重要的初始化分析过程:

一、认识初始化

初始化:这个似乎与上面的初始化为默认值有点矛盾,我们再看一遍:为累的静态变量赋予正确的初始值,上面是赋予默认值,这里是赋予正确的初始值,什么是正确的初始值,就是用户给赋予的值。我们来看一个例子

class Test{
private static int a = 1; }

我们知道,这个类加载好之后,a的值就是1,但实际是这样子的,类在加载的连接阶段,将a初始化为默认值0(int的默认值是0),然后在初始化阶段将a的值赋予为正确的初始值1. 我们看到最终a的值是等于1,但是实际的运行中是有一个将0赋予a的过程,这个过程放生在连接的准备阶段。类的初始化还有另外的一种形式,代码如下:

class Test{

private static int a ;

static{

a=1;

  }

}

这里强调一点,这个时候还是没有类的实例生成的,这点一定要注意!《深入java虚拟机第二版》里面有一个图阐述了对应的关系,如下:

静态变量的声明语句,以及静态代码块都被看做类的初始化语句,Java虚拟机会按照初始化语句在类文件中的先后顺序来依次执行它们。

二、初始化步骤

1.假如这个类还没有被加载和连接,那就先进行加载和连接。【加载和连接执行完成后不一定会执行初始化,要符合主动使用才会进行初始化,否则只会执行加载和连接】
2.假如类存在直接的父类,并且这个父类还没有被初始化,那就先初始化直接的父类。
3.假如类中存在初始化语句,那就依次执行这些初始化语句。

三、主动使用和被动使用【初始化时机】

Java程序对类的使用方式可分为2种,主动使用和被动使用。所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用时”才初始化他们。

主动使用的六种情况:

1.创建类的实例。

new Test();

2.访问某个类或接口的静态变量,或者对该静态变量赋值。

int b = Test.a;
Test.a = b;

3.调用类的静态方法

Test.doSomething();

4.反射

Class.forName(“com.mengdd.Test”);

5.初始化一个类的子类

class Parent{
}
class Child extends Parent{
public static int a = 3;
}
Child.a = 4;

6.Java虚拟机启动时被标明为启动类的类

java com.mengdd.Test

除了以上六种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化 (除了上述6种情况以外,都不会执行初始化,只会执行加载和连接)

这个时候我们再来看第一节学习中那段比较诡异的代码:

class Singleton{

    private static Singleton singleton=new Singleton();

    private static int counter1;

    private static int counter2 = 0;

    public Singleton() {

       counter1++;

       counter2++;

    }

    public static int getCounter1() {

       return counter1;

    }

    public static int getCounter2() {

       return counter2;

    }

    public static Singleton getInstance(){

       return singleton;

    }

}

public class ClassLoaderTest {

    @SuppressWarnings("static-access")

    public static void main(String[] args) {

       Singleton singleton=Singleton.getInstance();

       System.out.println("counter1:"+singleton.getCounter1());

       System.out.println("counter2:"+singleton.getCounter2());

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

    }

}

我们调用Singleton singleton=Singleton.getInstance();调用Singleton的静态方法,相当于主动使用了类Singleton,因此Singleton被初始化!
当我们看到显示的是:

private static Singleton singleton=new Singleton();

    private static int counter1;

    private static int counter2 = 0;

这样的时候,顺序执行,先赋予初始值,singleton为null,counter1为0,counter2为0,然后顺序对singleton赋予正确的值newSingleton(),执行构造函数,counter1增加变为1,然后counter2变为1,然后继续执行初始化,将counter2赋值为正确的值,将counter2修改为0,因此运行结果是1、0

反过来

private static int counter1;

    private static int counter2 = 0;

    private static Singleton singleton=new Singleton();

先赋予初始值counter1为0,counter为0,singleton为null,然后对counter2赋值为正确的值,counter2为0,然后对singleton执行初始化赋予正确的值new Singleton(),执行构造函数,counter1为1,counter2为1,因此执行结果是1、1

到此,是不是感觉前面的问题豁然开朗了呢?

参考资料:

圣思园张龙老师深入Java虚拟机系列

JVM学习四:JVM之类加载器之初始化分析的更多相关文章

  1. jvm系列(四):jvm知识点总结

    原文链接:http://www.cnblogs.com/ityouknow/p/6482464.html jvm 总体梳理 jvm体系总体分四大块: 类的加载机制 jvm内存结构 GC算法 垃圾回收 ...

  2. jvm学习006 jvm内存结构分配

    主要内容如下: JVM启动流程 JVM基本结构 内存模型 编译和解释运行的概念 一.JVM启动流程: JVM启动时,是由java命令/javaw命令来启动的. 二.JVM基本结构: JVM基本结构图: ...

  3. JVM学习笔记-JVM模型

    JVM学习笔记 == 标签(空格分隔): jvm 学习笔记全部来自于<深入理解java虚拟机>总结 jvm内存示意图 虚拟机栈(Java Virtual Machine Stacks): ...

  4. JVM学习三:JVM之类加载器之连接分析

    学习完类加载之加载篇后,让我们继续来看加载之连接,连接分为三个步骤:验证.准备和解析三步,我们将一一分析之. 连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去. 类加载完毕之后进入 ...

  5. JVM学习四:深入分析ClassLoader

    一.什么是ClassLoader? 大家都知道,当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程 ...

  6. JVM体系结构之二:类加载器之2:JVM 自定义的类加载器的实现和使用

    一.回顾一下jdk自带的类加载器: 1.java虚拟机自带的加载器     根类加载器(Bootstrap,c++实现)     扩展类加载器(Extension,java实现)     应用类加载器 ...

  7. 【JVM学习笔记】系统类加载器

    可以通过“java.system.class.loader"属性指定系统类加载器 默认情况下,该属性值为空: public class Test { public static void m ...

  8. 【JVM学习笔记】扩展类加载器

    扩展类加载器独有的特点,代码如下 public class Sample { } public class Test { static { System.out.println("Test ...

  9. JVM学习--(四)垃圾回收算法

    我们都知道java语言与C语言最大的区别就是内存自动回收,那么JVM是怎么控制内存回收的,这篇文章将介绍JVM垃圾回收的几种算法,从而了解内存回收的基本原理. stop the world 在介绍垃圾 ...

随机推荐

  1. golang中的检验hash

    1.对字符串进行hash 大家可以看一下, SHA1 Hashes Go by Example写道: The pattern for generating a hash is sha1.New(), ...

  2. QP(Quote-Printable) 编码

    QP(Quote-Printable)   方法,通常缩写为“Q”方法,其原理是把一个 8   bit   的字符用两个16进制数值表示,然后在前面加“=”.所以我们看到经过QP编码 后的文件通常是这 ...

  3. Hibernate 中一级缓存和快照区的理解

    刚刚开始的时候觉得这个快照区很难理解,在网上看了很多博客之后,开始明白了.我是结合 ADO.NET 理解的,在ADO.NET 中有一个类, 叫 SqlCommandBuilder,在我看来,他就是 A ...

  4. sublime text3 php开发必要的插件

    一.安装Sublime Text 3 官网 http://www.sublimetext.com/3 一定要选择ST3,而不是ST2,3比2好用,真的,后面你就知道了. 选择对应的版本安装.完事后,要 ...

  5. HDU4802_GPA

    水题. #include <iostream> #include <cstdio> #include <cstring> using namespace std; ...

  6. 【刷题】BZOJ 2555 SubString

    Description 懒得写背景了,给你一个字符串init,要求你支持两个操作 (1):在当前字符串的后面插入一个字符串 (2):询问字符串s在当前字符串中出现了几次?(作为连续子串) 你必须在线支 ...

  7. C++11线程使用总结

    std::thread 在 <thread> 头文件中声明,因此使用 std::thread 需包含 <thread> 头文件. <thread> 头文件摘要 &l ...

  8. Spring中使用要点集合

    1.InitializingBean和init-method方法 Spring的InitializingBean为bean提供了定义初始化方法的方式.InitializingBean是一个接口,它仅仅 ...

  9. BZOJ2216 [Poi2011]Lightning Conductor 【决策单调性dp】

    题目链接 BZOJ2216 题解 学过高中数学都应知道,我们要求\(p\)的极值,参变分离为 \[h_j + sqrt{|i - j|} - h_i \le p\] 实际上就是求\(h_j + sqr ...

  10. 百万级运维心得一:Mongodb和Redis数据不能放在同一个服务器

    百万级运维经验一:Mongodb和Redis数据不能放在同一个服务器 一开始时,为了省服务器,把Mongodb和Redis放在一个服务器上.网站每到高峰期都特别卡,还经常出现502.找了很久的原因,发 ...