如果一个类被多次加载,那么它的static-members会多次初始化吗?

时间:2014-08-07 16:17:58

标签: java initialization classloader

如果一个类被多次加载,那么它的static-members会多次初始化吗? 我该如何检查?

3 个答案:

答案 0 :(得分:6)

如果涉及不同的类加载器,那么它们将是完全独立的类,具有单独的静态字段等 - 并且每个都将单独初始化。

(最简单的诊断方法就是在初始化时记录,当然......)

static {
    // Log initialization
}

答案 1 :(得分:3)

静态块中的代码只执行一次:第一次创建该类的对象或第一次访问该类的静态成员时(即使您从未创建该类的对象)。这意味着当类加载器将类加载到内存时调用它。所以它的每类加载器。如果你有多个类加载器,每个都有自己的类副本,所以每个类加载器都会调用静态块。要测试这个,您可以尝试将Sysout放在静态块中,然后使用自定义类加载器加载它。在下面的示例中,静态块将执行两次。一个是系统类加载器,当我们运行静态main方法,然后是我们的自定义类加载器。

package sample;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class Sample {

    static {
        System.out.println("Entered Static Block!!");
    }

    public static void main(String[] args) {
        CustomClassLoader loader = new CustomClassLoader();
        try {
            Class<?> c = loader.findClass("sample.Sample");
            Object o = c.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

}

class CustomClassLoader extends ClassLoader {

    private Map<String, Class<?>> classes = new HashMap<String, Class<?>>();

    @Override
    public String toString() {
        return CustomClassLoader.class.getName();
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        if (classes.containsKey(name)) {
            return classes.get(name);
        }

        byte[] classData;

        try {
            classData = loadClassData(name);
        } catch (IOException e) {
            throw new ClassNotFoundException("Class [" + name
                    + "] could not be found", e);
        }

        Class<?> c = defineClass(name, classData, 0, classData.length);
        resolveClass(c);
        classes.put(name, c);

        return c;
    }

    private byte[] loadClassData(String name) throws IOException {
        BufferedInputStream in = new BufferedInputStream(
                ClassLoader.getSystemResourceAsStream(name.replace(".", "/")
                        + ".class"));
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int i;

        while ((i = in.read()) != -1) {
            out.write(i);
        }

        in.close();
        byte[] classData = out.toByteArray();
        out.close();

        return classData;
    }
}

答案 2 :(得分:1)

以下操作 not 导致加载类:

  • 引用在编译时已知的静态最终原始字段。
  • classLoader.getResource(className.replace('.', '/') + ".class")

以下导致类为loaded(即,解析.class文件,并创建Class<?>对象):

  • 以符号方式引用类的任何代码行,例如: Foo.class
  • Class.forName(String, false ,ClassLoader)
  • ClassLoader.loadClass(字符串)
  • 加载类的子类或数组,或初始化其代码或方法签名引用该类的类。

以下导致类为initialized(即执行静态块):

  • 构建新实例
  • 调用静态方法
  • 获取或设置非编译时常量的静态字段。
  • Class.forName(String, true ,ClassLoader)和Class.forName(String)

如果从多个ClassLoader初始化类,则静态块确实会多次执行。例如,以下代码:

import java.net.URL;
import java.net.URLClassLoader;

public class InitializeClassMultipleTimes {
    static class Foo {
        static {
            System.out.format("  %s initialized by %s%n", Foo.class.getSimpleName(), Foo.class.getClassLoader());
        }
        public static void foo() {}
    }
    private static Class<Foo> loadClass() {
        System.out.println("Loading class.");
        // Load the .class file. This will fail if the class file is gone or has
        // the wrong file format.
        return Foo.class;
    }
    private static void initializeClass(Class<?> innerClass) {
        System.out.println("Initializing class");
        try {
            Class.forName(innerClass.getName(), true, innerClass.getClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e);
        }
    }

    public static void main(String... argv) throws ClassNotFoundException {
        Class<Foo> fooClass = loadClass();
        initializeClass(fooClass);

        URLClassLoader myClassLoader = ((URLClassLoader) InitializeClassMultipleTimes.class.getClassLoader());
        URL[] urls = myClassLoader.getURLs();
        for (int i = 0; i < 2; i++) {
            URLClassLoader newClassLoader = new URLClassLoader(urls, null);  // parent=bootstrap
            System.out.format("%nLoading class using another class loader%n", Foo.class.getSimpleName());
            Class<?> fooClassAgain = Class.forName(fooClass.getName(), false, newClassLoader);
            initializeClass(fooClassAgain);
        }
    }
}

产生以下输出。请注意,您也可以在strace -f -e file下运行它以验证何时读取.class文件。

Loading class.
Initializing class
  Foo initialized by sun.misc.Launcher$AppClassLoader@73d16e93

Loading class using another class loader
Initializing class
  Foo initialized by java.net.URLClassLoader@15db9742

Loading class using another class loader
Initializing class
  Foo initialized by java.net.URLClassLoader@7852e922