类的生命周期
其中加载-验证-准备-初始化-卸载的先后顺序是固定的。
解析阶段有可能发生在初始化之后(为了支持运行时绑定)
虚拟机在一下场景冲必须对类进行初始化(之前肯定先进行加载-验证-准备阶段)
- 遇到 new 指令 getstatic,putstatic,invokstatic
- 对类进行反射的时候
- 初始化类的时候先进行初始化其父类
- main方法的类,虚拟机需要先进行初始化
运行的时候并不会加载SubClass
子类引用父类的静态字段,并不会触发子类的加载
class SuperClass {
public static int value =1;
static {
System.out.println(" super class init");
}
}
class SubClass extends SuperClass{
static {
System.out.println(" sub class init");
}
}
public class Test{
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
//
public class ConstClass {
public static final String constA = "A";
static{
System.out.println(" init ");
}
}
public class Test {
/**
* 并没有触发ConstClass的初始化,因为在虚拟机常量传播优化,在编译Test.class的时
候 就已经是当前类常量了
* @param args
* @author created by liuyujie
*/
public static void main(String[] args) {
System.out.println(ConstClass.constA);
}
}
加载:
读取二进制字节流(不管你是本地文件还是网络二进制流等)。
目的是 将二进制流读入,将静态的二进制流,转换为方法区运行时数据结构,同时在方法区中生成Class对象(对于Hotspot);
(数组不是通过类加载器创建,是通过java虚拟机直接创建的)
类加载器是用来加载类的,比如两个实例是否相等,instance of,只有在同一个类加载器加载的时候才有意义,
不同类加载器 加载的Class对象都不一样,类加载器使用的是双亲委派模型,如上图。
bootstap启动类加载器
加载的是$JAVA_HOME/lib下的类,用户自己定义的类是无法被启动类加载器加载的,
加载器会进行 比如包名,或者类名进行检查,对于有启动类加载的类我们是木办法覆盖的。
package java.lang;
public class Integer {
public static void main(String[] args) {
}
}
错误: 在类 java.lang.Integer 中找不到主方法, 请将主方法定义为:
public static void main(String[] args)
String property = System.getProperty("sun.boot.class.path");
可以查看启动类加载器 加载了那些类。
extension扩展类加载器
用的eclipse,要查看这个扩展类的源码 要下载openjdk,我用的jdk7
对于的地址是 http://download.java.net/openjdk/jdk7/promoted/b147/openjdk-7-fcs-src-b147-27_jun_2011.zip(参考blog https://bbs.csdn.net/topics/360024960)
把源文件导入到项目中来就可以看了 ExtClassLoader即扩展类加载器
String s = System.getProperty("java.ext.dirs");这个是扩展类加载的一些类
public class TeExt extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return super.loadClass(name);
}
public static void main(String[] args) throws Exception {
Class<?> loadClass = TeExt.class.getClassLoader().loadClass("vm.c7.Int");
System.out.println(loadClass);
Class<?> loadClass2 = TeExt.class.getClassLoader().getParent().loadClass("javax.crypto.KeyGenerator");
System.out.println(loadClass2);
TeExt.class.getClassLoader().getParent().loadClass("vm.c7.Int");
}
}
用上面的代码去加载vm.c7.Int这个类
加载代码如下
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);//首先去查询是否java虚拟机加载过,没有执行加载
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {//除bootstrap启动类记载器外,都有父类加载器,
//首先尝试使用父类记载器去加载。
c = parent.loadClass(name, false);//递归调用,
} else {//启动类加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {//如果父类都加载不成功,则当前加载器尝试加载
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);//如果加载不成功抛出异常
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
运行上述代码会发现报错Exception in thread "main" java.lang.ClassNotFoundException:
TeExt.class.getClassLoader().getParent().loadClass("vm.c7.Int");这行报错。
因为在初始化话ExtClassLoader的时候
去查找扩展目录下的类,然后
回去扫描JAVA_HOME/jre/lib/ext下面的jar包。
显然启动类加载器和扩展类加载器都没有vm.c7.Int这个类,所以就。。。
很明显java的类加载机制是父类优先加载,加载不了 才会到下面实现类加载,默认的加载类即appclassloader
验证
验证目的是,确保二进制字节码不会危害虚拟机,并且符合虚拟机的要求
包括 文件格式验证(如是否caffebabe开头,版本号等),元数据验证(如,除Object外所有类都有父类,final修饰的类不能被继承,抽象类的抽象方法子类是否实现等),字节码验证(验证程序语意是否合法);
准备
准备阶段是给类变量分配内存和设置初始值的阶段,是类变量,static修饰的变量
比如 static int a=1;
准备阶段会把a初始值赋值为0;
解析
解析阶段是将符号引用替换为常量的过程,
之前使用javap -verbose 查看的一些常量池等信息,这些信息都是用符号来保存的,就是一个字符串,
并且能够准确描述这个符号 代表的类型等信息。
假如类class Student 有一个字段 是 private Adress adress;
在加载Studen类的时候会触发加载类Addres
在类Studen中adress编译成class文件后就是一个字符串,解析就是把把address解析为
类Addres(或者其子类)的符号引用(拥有Addres的类型指针(句柄),是内存中的存在的),最后还要查看Student是否
对类Addres是否有访问权限
初始化
即执行类构造器<clinet>方法,将所有static按照出现的先后顺序,进行赋值和执行静态代码块,
虚拟机会保证父类<clinet>方法先于子类执行