从单例模式看JVM内幕

来源:互联网 发布:淘宝账号怎么申请注册 编辑:程序博客网 时间:2024/04/27 16:34

先看一道简单而变态的面试题:
class Singleton{

private static Singleton singleton = new Singleton();  //
public static int counter1;
public static int counter2 = 0;
//private static Singleton singleton = new Singleton();  //

public Singleton(){
  counter1++;
  counter2++;
}

public static Singleton getInstance(){
  return singleton;
}
}
//测试类
public class Mytest {
public static void main(String[] args) {
  Singleton singleton = Singleton.getInstance();
  System.out.println("counter1:"+singleton.counter1);
  System.out.println("counter2:"+singleton.counter2);
}
}

//输出结果
private static Singleton singleton = new Singleton();语句在位置①,结果:
counter1:1
counter1:0
private static Singleton singleton = new Singleton();语句在位置②,结果:
counter1:1
counter1:1

JVM加载、连接、初始化.class文件原理如下:
/*
* 1.虚拟机的生命周期:结束的几种方式:正常结束、System.exit()、异常、操作系统错误
* 2.加载、连接、初始化
*   加载:class文件二进制加载到内存
*   连接:
*        验证:确保加载类的正确性
*        准备:为类得静态变量分配内存,并将其默认为初始值
*        解析:把类得符号引用转换为直接引用
*   初始化:为类的静态变量赋予正确的初始值
*
* java对类的使用方式2种:主动使用、被动使用
* 主动使用(6种):
*   创建类得实例:new一个实例
*   访问静态变量
*   调用类得静态方法
*   反射:Class.forName("com.xxx.xxx")
*   初始化类的子类
*   java虚拟机启动时被标明为启动类得类:有main方法的入口
*
* ***所有java虚拟机实现必须在每个类或接口被java程序“首次主动使用”时才初始化它们
*
* 1.类的加载
*   加载过程:.class文件中二进制读入内存,将其放在运行时数据区的方法区,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区的数据结构
*   加载方式:本地系统加载
*          网络下载.class文件:URLClassLoader
*         从zip、jar归档文件中加载文件
*        数据库中提取.class文件
*        java源文件动态编译的.class文件
*  加载器:
*    java自带加载器:
*         a.根类加载器:bootstrap (c++编写的,在程序中无法在java代码中获得该类)
*         b.扩展加载器:extension (java写的)
*         c.系统(应用)加载器:system (java写的)
*    用户自定义加载器:java.lang.ClassLoader子类
* 2.连接:将已经读入到内存的类的二进制数据合并到虚拟机运行时环境中去
*   验证:
*    类的文件结构检查:java类文件固定格式
*    语义检查:符合java类语法规范:如final类无子类
*    字节码验证:字节码流 = 操作码单字节指令流,防止人为生成.class文件
*    二进制兼容性验证:一个类的方法调用另外一个类得某一方法,但方法不存在
*   准备:为类得静态变量分配内存,并将其默认为初始值
*   解析:把类得符号引用转换为直接引用
*    例子:在Worker类gotoWork()方法会引用到Car类的run()方法
*    public void gotoWork(){
*     car.run(); //这段代码在Worker类的二进制代码中表示为符号引用
*    }
*    //在Worker类的二进制数据中,包含了一个对Car类的run()方法的符号引用,它由run()方法的全名和相关描述符组成。在解析阶段,java虚拟机会把这个符号引用替换为一个指针(c语言真正的指针),该指针指向Car类的run()方法在方法区内的内存位置,这个指针就是直接引用
* 3.初始化:为类得静态变量赋予初始值
*   静态变量赋初始值有2种途径:1.在静态变量声明处。2.在静态代码块中static{}
*/