`
足至迹留
  • 浏览: 494739 次
  • 性别: Icon_minigender_1
  • 来自: OnePiece
社区版块
存档分类
最新评论

静态变量初始化 && 类实例初始化

阅读更多
【本文整合多篇网文+验证扩展而成】

1. 静态变量(块)初始化
1.1 大概了解Java虚拟机初始化的原理
JVM通过加装、连接和初始化一个Java类,使该类可以被正在运行的Java程序所使用。装载和连接必须在初始化之前就要完成。Java类型的生命周期:



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


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

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

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

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

3):类的初始化(不是对象实例的初始化): 将类的静态变量赋予正确的初始值,这个初始值是开发者自己定义时赋予的初始值,而不是默认值(这是跟准备阶段的区别)。

类初始化阶段,主要是为类变量(注意是静态变量,如果静态变量初始化时需要间接初始化其他类实例则转而去初始化依赖的变量,也就是<cinit>阶段遇到<init>就先执行完<init>再继续执行<cinit>)赋予正确的初始值。这里的“正确”初始值指的是程序员希望这个类变量所具备的起始值。一个正确的初始值是通过类变量初始化语句或者静态初始化语句给出的。初始化一个类包含两个步骤:
1) 如果类存在直接超类的话,且直接超类还没有被初始化,就先初始化直接超类。
2) 如果类存在一个类初始化方法,就执行此方法。

那什么时候类会进行初始化呢?Java 虚拟机规范为类的初始化时机做了严格定义:在首次主动使用时初始化。
那哪些情形才符合首次主动使用的标准呢?Java虚拟机规范对此作出了说明,他们分别是:
1) 创建类的新实例;
2) 调用类的静态方法;
3) 操作类或接口的静态字段(final字段除外);
4) 调用Java的特定的反射方法;
5) 初始化一个类的子类,初始化一个类的子类的时候,父类也相当于被程序主动调用了(如果调用子类的静态变量是从父类继承过来并没有复写的,那么也就相当于只用到了父类的东东,和子类无关,所以这个时候子类不需要进行类初始化)
6) 指定一个类作为Java虚拟机启动时的初始化类,也就是直接运行一个main函数入口的类。
除了以上六种情形以外,所有其它的方式都是被动使用的,不会导致类的初始化。
注意:这里是类的初始化,不是对象实例初始化,只涉及静态变量的初始化,所以不会主动调用构造函数,除非<cinit>中调用了<init>。请参看本文示例【练习2】。

<init>介绍:
Java编译器为它编译的每个类都至少生成一个实例初始化方法,即<init>()方法。一个<init>()方法内包括的代码内容可能有三种:调用另一个<init>() 方法;对实例变量初始化;构造方法体的代码。(注意,实例变量初始化和非静态初始化块的代码优先构造体先执行)。


1.2 静态成员变量会被jvm自动转成静态块
public class Person{
  public static String name="张三";
  public static int age;
  static{
       age=20;
    System.out.println("初始化age");
  }
  public static String address;
  static{
    address="北京市";
    age=34;
  }
  public static void main(String[] args) {
                   System.out.println(name);
                   System.out.println(age);
                   System.out.println(address);
         }
}


当java源代码转换成一个class文件后,其转换成类似下面的代码:
public class Person{
  public static String name;
  public static int age;
  public static String address;
  static{
    name="张三";
    age=20;
    System.out.println("初始化age");
    address="北京市";
    age=34;
  }
  public static void main(String[] args) {
                   System.out.println(name);
                   System.out.println(age);
                   System.out.println(address);
         }
}


进一步说,static变量的声明和初始化是分开的,即使是写成一行,编译时也会分成两步。如果先初始化,后声明也没关系,比如:
class **
{
    static
    {
       _aa = 2;
    }

    static int _aa = 3;
}
编译后就是:
class **
{
    static int _aa

    static
    {
       _aa = 2;
       _aa = 3;
    }
}

1.3 静态变量(静态块)初始化线程安全
前面说过,如果静态变量声明时就初始化,则编译后会自动添加一个static块。
JVM 保证同一个 ClassLoader 加载的类中的静态块只被执行一次,如果有其他线程也会触发静态初始化,就会block,等待正在进行的初始化。因此在static块中初始化是线程安全的。也正因为这一点,很多时候我们可以利用 static initialization block 执行一些初始化(write)操作,而无需对该 block 使用任何同步机制。 但这里只是说的初始化过程,初始化之后的使用,还有通过static method做的懒加载就不保证线程安全了,需要自己保证。
下面的例子不推荐:
class Employee{ 
    private Employee(){}
    private static Employee emp;
    static {    
        if (emp==null){
        emp=new Employee(); 
    }  
 
        }   
    public static Employee getEmployee(){  
        return emp; 
        }
}
这里的if (emp==null)是多余的,而且造成误解,会让人考虑是否需要同步。这里的emp一定是null的。而且跟下面的写法是一样的:
class Employee{ 
    private Employee(){}
   
private static Employee emp = new Employee(); 
 
    public static Employee getEmployee(){  
        return emp; 
        }
}

1.3.1 多线程写静态变量实例
包含静态块:
public enum Test
{
    public static ConcurrentHashMap<String, Integer> messageTypeMap = null;

    static {
        System.out.println("static init begin");
        messageTypeMap = new ConcurrentHashMap<String, Integer>();
        messageTypeMap.put("a",1);

        try
        {
            Thread.sleep(1000 * 10);
        }
        catch (Exception e)
        {

        }

        System.out.println("static init end");

    }
}

多线程:
public class TT implements Runnable
{
    int index;

    public TT(int index)
    {
        this.index = index;
    }
    @Override
    public void run()
    {
        System.out.println("TT run:" + Thread.currentThread().getName());
        Test.messageTypeMap.put("TT" + index, 1);
        //Test.messageTypeMap.get("aa"); // get也是一样
        System.out.println("TT run:" + Thread.currentThread().getName() + "mapSize=" + Test.messageTypeMap.size());

    }

    public static void main(String[] args)
    {
        for (int i = 0; i < 10; i++)
        {
            (new Thread(new TT(i))).start();
        }
    }
}

结果的关键是static init begin和static init end之间不会有任何Test.messageTypeMap.put("TT" + index, 1);执行,一直等待static block执行完才开始执行。

还可以参考:
http://stackoverflow.com/questions/878577/are-java-static-initializers-thread-safe?rq=1

关于final变量的初始化安全性可以参考:
http://zoroeye.iteye.com/blog/2058889  1.3节
http://zoroeye.iteye.com/blog/2026866 2.4.1节

2. 类实例初始化(对象的生成)
对于对象的生成其初始化过程与类的初始化过程类似,但会增加构造函数阶段,源代码如下:
public class Person{
    {
      name="李四";
      age=56;
      System.out.println("初始化age");
      address="上海";
    }
    public String name="张三";
    public int age=29;
    public String address="北京市";
    public Person(){
      name="赵六";
      age=23;
      address="上海市";
    }
}

编译器转换成class文件后,会转换成类似下面的代码:
public class Person{
   public String name;
   public int age;
   public String address;
   public Person(){
     name="李四";
     age=56;
     System.out.println("初始化age");
     address="上海";
     name="张三";
     age=29;
     address="北京市";
     name="赵六";
     age=23;
     address="上海市";
   }
}

可以看到,对于类中对成员变量的初始化和代码块中的代码全部都挪到了构造函数中,并且是按照java源文件的初始化顺序依次对成员变量进行初始化的,而原构造函数中的代码则移到了构造函数的最后执行。

3.总结
3.1 无继承关系的类初始化
对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序依次是(静态变量、静态初始化块) >(变量、初始化块) > 构造器

3.2 有继承关系类初始化
(1)加载父类(以下序号相同,表明初始化是按代码从上到下的顺序来的)
1.为父类的静态属性分配空间并赋于初值
1.执行父类静态初始化块;

(2)加载子类
2.为子类的静态属性分配空间并赋于初值
2.执行子类的静态的内容;

(3)加载父类构造器
3.初始化父类的非静态属性并赋于初值
3.执行父类的非静态代码块;
4.执行父类的构造方法;

(4)加载子类构造器
5.初始化子类的非静态属性并赋于初值
5.执行子类的非静态代码块;
6.执行子类的构造方法. 

总之一句话,静态代码块内容先执行(父先后子),接着执行父类非静态代码块和构造方法,然后执行子类非静态代码块和构造方法。
静态变量和静态初始化块谁在前面谁先执行。

练习1:
public class ClassInit {
	public static String dd = "ClassInit static field.";
	static
	{
		System.out.println(dd);
	}
    public static void main(String[] args) {  
        SuperInitField p = new SuperInitField();  
        SuperInitField c = new SubInitField();  
        //SuperInitField p = new SuperInitField();  
    }  
}  
 
class SuperInitField {  
    public SuperInitField() {  
        System.out.println("parent");  
    }  
    static {  
        System.out.println("static parent");  
    }  
 
}  
 
class SubInitField extends SuperInitField {  
    public SubInitField() {  
        System.out.println("child");  
    }  
    static {  
        System.out.println("static child");  
    }  
}
输出:
ClassInit static field.
static parent
parent
static child
parent
child

分析:
(1) Jvm先要找到包含main的类ClassInit,然后初始化静态成员或静态块,输出第一行(测试一下,如果把static块变成非static块,则不会执行)。
(2) 然后执行main方法,先创建SuperInitField实例,还是按照先链接阶段初始化静态成员或静态块,再初始化实例变量的顺序再构造函数的顺序。
(3) 创建SubInitField实例时先加载父类,父类静态变量已经初始化所以不再加载,接着加载子类,先加载静态变量。然后运行父类的构造函数,再执行子类构造函数。所有的类都加载链接完再按特定顺序初始化。
(4) 如果main方法改为:
public static void main(String[] args) { 
        //SuperInitField p = new SuperInitField(); 
        SuperInitField c = new SubInitField(); 
        SuperInitField p = new SuperInitField(); 
    } 
则输出:
ClassInit static field.
static parent
static child
parent
child
parent

练习2:
public class InitTest
{
	public InitTest(){  
        System.out.println("parent");  
    }  
    static{  
        System.out.println("static parent");  
    }  
    public static void main(String[] args) {  
        System.out.println("main");  
    } 
}
输出:
static parent
main

分析:因为jvm要执行main方法,先要加载InitTest类,首先初始化静态变量或静态块,然后执行main方法。因为没有显式或隐式创建InitTest实例,所以构造函数不需要执行。

练习3:
在类的内部,变量定义的先后顺序决定了初始化的顺序。即使变量定义散布于方法定义之间,它们仍旧在任何方法(包括构造器)被调用之前得到初始化。可以参考:http://zhangjunhd.blog.51cto.com/113473/20927/

【补充:】
1.对于final修饰的static字段处理会跟单独的static修饰不一样,final修饰的会被jvm当做常量在编译阶段就初始化了,类加载时直接能看到分配的值,但这仅限于String和原始类型,Object类型仍然不会编译时就初始化。可以通过javap -verbose classFile 查看到带final和不带final的区别。

以上参考
1.http://developer.51cto.com/art/201205/337932.htm
2.http://wenku.baidu.com/link?url=NdUKiyOQJimng03U13ZafTDnGGjPETbWaK7CUw-Lzsi7SepCbrDxJVIkwFPSwFqB1I2zie3DXyG15ykfR00GMQIJ70lcKYPJez5LiJOalZS
3.http://developer.51cto.com/art/201103/249613.htm
4.http://www.cnblogs.com/lmtoo/archive/2012/04/08/2437918.html

  • 大小: 45.2 KB
分享到:
评论

相关推荐

    Java类加载器:静态变量初始化.docx

    Java 类加载器静态变量初始化机制详解 Java 类加载器是 Java 语言的核心组件之一,负责将 Java 字节码文件加载到内存中,以便 JVM 可以执行它们。在 Java 中,类加载器是通过委派机制来实现的,即一个类加载器可以...

    学习java静态数据初始化.doc

    实例变量是在实例创建时被初始化的,而静态变量是在类加载时被初始化的。静态变量可以被所有实例共享,而实例变量每个实例都有自己的副本。 在 Java 中,静态变量的初始化有两种方式:隐式初始化和显式初始化。隐式...

    Java变量初始化

    Java 变量初始化的时机可以分为两类:实例变量初始化和类变量初始化。 实例变量初始化 实例变量初始化可以在三个地方进行: 1. 定义实例变量的时候指定初始值; 2. 非静态初始化代码块中对实例变量指定初值; 3. ...

    php 静态变量的初始化

    然而,如果你想要将静态变量初始化为一个对象,PHP并不支持像Java那样的静态构造器或静态初始化块。在这种情况下,你需要在代码的某个时刻显式地调用一个方法来完成初始化。例如: ```php class A { static ...

    探究java的ClassLoader及类变量初始化顺序

    同时,掌握类变量初始化顺序可以避免因误解而导致的错误,特别是在多线程环境中,对静态变量的并发访问和初始化顺序的控制需要特别注意。 总之,深入理解Java的ClassLoader机制和类变量初始化顺序是提升Java编程...

    Java中static静态变量的初始化完全解析

    ### 静态变量初始化的基本规则: 1. **默认初始化**:当类被加载时,静态变量首先会被赋予其数据类型的默认值。例如,`int`类型的静态变量会被初始化为`0`,`boolean`为`false`,引用类型为`null`。 2. **显式初始化...

    Java静态初始化块和对象初始化块

    对象初始化块(也称为实例初始化块),没有`static`关键字,它在每次创建类的新实例时执行。这些块用于为特定对象实例进行初始化,而不是类本身。它们可以被视为构造函数的补充,提供额外的初始化逻辑,特别是当多个...

    C#中静态变量的使用

    1. 内存分配:静态变量在应用程序初始化时创建,而非静态变量需要被实例化后才会分配内存。 2. 生命周期:静态变量生存周期为应用程序的存在周期,而非静态变量的生存周期取决于实例化的类的存在周期。 3. 调用方式...

    java中静态与非静态的变量定义与使用

    这强调了静态变量初始化时可以执行的方法调用,而这些方法调用是在任何对象创建之前进行的。 此外,注意到 `main` 方法中 `t2.f2(1)` 和 `c1.f3(1)` 的调用。由于 `t2` 和 `c1` 是静态变量,我们可以直接通过它们...

    浅析C#静态类,静态构造函数,静态变量

    在 `Cow` 类的示例中,静态构造函数 `static Cow()` 被用来初始化静态变量 `count`。值得注意的是,静态构造函数的执行时间早于任何实例构造函数,而且静态构造函数的执行顺序取决于静态成员的引用顺序,而不是代码...

    java中类的初始化顺序

    类的初始化涉及到多个方面,包括静态成员变量、实例成员变量、静态初始化块、实例初始化块以及构造函数等。本文将详细探讨Java中类的初始化过程及其顺序,并通过具体的代码示例来帮助理解这一过程。 #### 二、基础...

    类初始化和实例初始化1

    在Java编程语言中,类和实例的初始化...总之,类初始化和实例初始化是Java程序运行的基础,理解这两个过程有助于编写更高效、更健壮的代码。同时,掌握方法的重写规则和多态性的应用,可以充分利用面向对象编程的优势。

    Java中的静态变量静态方法静态块与静态类.docx

    在静态块中,我们通常进行一些需要进行异常捕获的静态变量的初始化。 例如: ```java public class StaticExample { static { System.out.println("StaticExample static block"); str = "Test"; setCount(2);...

    C# 公有变量 私有变量 静态变量

    静态变量在类加载时初始化,并且在整个程序生命周期内保持其值。例如: ```csharp public class MyClass { static int StaticVar = 10; // 静态变量 public static void ModifyStaticVar() { StaticVar += 1; ...

    java面试题-类的初始化顺序.doc

    此外,静态初始化块只在类加载时执行一次,而初始化块(也称为实例初始化块)会在每次创建类的新实例时执行。这意味着静态成员和静态初始化块对于所有类的实例都是共享的,而实例成员和实例初始化块则是每个对象独有...

    类继承的初始化顺序类,继承的初始化顺序

    4. **子类非静态成员初始化**:接着是子类的非静态成员变量初始化。 5. **基类构造函数调用**:通过`super()`调用基类的构造函数。 6. **子类构造函数调用**:最后执行子类自身的构造函数。 ### 初始化过程详解 ##...

    Java初始化顺序1

    在 Java 中,实例变量的初始化顺序是按照定义的顺序进行的,而静态变量的初始化顺序则是按照定义的顺序,并且只在第一次访问时初始化。 在上面的示例代码中,我们可以看到,类变量和实例变量的初始化顺序是按照定义...

    java代码的初始化顺序demo

    总之,Java代码的初始化顺序是类加载的必然过程,涉及到静态和实例初始化块、构造函数、成员变量初始化以及继承关系的影响。这个demo是学习和理解这些概念的重要工具,通过实际操作可以加深对Java内存管理和对象生命...

    类中静态成员变量和普通变量的区别——实例代码(VS2010)

    - **访问性**:可以不通过类的实例直接访问静态变量,使用`类名::静态变量名`的方式。 - **生命周期**:静态变量的生命周期从类加载到内存时开始,到程序结束时才释放,因此在整个程序运行期间都可被访问。 - **...

Global site tag (gtag.js) - Google Analytics