废话不多说,直接上代码!!!
先创建一个测试类:
package cn.xiaohu.jvm;
public class Singleton
{
public static Singleton singleton=new Singleton();
public static int num1;
public static int num2=0;
public Singleton ()
{
num1++;
num2++;
}
public static Singleton getInstance()
{
return singleton;
}
}
main方法运行这个测试类:
package cn.xiaohu.jvm;
public class TestJVM
{
public static void main(String[] args)
{
Singleton singleton=Singleton.getInstance();
System.out.println("num1:"+Singleton.num1);
System.out.println("num2:"+Singleton.num2);
}
}
控制台打印的结果是什么?相信大部分程序猿分析的结果:num1:1 num2:1
实际的结果:num1:1 num2:0
很奇怪是不是 ?为什么会是这样
现在调整一下Singleton 这个类:
package cn.xiaohu.jvm;
public class Singleton
{
public static int num1;
public static int num2=0;
public static Singleton singleton=new Singleton();//调整到这个位置
public Singleton ()
{
num1++;
num2++;
}
public static Singleton getInstance()
{
return singleton;
}
}
现在的结果 和 我们 分析的结果是一样的了:num1:1 num2:1
出现这种情况要从java虚拟机说起!
java虚拟机和程序的生命周期:
在以下几种情况,java虚拟机将结束生命周期
1.执行了System.exit();方法
2.程序执行完毕正常结束
3.程序在执行过程中遇到了异常或者错误而异常终止
4.由于操作系统出现了错误而导致java虚拟机终止
类的加载,连接与初始化
加载:查找并加载类的二进制数据
连接:
1.验证:确保被加载的类的正确性
2.准备:为类的静态变量分配内存,并将其初始化为默认值
3.解析:把类中的符号引用转化为直接引用
初始化:为类中的静态变量赋予正确的初始值
看似 连接中的准备步骤 和 初始化的步骤是重复的 实际不重复
其中分为两步 比如一个 类中 static int i=3;
先是给i=0; 然后 在给 i赋值为3;
java程序对类的使用分为:
1.主动使用
2.被动使用
所有的java虚拟机实现必须在每个类或者接口被java程序“首次主动使用”时才初始化它们
主动使用分为六种情况:
1.创建类的实例
2.访问某个类的接口或者静态变量,或者对该静态进行赋值
3.调用类的静态方法
4.反射
5.初始化一个类的子类
6.Java虚拟机启动时被标明为启动类的类
除了以上六种情况 ,其他全是被动使用!!!也就是说 除了这六种情况,都不会被执行初始化 步骤
类的加载:
类的加载是指将.class文件中二进制数据读入到内存中,将其放到运行时数据区的方法区内,
然后在堆中创建一个java.lang.Class 对象,用来封装 类在方法区内的数据结构
类的加载最终的产品就是位于堆中的Class对象,Class对象封装了类位于方法去内的数据结构,
并且向java程序员提供了访问方法区内的数据结构的接口
所以根据以上原理,类加载器先把Singleton加载到方法区内,然后在连接--准备阶段 ,把静态变量,
分配内存空间,代码会依次从上而下的执行,所以,
singleton会赋值为null,num1赋值为0,num2赋值为0,但是这个0,不是显示赋值的0,
在main方法中,调用了getInstance()方法,是为 首次主动使用 ,开始 初始化 步骤,
先是singleton变量new对象,调用构造函数,num1和num2变成1,然后,num2会在再次被显式在次赋值为0
第二次 调整了一下执行顺序,所以,num1和num2都为1
原理就是这样,但是语言组织匮乏,希望能帮助到你!