概述
JVM-----在类的声明周期中,类的二进制数据位于方法区里,同时在堆区内还会有一个响应的描述这个类的Class对象.
Java虚拟机及程序的生命周期
当通过java命令运行一个程序时,就启动了一个java虚拟机进程.从开始到结束就是虚拟机的生命周期.以下情况下JVM将结束生命周期
1,程序正常执行结束
2,程序在执行过程中因为出现异常或者错误而终止
3,执行了System.exit()方法
4,由于操作系统出现错误而导致JVM进程终止
类的加载,连接和初始化
加载:查找并加载类的二进制数据.把类的class文件中的二进制数据读入到内存中,把它放在运行时数据区对方法区内.在堆上创建Class对象.用来封装在方法区内的数据结构.
加载方式:
1.本地加载
2.网络下载
3.zip jar中提取
4.从一个专有的数据库中提取
5.把一个java源文件动态编译为class文件
加载方式是由类加载器完成的,包括JVM自带的类加载器和用户继承ClassLoader类的实例.加载方式可以是首次使用主动加载或者预先加载.预先加载如果发现class文件不存在,只有在首次使用才会报告错误.LinkageError错误.
连接:把已经读到内存中的类的二进制数据合并到JVM运行时环境中去.
验证:确保被加载类的正确性.包括类文件的结构检查,语义检查,字节码验证,二进制兼容
准备:为类的静态变量分配内存,并将其初始化为默认值.
解析:把类中的符号引用转换为直接引用
初始化:给类的静态变量赋予正确的初始值 (首次主动使用才会进行这步骤).1,静态变量的声明进行初始化 2,静态代码块中进行初始化.
<1>假设这个类还没有被加载和连接,那就先进行加载和连接
<2>假如类存在直接的父类,并在这个父类还没有被初始化,那就先初始化直接的父类.
<3>假如类中存在初始化语句,那就依次执行这些初始化语句.
package init;
class Base{
static int a=1;
static{
System.out.println("init base");
}
}
class Sub extends Base{
static int b=1;
static{
System.out.println("init Sub");
}
}
public class InitTest {
static{
System.out.println("init initTest");
}
public static void main(String[] args) {
System.out.println("b="+Sub.b);//执行这行代码时,先依次初始化Base和Sub
}
}
如果把以上的main方法修改为以下:
public static void main(String[] args) {
Base base;//不会初始化Base类
System.out.println("After defining base");//执行这行代码时,先依次初始化Base和Sub
base=new Base();
System.out.println("After creating an object of Base");
System.out.println("a="+base.a);
System.out.println("b="+Sub.b);
}
执行结果变为:
init initTest
After defining base
init base
After creating an object of Base
a=1
init Sub
b=1
从以上总结出类的初始化时机:
对类或者接口的主动使用
1,创建类的示例,new 克隆 反射 反序列化
2,调用类的静态方法
3,访问某个类或者接口的静态变量,或者对该静态变量赋值
4,调用API中的某些反射方法.Class.forName()
5,初始化一个类的子类,
6,JVM启动时被标明为启动类的类.例如包含main()方法的类.
被动使用
1,对于final类型的静态变量,如果在编译时就能计算出变量的取值,那么这种变量就被看做是编译时常量,对这种常量的使用不会导致类的初始化.如果无法在编译期间进行值的确定那么就会转化为主动使用进行初始化操作.
class Base{
public final static int a=1;//编译时常量 不会导致初始化
static{
System.out.println("init base");
}
}
2.当JVM初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则不适用于接口.
在初始化一个类时,并不会先初始化它所实现的接口
在初始化一个接口时,并不会先初始化它的父接口.
只有当首次使用特定接口的静态变量时,才会导致该接口的初始化.
只有当程序访问的静态变量或者静态方法的确在当前类或者接口中定义时,才可以看做是对类或者接口的主动使用.
package init;
class Base{
static int a=1;//编译时常量 不会导致初始化
static void method(){
System.out.println("method of Base");
}
static{
System.out.println("init base");
}
}
class Sub extends Base{
static{
System.out.println("init Sub");
}
}
public class InitTest {
static{
System.out.println("init initTest");
}
public static void main(String[] args) {
System.out.println("a="+Base.a);//仅仅初始化Base
Sub.method();
}
}
结果为:
init initTest
init base
a=1
method of Base
调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化操作.
package init;
class ClassA{
static{
System.out.println("now init classA");
}
}
public class ClassB {
public static void main(String[] args) throws Exception{
ClassLoader loader=ClassLoader.getSystemClassLoader();//获得系统类加载器
Class objClass=loader.loadClass("init.ClassA");//加载ClassA
System.out.println("after load ClassA");
System.out.println("before init ClassA");
objClass=Class.forName("init.ClassA");//初始化ClassA
}
}
after load ClassA
before init ClassA
now init classA
最后
以上就是要减肥母鸡为你收集整理的JAVA--类的声明周期的全部内容,希望文章能够帮你解决JAVA--类的声明周期所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复