首先什么话都不说,先把这个很变态的面试题放上来,大家有情趣自己分析一下,然后再运行一下看看结果,据说第
一次遇到这个题目的Java程序员都是会做错的。
package com.bird.classLoad; public class Test1 { @SuppressWarnings("static-access") public static void main(String[] args) { Singleton s = Singleton.getSingleton(); System.out.println("counter1 = "+ s.counter1); System.out.println("counter2 = "+s.counter2); } } class Singleton{ private static Singleton singleton = new Singleton(); public static int counter1; public static int counter2 = 0; public Singleton(){ counter1++; counter2++; } public static Singleton getSingleton(){ return singleton; } }
首先介绍一下JVM对一个类的字节码class文件是如果装载到内存中然后被虚拟机执行的。
1.加载:查找并加载类的二进制数据
2.连接
2.1:确保被加载类的正确性
2.2:为类的静态变量分配内存,并将其初始化为默认值
2.3:把类中的符号引用转换为直接引用
3.初始化:为类的静态变量赋予正确的初始值
请注意,在连接的时候,为类的静态变量分配内存,初始化为默认值,然后才把类中的符号转换为指定的引用。
当然了,JVM只有在主动调用类的时候才回去加载一个类,主动调用一共有六种方式
Java虚拟机主动使用一个类的六种情况
1.创建类的实例
2.访问某个类或接口的静态变量,或者对该静态变量赋值
3.调用类的静态方法
4.反射
5.初始化一个类的子类
6.Java虚拟机启动时被标明为启动类的类
除了以上六种情况,其他都为被动使用,都不会执行类的初始化
然后对于类加载器
有两种类型的类加载器
1.根类加载器(使用C++编写,程序员无法在Java代码中获得该类)
2.扩展类加载器
3.系统类加载器
用户自定义类加载器
ClassLoader的子类
比如
package com.bird.classLoad; public class Test2 { public static void main(String[] args) throws Exception { Class clazz = Class.forName("java.lang.String"); System.out.println(clazz.getClassLoader()); Class clazz2 = Class.forName("com.bird.classLoad.A"); System.out.println(clazz2.getClassLoader()); } } class A{}运行结果
null sun.misc.Launcher$AppClassLoader@addbf1
访问这个根加载器的。
然后对于自己写的类,当然是通过内部类application加载器加载的,虽然都能使用,但是底层加载方式各不相同。
最后我们来分析程序
首先执行Singleton s = Singleton.getSingleton();
主动调用 Singleton,这之后初始化Singleton类,为static变量初始化内存空间然后赋予默认值。所以,singleton为
null,counter1为0,counter2为0.
然后赋予引用值。singleton调用构造函数,这时候counter1等于1,counter2等于1.
然后顺次执行,counter1没有被赋值,counter2被赋值为0.
这次就返回这个对象了,所以最终结果为,1,0
总结一下,这道题真坑爹。