[toc]
以下代码运行的结果是什么
package cn.wyj.jvm;
class Singleton {
// private static Singleton singleton = new Singleton();
public static int a;
public static int b = 0;
private static Singleton singleton = new Singleton();
private Singleton() {
a++;
b++;
}
public static Singleton getInstance() {
return singleton;
}
}
public class MyTest1Static {
public static void main(String[] args) {
Singleton singleton= Singleton.getInstance();
System.out.println(singleton.a);
System.out.println(singleton.b);
}
}
运行结果是
1
1
如果注释内容调换后执行结果是
1
0
1. java虚拟机会结束程序生命周期
- 执行System.exit();
- 程序正常结束
- 程序在执行过程中遇到异常
- 由于操作系统出现的错误导致java虚拟机进程终止
类的加载链接初始化
- 加载,查找并加载类的二进制文件
- 链接,
- 验证:确保类加载的类的正确性
- 准备:为<span style="color:red">静态变量</span>分配内存,并将其初始化为默认值
- 解析:把类中的符号引用转换为直接引用
- 初始化,为类的静态变量赋予正确的初始值
加载流程图
java对类的使用方式
JVM规范没有对类加载时机做强制越说但是的对初始化时机有规定
所有的java虚拟机实现必须在每个类或者接口被java程序<span style="color:red;;font-weigth:bold"> "首次主动使用"</span>时才会初始化他们
-
主动使用(6种)
- 创建类的实例
- 访问某个类的静态变量,或者对静态变量赋值
- 调用类的静态方法
- 反射例如 Class.forName
- 初始化一个类的子类
- java虚拟机启动时被标明为启动类的类
-
被动使用
- 通过子类引用父类的静态字段,为子类的被动使用,不会导致子类初始化
- 通过数组定义类引用类,为类的被动使用,不会触发此类的初始化
- 常量在编译阶段会存入调用方法所在的类的常量池
类的加载
类的加载是指将类的class文件读入到内存当中,将其存放在运行时数据区的方法区内,然后再堆区创建一个java.lang.Class对象,用来封装类在方法区内数据结构
imageClass.class class对象是由java虚拟机创建,不能自己创建
/*
* Private constructor. Only the Java Virtual Machine creates Class objects.
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
-
class类的加载方式
- 从本地系统直接加载
- 通过网络下.class文件
- 从zip,jar等归档文件中读取
- 从专有数据库中提取.class文件
- 将java文件动态编译为.class文件
-
类加载最终产品是位于堆区的Class对象
-
class对象封装了类在方法区的数据结构,并向程序员提供了访问方法区数据结构的接口
-
类加载器
- java虚拟机自带
- 根类加载器(Bootstrap),c++编写,无法获取
- 扩展加载器(Extension),java编写
- 系统加载器(System),java编写
- 用户自定义类加载器
- java.lang.ClassLoader 子类
- 用户定制自己的类加载器
- java虚拟机自带
类加载器并不需要等到某个类首次主动使用时在加载
JVM规范允许加载器在预料某个类将要被使用时就预先加载他,如果在预想加载的过程中遇到.class文件确实或者存在错误,类加载器必须首次主动使用该类时才报错(LinkageError错误)
如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误
- 类的验证
类被加载后,就I加你入链接阶段,链接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去- 类文件的结构检查
- 确保类文件遵从java类文件的固定格式
- 语义检查
- 确保类本身符合javascript语言语法规定,比如final类型的类有子类,已经final类型的方法没有被覆盖
- 字节码验证
- 确保字节码流可以被java虚拟机安全地执行.字节码流代表java方法(包括静态方法阿和实例方法),它是由被称作操作码的单字节指令组成的序列,每一个操作码后面都跟着一个或多个操作数.字节码验证步骤会检查每个操作码是否合法,即是否有着合法的操作数.
- 二进制兼容性的验证
- 确保相互应用的类之间协调一致,流入在Worker类的goto workd 方法中会调用car类的run方法.java虚拟机在验证worker类时,会检查在方法区内是否存在car类的run方法,加入不存在(当Woker类和Car类的版本不兼容就会出现这种文件),就会抛出NoSchMethodError错误.
- 类文件的结构检查
-
类的准备
在准备阶段,java虚拟机为类的静态变量分配内存,并设置默认的初始值. -
类的解析
在解析阶段,java虚拟机会吧类的二进制数据中的符号引用替换为直接引用. -
类的初始化
在初始化阶段,java虚拟机执行类的初始化语句,为类的静态变量赋予初始值,在程序中,静态比那里的初始化有两种途径,- 在静态变量生命的处进行初始化
- 在静态代码块中进行初始化
初始化步骤
- 加入这个类还没有被加载和连接,那就先进性加载和连接
- 假如类存在直接的父类,并且这个父类还没有被初始化,那么久先初始化直接的父类
- 加入类中存在初始化语句,那就依次执行这些初始化语句
常量对初始化的影响
类的初始化时机
当java虚拟机初始化一个类时,要求他的酥油父类都已经被初始化,但是这条规则不适用于接口
- 在初始化一个类时,并不会初始化他所实现的接口
- 在初始化一个接口时,并并不会先初始化它的父接口
程序中对子类的主动使用会导致父类的初始化,对父类的主动使用不会导致子类初始化,例如使用使用Object类不会对所有类实例化
只有当程序访问的静态变量或者静态方法确实在当前类或者当前接口定义时,才可以认为是对类或者接口的主动使用
调用clasLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化
类加载器
- 有两种类型的类加载器
- Java 虚拟机自带的加载器
- 根类加载器(Bootstrap)
- 扩展类加载器(Extension)
- 系统类加载器(System)
- 用户自定义的类加载器
- java.lang.ClassLoader 的子类
- 用户可以定制类的加载方式
- Java 虚拟机自带的加载器
- 双亲委托
Java中ClassLoader的加载采用了双亲委托机制,采用双亲委托机制加载类的时候采用如下的几个步骤:- 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。
每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。 - 当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.
- 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
- 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。
网友评论