金融机构网站建设费用,龙岗区黄阁坑社区,网站开发小结,免费申请网站官网一、java虚拟机与程序的生命周期
在如下几种情况之下#xff0c;java虚拟机将结束生命周期#xff1a;
执行了System.exit()方法程序正常执行结束程序在执行过程中遇到了异常或者错误而异常终止由于操作系统用出现错误而导致java虚拟机进程终止
二、类的加载#xff0c;链…一、java虚拟机与程序的生命周期
在如下几种情况之下java虚拟机将结束生命周期
执行了System.exit()方法程序正常执行结束程序在执行过程中遇到了异常或者错误而异常终止由于操作系统用出现错误而导致java虚拟机进程终止
二、类的加载链接初始化
2.1 加载查找并加载类的二进制数据
类加载器并不需要某个类被首次主动使用时再加载他。JVM规范允许类加载器在预料某个类将要被使用时就预先加载它如果在预先加载的过程中遇到了.class文件缺失或存在错误类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)。如果这个类一直没有被程序主动使用那么类加载器就不会报告错误。类被加载后就进入连接阶段。
2.2 连接
将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。然后要经过一系列的验证。
2.2.1 验证确保被加载的类的正确性验证字节码
类文件的结构检查确保类文件遵从java类文件的固定格式。语义检查确保类本身符合java语言的语法规定比如验证final类型的类没有子类以及final类型的方法没有被覆盖。虽然编译时就可以发现错误但不经过编译手动生成class文件那么就会发现不了final类型的方法被覆盖但是语义检查就可以发现字节码验证确保字节码流可以被java虚拟机安全的执行。字节码流代表java方法报空静态方法和实例方法它是由被称作操作码的单字节指令组成的序列每一个操作码后跟着一个或多个操作数。字节码验证步骤会检查每个操作码是否合法即是否有着合法的操作数。二进制兼容性的验证确保相互引用的类之间的协调一致例如在Wroker类的gotoWork()方法中会调用Car类的run()方法。java虚拟机在验证work()类时会检查在方法区内是否存在Car类的run()方法假如不存在当worker类和Car类的版本不兼容就会出现这种问题就会抛出NoSuchMethodError方法。
public class Wroker{public void gotoWork(){Car car new Car(); car.run();//这段代码在worker类的二进制数据中表示为符号引用}
}2.2 准备为类的静态变量分配内存并将其初始化为默认
在准备阶段Java虚拟机为类的静态变量分配内存并设置默认的初始值。例如对于一下Sample类在准备阶端将为int类型的静态变量a分配4个字节的内存空间并且赋予默认值0为long类型的静态变量b分配8个字节的内存空间并且赋予默认值0。
public class Sample{private static int a1;public static long b;static{b2;}
}2.3 解析把类中的符号引用转换为直接引用
在解析阶段java虚拟机会把类的二进制数据中的符号引用替换为直接引用。例如在Worker类的gotoWork()方法中会引用Car类的run()方法。
在Worker类中的二进制数据中包含了一个对Car类的run()方法的符号引用它由run()方法的全名和相关描述符组成。在解析阶段Java虚拟机会把这个符号替换为一个指针该指针指向Car类的run()方法在方法区内的内存位置。这个指针就是直接引用。
public class Wroker{public void gotoWork(){Car car new Car(); car.run();//这段代码在worker类的二进制数据中表示为符号引用}
}2.3 初始化为类的静态成员变量赋予正确的初始值
在初始化阶段java虚拟机执行类的初始化语句为类的静态变量赋予初始值。在程序中静态变量初始化有两种途径
在静态变量的声明处进行初始化在静态代码快中进行初始化。例如在以下代码中静态变量a和b都被显示初始化而静态变量c没有被显示初始化它将保持默认值为0但是如果要使用c则必须进行初始化。
public class Sample{private static int a1; //在静态变量声明出进行初始化public static long b;public static long c; //但是如果要使用c则必须进行初始化static{b2; //在静态代码块中进行初始化}
}示例:
public class ClassLoaderTest {public static void main(String[] args) {Singleton singletonSingleton.getInstance();System.out.println(counter1 singleton.counter1);System.out.println(counter2 singleton.counter2);}
}/***程序是从上向下顺序执行
* new Singleton()时counter1counter2初始值均为0
* 在通过构造方法Singleton()均加1.则返回的值counter1counter2均为1
* 然后再程序在继续向下执行由于counter1没有显示初始化则值还是为1
* 但是counter2经过显示初始化后其值为0
* author coderacademy
*/
class Singleton{private static Singleton singletonnew Singleton();//new语句在这是结果为counter1 1counter2 0public static int counter1;public static int counter20;//private static Singleton singletonnew Singleton();//new语句在这是结果为counter1 1counter2 1private Singleton(){counter1;counter2;}public static Singleton getInstance(){return singleton;}
}静态变量的声明语句以及静态代码块都被看做类的初始化语句java虚拟机会按照初始化语句在类文件中的先后顺序来一次执行他们。类的初始化步骤假如这个类还没有被加载和连接那就先进行加载和连接假如类存在直接的父类并且这个父类还没有被初始化那就先初始化直接的父类。假如父类中存在初始化语句那就依次执行这些初始化语句。 public class FinalTest {public static void main(String[] args) {System.err.println(Test.X);}
}/**
* 当X6/3时编译时即可算出X2即编译时常量即不需要运行类所以不打印静态代码块中的内容
*当Xnew Random().nextInt(100)时编译时不能算出X的值只有运行程序才知道所以打印结果为FinalTest static final 2
* author coderacademy
*/
class Test{public static final int X6/3;//打印结果 2//public static final int Xnew Random().nextInt(100);//打印结果为FinalTest static final 2static{System.err.println(FinalTest static final);}
}类的初始化时机当java虚拟机初始化一个类时要求他的所有父类都已经被初始化但是这条规则并不适用于接口。在初始化一个类时并不先初始化它所实现的接口在初始化一个接口时并不会先初始化他的父接口 因此一个父接口并不会因为他的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时才会导致该接口的初始化。
public class Test4 {static {System.err.println(Test4 static block);}public static void main(String[] args) {System.err.println(Child.b);}
}/**
* Test4 static block
* Parent static block
* Child static block
* 4
* author coderacademy
*/
class Parent{public static final int a3;static{System.err.println(Parent static block);}
}class Child extends Parent{public static int b4;static{System.err.println(Child static block);}
}如以下示例赋值的执行流程
public class test(){private static int a3;
}//首先在准备阶段java虚拟在内存中为a分配内存int的初始值是0所以此时a的值是0在初始化阶段给赋值为3
//相当于public class test(){private static int a;//从上到下执行static{a3;}
}三、java程序对类的使用方式可分为两种
3.1 主动使用
创建类的实例。比如new Test()访问某个类或者接口的静态变量或者对该静态变量赋值。比如int bTest.a调用类的静态方法。例如Test.doSomething();反射(如class.forName(com.jvm.classloader.test))初始化一个类的子类(对父类的主动使用)。例如
class Parent {
}class Child extends Parent{public static int a4;
}
Child.a8;java虚拟机启动时被表明为启动类的类
程序中对子类的“主动使用”会导致父类被初始化但对父类的“主动使用”并不会导致子类初始化不可能说生成一个Object类的对象就导致系统中所有的子类都会被初始化。
public class Test5 {static{System.err.println(Test5 static block);}public static void main(String[] args) {Parent2 parent;System.err.println(-------------);parentnew Parent2();System.err.println(Parent2.a);System.err.println(Child2.b);}
}/*** Test5 static block
* -------------
* Parent2 static block
* 3
* Child2 static block
* 4
*
*/
class Parent2{public static final int a3;static{System.err.println(Parent2 static block);}}class Child2 extends Parent2{public static int b4;static{System.err.println(Child2 static block);}
}只有当程序访问的静态变量或静态方法确实在当前接口定义时才可以认为是对类或接口的主动使用。
public class Test6 {public static void main(String[] args) {System.err.println(Child3.a);Child3.doSomething();}
}/**
* Parent3 static block
* 3
* doSomething
* author coderacademy
*/
class Parent3{static int a3;static {System.err.println(Parent3 static block);}static void doSomething(){System.err.println(doSomething);}}class Child3 extends Parent3{static{System.err.println(Child3 static block);}
}调用ClassLoader类的loadClass方法加载一个类并不是对类的主动使用不会导致类的初始化。
public class Test7 {public static void main(String[] args) throws ClassNotFoundException {ClassLoader loaderClassLoader.getSystemClassLoader();Class? clazzloader.loadClass(com.jvm.classloader.Z);System.err.println(------------------------);clazzClass.forName(com.jvm.classloader.Z);}
}/**
* ------------------------
*Z static block
* author coderacademy
*/
class Z{static{System.err.println(Z static block);}
}3.2 被动使用
除去以上六种主动使用以外的使用都是被动使用都不会导致类的初始化。所有的java虚拟机实现必须在每个类或接口被java程序首次主动使用时才初始化他们。 类的加载指的是将类的.class文件中的二进制数据读入到内存中将其方法存进运行时数据区的方法区内。然后在堆区创建一个Java.lang.Class对象用来封装在类在方法区内的数据结构。 四、 加载class文件的方式
4.1 本地系统中直接加载
通过网络下载.class文件(java.net.URLClassLoader(URL[] urls))从zipjar等归档文件中加载.class文件从专有数据库中提取.class文件将java源文件动态编译为.class文件。 类的加载的最终产品是位于堆区中的Class对象。Class对象封装了类在方法区内的数据结构并且向Java程序员提供了访问方法区内的数据结构的接口。
4.2、两种类型的类加载器
4.2.1 Java虚拟机自带的加载器
根类加载器(Bootstrap)。使用C编写程序员无法在java代码中获得该类。扩展类加载器Extension使用java代码实现系统类加载器(System)应用加载器使用java代码实现
4.2.2 用户自定义的类加载器
java.lang.ClassLoader的子类用户可以定制类的加载方式 public ClassLoader getClassLoader()方法。针对这个类返回一个个加载器但是某些实现可能会返回null代表根类加载器。如果使用根类加载器加载类那么这个方法就会返回null例
public class BootStrapTest {public static void main(String[] args) throws Exception {Class clazzClass.forName(java.lang.String);ClassLoader loaderclazz.getClassLoader();/*** 打印结果为null*/System.err.println(loader);Class clazz2Class.forName(com.jvm.classloader.C);ClassLoader loader2clazz2.getClassLoader();/*** 打印结果为sun.misc.Launcher$AppClassLoader54a5f709 应用加载器*/System.err.println(loader2);}
}class C{}本文已收录于我的个人博客码农Academy的博客专注分享Java技术干货包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等