在如下几种情况之下,java虚拟机将结束生命周期:
System.exit()
方法类加载器并不需要某个类被首次主动使用时再加载他。JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError
错误)。如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。类被加载后,就进入连接阶段。
将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。然后要经过一系列的验证。
Wroker
类的gotoWork()
方法中会调用Car类的run()方法。java虚拟机在验证work()类时,会检查在方法区内是否存在Car类的run()方法,假如不存在(当worker类和Car类的版本不兼容,就会出现这种问题),就会抛出NoSuchMethodError
方法。public class Wroker{
public void gotoWork(){
Car car = new Car();
car.run();//这段代码在worker类的二进制数据中表示为符号引用
}
}
在准备阶段,Java虚拟机为类的静态变量分配内存,并设置默认的初始值。例如对于一下Sample类,在准备阶端,将为int类型的静态变量a分配4个字节的内存空间,并且赋予默认值0,为long类型的静态变量b分配8个字节的内存空间,并且赋予默认值0。
public class Sample{
private static int a=1;
public static long b;
static{
b=2;
}
}
在解析阶段,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类的二进制数据中表示为符号引用
}
}
在初始化阶段,java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。在程序中,静态变量初始化有两种途径:
public class Sample{
private static int a=1; //在静态变量声明出进行初始化
public static long b;
public static long c; //但是如果要使用c,则必须进行初始化
static{
b=2; //在静态代码块中进行初始化
}
}
示例:
public class ClassLoaderTest {
public static void main(String[] args) {
Singleton singleton=Singleton.getInstance();
System.out.println("counter1= "+singleton.counter1);
System.out.println("counter2= "+singleton.counter2);
}
}
/**
*程序是从上向下顺序执行
* new Singleton()时,counter1,counter2初始值均为0
* 在通过构造方法Singleton(),均加1.则返回的值counter1,counter2均为1
* 然后再程序在继续向下执行,由于counter1没有显示初始化,则值还是为1
* 但是counter2经过显示初始化后,其值为0
* @author coderacademy
*/
class Singleton{
private static Singleton singleton=new Singleton();//new语句在这是结果为counter1= 1;counter2= 0
public static int counter1;
public static int counter2=0;
//private static Singleton singleton=new Singleton();//new语句在这是结果为counter1= 1;counter2= 1
private Singleton(){
counter1++;
counter2++;
}
public static Singleton getInstance(){
return singleton;
}
}
public class FinalTest {
public static void main(String[] args) {
System.err.println(Test.X);
}
}
/**
* 当X=6/3时,编译时即可算出X=2,即编译时常量,即不需要运行类,所以不打印静态代码块中的内容
*当X=new Random().nextInt(100)时,编译时不能算出X的值,只有运行程序才知道,所以打印结果为:FinalTest static final 2
* @author coderacademy
*/
class Test{
public static final int X=6/3;//打印结果: 2
//public static final int X=new Random().nextInt(100);//打印结果为FinalTest static final 2
static{
System.err.println("FinalTest static final");
}
}
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 a=3;
static{
System.err.println("Parent static block");
}
}
class Child extends Parent{
public static int b=4;
static{
System.err.println("Child static block");
}
}
如以下示例赋值的执行流程:
public class test(){
private static int a=3;
}
//首先在准备阶段java虚拟在内存中为a分配内存,int的初始值是0,所以此时a的值是0;在初始化阶段,给赋值为3
//相当于:
public class test(){
private static int a;
//从上到下执行
static{
a=3;
}
}
new Test()
int b=Test.a
Test.doSomething()
;class.forName("com.jvm.classloader.test")
)class Parent {
}
class Child extends Parent{
public static int a=4;
}
Child.a=8;
程序中对子类的“主动使用”会导致父类被初始化,但对父类的“主动使用”并不会导致子类初始化,不可能说生成一个Object类的对象就导致系统中所有的子类都会被初始化。
public class Test5 {
static{
System.err.println("Test5 static block");
}
public static void main(String[] args) {
Parent2 parent;
System.err.println("-------------");
parent=new 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 a=3;
static{
System.err.println("Parent2 static block");
}
}
class Child2 extends Parent2{
public static int b=4;
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 a=3;
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 loader=ClassLoader.getSystemClassLoader();
Class<?> clazz=loader.loadClass("com.jvm.classloader.Z");
System.err.println("------------------------");
clazz=Class.forName("com.jvm.classloader.Z");
}
}
/**
* ------------------------
*Z static block
* @author coderacademy
*/
class Z{
static{
System.err.println("Z static block");
}
}
除去以上六种主动使用以外的使用都是被动使用,都不会导致类的初始化。所有的java虚拟机实现必须在每个类或接口被java程序首次主动使用时才初始化他们。
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其方法存进运行时数据区的方法区内。然后在堆区创建一个Java.lang.Class对象,用来封装在类在方法区内的数据结构。
java.net.URLClassLoader(URL[] urls)
)Bootstrap
)。使用C++编写,程序员无法在java代码中获得该类。Extension
),使用java代码实现System
),应用加载器,使用java代码实现java.lang.ClassLoader
的子类public ClassLoader getClassLoader()
方法。针对这个类返回一个个加载器,但是某些实现可能会返回null代表根类加载器。如果使用根类加载器加载类,那么这个方法就会返回null;例:public class BootStrapTest {
public static void main(String[] args) throws Exception {
Class clazz=Class.forName("java.lang.String");
ClassLoader loader=clazz.getClassLoader();
/**
* 打印结果为null
*/
System.err.println(loader);
Class clazz2=Class.forName("com.jvm.classloader.C");
ClassLoader loader2=clazz2.getClassLoader();
/**
* 打印结果为:sun.misc.Launcher$AppClassLoader@54a5f709 应用加载器
*/
System.err.println(loader2);
}
}
class C{
}
本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等