1. java static块执行时机

java static块在类被初始化的时候被执行。

参考《深入Java虚拟机》中的描述,一个java class的生命周期:

  • 装载

    • 通过类的全限定名,产生一个代表该类型的二进制数据流;

    • 解析这个二进制数据流为方法区内的数据结构;

    • 创建一个表示该类型的java.lang.Class的实例。

如果一个类装载器在预先装载的时遇到缺失或错误的class文件,它需要等到程序首次主动使用该类时才报告错误。

  • 连接

    • 验证,确认类型符合Java语言的语义,检查各个类之间的二进制兼容性(比如final的类不用拥有子类等 ),另外还需要进行符号引用的验证;

    • 准备,Java虚拟机为类变量分配内存,设置默认初始值;

    • 解析(可选的 ) ,在类型的常量池中寻找类,接口,字段和方法的符号引用,把这些符号引用替换成直接引用的过程。


  • 初始化

当一个类被主动使用时,Java虚拟就会对其初始化,如下六种情况为主动使用:


    • 当创建某个类的新实例时(如通过new或者反射,克隆,反序列化等)

    • 当调用某个类的静态方法时

    • 当使用某个类或接口的静态字段时

    • 当调用Java API中的某些反射方法时,比如类Class中的方法,或者java.lang.reflect中的类的方法时

    • 当初始化某个子类时

    • 当虚拟机启动某个被标明为启动类的类(即包含main方法的那个类)


Java编译器会收集所有的类变量初始化语句和类型的静态初始化器,将这些放到一个特殊的方法中:clinit。


static块可以使用下述实例验证:

public abstract class AbstractTestStatic {
  static int count;
  static {
  System. out .println("AbstractTestStatic static block" );
  count = 1;
  }
  public static int getCount(){
  System. out .println("AbstractTestStatic getCount" );
  return count ;
  }
}
public class TestStatic extends AbstractTestStatic{
  static {
  System. out .println("TestStatic static block" );
  count = 2;
  }
  public static int getCount() {
  System. out .println("TestStatic getCount" );
  return count;
  }
}
public class Main1 {
  public static void main(String[] args) {
  Class[] classArray = new Class[1];
  classArray [0] = TestStatic. class;
  }
}


classArray [0] = TestStatic. class; 这个语句会引起类的装载和连接,但不会初始化。运行程序,可以看到,静态块没有被执行。


2. java static块在一个classloader中只会执行一次


同一个classloader中验证较为简单


不同的classloader中的验证方法如下:

public class DynamicClassLoader extends ClassLoader {
  public DynamicClassLoader(ClassLoader parent ) {
  super (parent );
  }
  @SuppressWarnings ("unchecked" )
  public Class loadClass(String classPath , String className ) throws ClassNotFoundException {
  try {
  String url = classPathParser(classPath ) + classNameParser(className );
  System. out .println(url );
  URL myUrl = new URL( url);
  URLConnection connection = myUrl .openConnection();
  InputStream input = connection .getInputStream();
  ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  int data = input.read();
  while (data != -1) {
  buffer .write(data );
  data = input .read();
  }
  input .close();
  byte [] classData = buffer.toByteArray();
  return defineClass(noSuffix(className ), classData, 0, classData .length );
  } catch (MalformedURLException e ) {
  e.printStackTrace();
  } catch (IOException e ) {
  e.printStackTrace();
  }
  return null ;
  }
  private String pathParser(String path ) {
  return path .replaceAll( "\\\\", "/" );
  }
  private String classPathParser(String path ) {
  String classPath = pathParser(path );
  if (!classPath .startsWith( "file:")) {
  classPath = "file:" + classPath;
  }
  if (!classPath .endsWith( "/")) {
  classPath = classPath + "/";
  }
  return classPath ;
  }
  private String classNameParser(String className ) {
  return className .substring(0, className .lastIndexOf("." )).replaceAll( "\\.", "/" )
  + className .substring(className .lastIndexOf( "."));
  }
  private String noSuffix(String className ) {
  return className .substring(0, className.lastIndexOf( "." ));
  }
}
public class AClass {
  static {
  System. out .println("static in AClass" );
  }
}
public class Main2 {
  @SuppressWarnings ("rawtypes" )
  public static void main(String[] args)
  throws ClassNotFoundException, InstantiationException, IllegalAccessException {
  DynamicClassLoader acl = new DynamicClassLoader(DynamicClassLoader. class.getClassLoader());
  Class s1 = acl .loadClass("D:/workspaces/workspace-rabbit/MultiThread/target/classes" ,"com.vip.test.MultiThread.ch1.thread.staticorder.AClass.class" );
  s1.newInstance();
  DynamicClassLoader bcl = new DynamicClassLoader(DynamicClassLoader. class.getClassLoader());
  Class s2 = bcl .loadClass("D:/workspaces/workspace-rabbit/MultiThread/target/classes" ,"com.vip.test.MultiThread.ch1.thread.staticorder.AClass.class" );
  s2.newInstance();
  }
}


3. java static块执行顺序

java static块的执行顺序是按照父类静态块->子类静态块,一个类内部的静态块按照定义顺序执行。

public class Test {
     public static int i;
    static {
        i = 10;
    }
     public static void main(String[] args) {
    }
    static {
     i = 20;
    }
}


经过编译后,效果和下面的代码一致:

public class Test {
    public static int _i;
    public static void main(String[] args) {
    }
    static {
     i = 10;
     i = 20;
}
}


4. java static块执行时多线程是安全的,但同一线程内不能保证安全

多线程安全性可以用下面的例子看出:

public class BClass {
  static Integer count;
  static BClass instance;
  static {
  System. out .println("Bclass statc block" );
  try {
  TimeUnit. SECONDS .sleep(10);
  } catch (InterruptedException e ) {
  // TODO Auto-generated catch block
  e.printStackTrace();
  }
  count = new Integer(0);
  instance = new BClass();
  }
  static BClass getInstance(){
  return instance;
  }
  public Integer getCount(){
  return count ;
  }
}
public class Main3 {
  public static void main(String[] args) {
  Thread threads [] = new Thread[2];
  for (int i = 0; i < 2; i++) {
  threads [i ] = new Thread() {
  @Override
  public void run() {
  System. out .printf("Thread : %d started\n" , Thread.currentThread().getId());
  BClass bc = BClass. getInstance();
  System. out .printf("Thread : %d getcount\n", Thread. currentThread().getId());
  System. out .printf("Thread : %d getcount %d \n", Thread. currentThread().getId(), bc.getCount());
  }
  };
  threads [i ].start();
  try {
  TimeUnit. SECONDS .sleep(5);
  } catch (InterruptedException e ) {
  // TODO Auto-generated catch block
  e.printStackTrace();
  }
  }
  for (int i = 0; i < 2; i++) {
  try {
  threads [i ].join();
  } catch (InterruptedException e ) {
  // TODO Auto-generated catch block
  e.printStackTrace();
  }
  }
  }
}


执行后,输出:

Thread : 13 started

Bclass statc block

Thread : 14 started  在这里thread14会阻塞直到13的getInstance执行完成后,才继续执行。

Thread : 13 getcount

Thread : 14 getcount

Thread : 14 getcount 0

Thread : 13 getcount 0


单线程不安全的问题如下面这个例子,在static块中,又使用handler,触发static方法的执行。此时,JVM并不会阻塞同一个线程,从而引发NPE。

public class CfgLoader {
  ConfigContext newContext = new ConfigContext();
  ConfigContext oldContext = new ConfigContext();
  public ConfigContext initConfig(IConfigChangedHandler handler) {
  ConfigContext newContext = new ConfigContext();
  handler .handle(oldContext , newContext );
  return newContext ;
  }
}
public class ConfigContext {
  int count = 0;
  public int getCount() {
  return count ;
  }
  public void setCount( int count ) {
  this .count = count;
  }
}
public interface IConfigChangedHandler {
  void handle(ConfigContext previous , ConfigContext current);
}
public class TestBean implements IConfigChangedHandler {
  static ConfigContext context;
  static {
  context = new CfgLoader().initConfig( new TestBean());
  }
  @Override
  public void handle(ConfigContext previous, ConfigContext current ) {
  context .getCount();
  }
  public static int getCount() {
  return context .getCount();
  }
}
public class TestStatic {
  public static void main(String[] args) {
  TestBean. getCount();
  }
}


这个demo在运行的时候,TestBean.getCount会注册handler,回调中又使用了静态方法,引起同一个线程的重入问题。


5. 内部类

内部类的初始化和外部类的初始化没有必然联系,仍然在第一次被使用时,调用内部类的静态块。