关闭

JVM-类型的生命周期

796人阅读 评论(0) 收藏 举报
分类:
类型的生命周期包括:装载,连接,初始化,卸载(和类装载器有关,不一定有)。
装载:将类的结构信息装载到内存中(这其中就包括很重要的角色--类的常量池,但是符号引用还没有解析),装载完毕之后会生成一个该类的Class对象。
连接:连接又包括验证,准备和解析。验证就是检验class文件语法等正确性。准备是为类变量分配内存并为他赋默认值。解析是将在装载阶段装载到内存中的常量池的符号引用解析为直接引用,这一步可以延后一直到类被首次主动使用的时候进行。关于首次主动使用,会在下文中说明。
初始化:初始化就是为类变量赋正确的值,在连接阶段,为类变量赋了默认的值,在这一步中,会将默认值修改为正确值。所谓正确值,就是程序员在程序中赋予变量的值。初始化过程会在一个类首次主动使用的时候进行。
一个类的初始化过程分为2个步骤:
1 如果该类存在父类,并且父类没有被初始化,则初始化父类。
2 如果该类存在<clinit>方法,执行此方法。

上面的第一条容易理解,我们来介绍第二条中的<clinit>方法。
<clinit>方法叫做类初始化方法,它专门负责执行“类变量初始化语句”和“静态初始化语句”。<clinit>方法在class文件生成时被创建。如果一个类没有类变量初始化语句,也没有静态初始化语句,那么该类就不存在<clinit>方法。
我们来看下面的例子:
public class Example {
	static int width=3;
	static int height=1+(int)(Math.random()*3.0);
	static int length;
	static {
		length=10+(int)(Math.random()*3.0);
	}
}

当这个类生成class文件时,会在class文件中生成<clinit>方法,<clinit>方法负责初始化类的height变量和length变量。为何没有width变量?weight变量会被编译器特殊处理为常量(这在生成class文件时就已经做好了)。也就是说在程序中的变量在class文件中不一定是变量,对于型如width这种初始化的变量会被优化为常量,存放在常量池中。当其他类引用Example.width时,会在其他类中直接存放一个3,而不是存放一个Example.width的引用。比如在类A中有语句int tmp=Example.width,那么在类A的常量池中会将tmp的值存储为3,而不是Example.width的引用。
另外,我们需要重点知道的是,jvm会保证<clinit>方法被同步的执行,这就是使用内部静态类实现的单例模式能保证线程安全的原因。
下面我们来说说什么叫主动使用?有6种行为被认为是主动使用:
1 创建类的实例
2 调用类中的静态方法
3 操作类中的非常量静态字段
4 调用java API中特定的反射方法
5 初始化一个类的子类
6 指定一个类作为启动初始化类
上面的6种行为其实很好理解,归根到底就是不初始化一个类程序执行不下去的时候,就要初始化一个类。下面给出一个例子:
class Parent{
	static int hoursOfSleep=(int)(Math.random()*3.0)+2;
	static {
		System.out.println("Parent was initialed!");
	}
}
class NewBaby extends Parent{
	static int hoursOfCry=3+(int)(Math.random()*3.0);
	static {
		System.out.println("NewBaby was initialed!");
	}
}
public class Example {
	public static void main(String [] args){
		int hours=NewBaby.hoursOfSleep;
		System.out.println(hours);
	}
	static {
		System.out.println("Example was initialed!");
	}
}


上面的代码输出结果为:
Example was initialed!
Parent was initialed!
2
我们来分析下值命令行中执行java Example之后的过程,首先装载Example的结构到内存中(包括常量池),接着验证,准备,连接。由于Example中没有类变量,不需要为类变量分配内存和赋值操作。接着初始化,初始化的时候会执行static块中的静态初始化语句,输出Example was initialed!。类Example装载完毕之后,就开始执行它的main方法啦!执行到main方法的第一句,需要使用NewBaby的hoursOfSleep,发现NewBaby类的hoursOfSleep变量指向Parent的hoursOfSleep变量。而要使用Parent的hoursOfSleep变量需要先将其初始化(只用初始化才会执行类中的类变量初始化语句和静态初始化语句),因此初始化Parent类,执行Parent类的<clinit>方法,返回hoursOfSleep的值。在执行Parent的<clinit>方法时自然会输出Parent was initialed!。接着程序继续执行主函数的println方法即可。我们可以看到:在程序执行过程中,没有必要初始化NewBaby类。
自此,我们介绍了一个java类装载,连接,初始化的过程,当然,当一个类型不在被使用的时候,我们可以选择将该类型从内存中卸载,但是常用的启动类装载器不支持卸载。
0
0

猜你在找
【直播】机器学习&数据挖掘7周实训--韦玮
【套餐】系统集成项目管理工程师顺利通关--徐朋
【直播】3小时掌握Docker最佳实战-徐西宁
【套餐】机器学习系列套餐(算法+实战)--唐宇迪
【直播】计算机视觉原理及实战--屈教授
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之矩阵--黄博士
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之凸优化--马博士
【套餐】Javascript 设计模式实战--曾亮
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:112233次
    • 积分:1308
    • 等级:
    • 排名:千里之外
    • 原创:137篇
    • 转载:10篇
    • 译文:0篇
    • 评论:23条
    最新评论