介绍一下JVM将何时结束线程的生命
(1)程序执行System.exit()
(2)程序正常结束
(3)程序抛出异常,一直向上抛出,没有try{}catch()finally{}处理。
(4)由于操作系统的异常,导致JVM退出
类的加载过程主要包括三个部分:加载,连接(验证,准备,解析),初始化
简单介绍这几个环节的作用:
加载:就是将class二进制文件由类加载器加载到内存。
连接-验证:主要验证class文件的格式是否正确,语法是否正确,字节码是否正确,二进制是否兼容。
连接-准备:为类的静态变量分配空间,且赋予默认的初始值。
连接-解析:将class文件中符号引用转变成直接引用。
初始化:为类的静态变量赋予正确的初始值,只有在类首次主动使用的时候,才会进行初始化。
详细介绍每一个过程:
加载:通过类加载器将class文件加载到运行时数据区的方法区中,在堆中创建一个对象的java.lang.Class对象,用来封装类在方法区中数据结构。
类加载器包括二种:
(1)java虚拟机自带的类加载器
1)根类加载器(BootStrap) 使用C++编写,如果一个类要返回对象的类加载器,如果是根类加载器加载的话,将会返回null
2)扩展类加载器(extension)使用java编写,主要加载java中扩展类的
3)应用加载器或者系统加载器(System) 使用java编写 ,是我们经常使用的类加载器
(2)用户自定义类加载器(java.lang.ClassLoader)
我们自定义类加载器需要继承ClassLoader这个类,实现特定的加载。
加载Class文件的方式有哪些?
(1)本地直接加载
(2)通过网络下载.class文件(URLClassLoader(URL[] urls)
)
(3)加载zip,jar文件中的.class文件
类加载器并不需要等到某个类被首次主动使用时在加载它,JVM规范规定JVM可以预测加载某一个类,如果这个类出错,但是应用程序没有调用这个类,JVM也不会报错,如果调用这个类的话,JVM才会报错,(LinkAgeError错误)
2、连接:将已经读入到内存类的二进制数据合并到虚拟机运行时环境中去。
验证:主要验证4个方面,保证加载的类时完全正确的。
(1)class文件的结构完全正确
(2)语法检查,保证java的语法的正确性
(3)java字节码的正确性以及操作数的正确性
(4)保证二进制是兼容的,如果使用JDK1.6编写,部署在JDK1.5中那么就会出现二进制不兼容的特性
准备:为类的静态属性分配空间,并且赋值为默认的初始值,int为0,引用为null
解析:就是将类中的符号引用转换成直接引用,
运行时数据区中的方法去中的运行时常量池记录着类的方法,属性的内存地址。
初始化:为类的静态变量赋予正确的初始值,只有在类首次主动使用的时候,才会进行初始化。
package yy
public class Test{
public static int a=0;
public static void hello(){
System.out.prinltn("hello world");
}
public static void main(String[] args){
}
}
public class Test1 extends Test{
}
什么叫做类的首次主动使用呢?以下的6个方式都是主动使用:
(1)创建对象的实例(Test test=new Test())
(2)调用类的静态属性或者给静态属性赋值(Test.a)
(3)调动类的静态方法(Test.hello())
(4)通过Class文件反射创建对象(Class.forname("yy.Test")).newInstance();
(5)初始化一个类的子类(Test1 test=new Test1())
(6)Java虚拟机启动时被标记为启动类的类(Test 类)
类初始化的步骤:1、如果这个类没有被加载和连接的话,那就先进行加载和连接。
2、如果这个类有父类,并且这个父类没有被初始化,则先初始化父类
3、假如类中存在初始化语句,依次执行初始化语句。
对于接口的初始化有特性的规定:
(1)在初始化一个类时,并不会先初始化它所实现的接口。
(2)在初始化一个接口时,并不会先初始化它的父接口。
因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致接口的初始化。
调用ClassLoader类的LoadClass方法加载一个类,并不是对类的主动使用,不会到类的初始化。
总之:只有当程序访问的静态变量或者静态方法确实在当前类或者接口中定义时,才可以认为是对类或接口的主动使用。
类加载的顺序如果是一个类的话,加载的顺序是:
类的静态属性
类的静态代码块
类的非静态属性
类的非静态代码块
构造方法
如果一个类有父类,加载的顺序是:
父类的静态属性
父类的静态代码块
子类的静态属性
子类的静态代码块
父类的非静态属性
父类的非静态代码块
父类的构造方法
子类的非静态属性
子类的非静态代码块
子类的构造方法。
package classloader;
class Parent{
static int x=3;
static{
System.out.println("parent static block");
}
}
class Child extends Parent{
static int y=4;
static{
System.out.println("child static block");
}
}
public class Test3 {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(Child.y);
}
}
结果如下
parent static block
child static block
4
-----------------------------------------------------------------
package classloader;
class Parent2{
static int x=3;
static{
System.out.println("Parent2 static block");
}
}
class Child2 extends Parent2{
static int y=4;
static{
System.out.println("child2 static block");
}
}
public class Test4 {
/**
* @param args
*/
static{
System.out.println("Test4 static block");
}
public static void main(String[] args) {
Parent2 parent;
parent=new Parent2();
System.out.println(parent.x);
System.out.println(Child2.y);
}
}
Test4 static block
Parent2 static block
3
child2 static block
4
-----------------------------------------------------------------
package classloader;
class Parent3{
static int x=3;
static{
System.out.println("Parent3 static block");
}
}
class Child3 extends Parent3{
static{
System.out.println("child2 static block");
}
}
public class Test5 {
/**
* @param args
*/
static{
System.out.println("Test5 static block");
}
public static void main(String[] args) {
System.out.println(Child3.x);
}
}
结果
Test5 static block
Parent3 static block
3