每一次运行程序都会启动一个Java虚拟机,程序依靠jvm运行,jvm结束,程序结束
<1>.java虚拟机结束生命周期的情况:
a.System.exit();
b.程序正常结束
c.程序异常或错误非正常退出
d.操作系统错误导致虚拟机结束
<2>.类的加载,连接,初始化
a.加载:查找并加载类的二进制数据
class文件中的二进制数据从硬盘到内存中,将其存放到方法区中,
然后堆区创建java.lang.Class对象,用来封装类在方法内的数据结构,Class时整个反射的接口,通过它获取类的数据结构
·加载.class文件方式:
-文件系统直接加载
-网络下载.class文件
-zip,jar中加载.class文件
-专用数据库提取
-将java源文件.java文件动态编译为.class文件(比如web应用等,spring)
(类加载的最终产品是位于堆区的Class对象)
b.连接:验证:确保被加载的类的正确性,(恶意用户手工生成class字节码文件,平时javac生成不会出错)
准备:为类的静态变量分配内存,并将其初始化为默认值。默认值:整形0,boolean false,引用null
解析:符号引用转换为直接引用
c.初始化:为类的静态变量赋予正确的初始值,即用户赋予的值
public class test{
private static int a = 3;
}
//相当于
public class test{
private static int a;//连接步骤值为0
static{
a = 3;//初始化时候赋值3
}
})
<3>.java对类的使用方式分为两种:
--主动使用
·创建类的实例(new test())
·访问类或者接口的静态变量,或复制(int b = test.a)
·调用类的静态方法(test.dosomething())
·反射(Class.forName("testclass"))
·初始化类的子类(b extends a;b b1 = new b());
·jvm启动时被表明启动的类,比如包含main的类
--被动使用
除了上述六种外都叫做对垒的被动使用,都不会导致类的初始化
!!!!所有的java虚拟机实现必须在每个类或接口被java虚拟机主动使用时才初始化他们
<4>.举个栗子:
静态块的代码编写顺序导致静态变量初始化后值不同:
class Singleton{
//位置1
//private static Singleton singleton = new Singleton();
//准备阶段值为null,初始化阶段指向实例
public static int counter1;
//位置1:准备阶段0,singleton初始化时赋值1
//位置2:准备阶段0,singleton初始化时赋值1
public static int counter2 = 0;
//位置1:准备阶段0,初始化实例时候为1,初始化counter2时候为0,最终为0
//位置2:准备阶段0,初始化时为0,singleton初始化时为1,最终为1
//位置2
private static Singleton singleton = new Singleton();
private Singleton() {
counter1++;
counter2++;
}
public static Singleton getInstance(){
return singleton;
}
}
public class t1 {
public static void main(String[] args){
Singleton singleton = Singleton.getInstance();
System.out.println("counter1=" + singleton.counter1);
System.out.println("counter2=" + singleton.counter2);
}
}
<1>.java虚拟机自带的加载器
--根类加载器(Bootstrap, c++编写的,java中无法获得)
--扩展类加载器 (Extend)
--系统/应用加载器 (System)
<2>.用户自定义的类加载器:
a.都是java.lang.Class的子类
<3>.类的加载
!!!!类加载器不需要等到某各类被首次主动使用时才加载他
-jvm允许类加载器在预料到某各类将要被使用时预先加载他,如果出现错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误,
比如jdk1.6编译的.class放到jdk1.5可能报错)
-若这个类一直没有被程序主动使用,那么类加载器就不会报告错误
<4>.类的验证
将读入内存类的二进制数据合并到虚拟机的运行环境中去
类的验证的内容:
-类文件结构检查,确保类文件遵从java文件的固定格式
-语义检查,确保类本身符合java语言规定,比如验证final类型的类没有子类
-字节码验证
-二进制兼容性测试(jdk版本等)
<5>.类的准备
jvm为类的静态变量分配内存,并设置默认的初始值。
public class Sample{
private static int a = 1;
private static long b;
static{
b = 2;
}
}
准备阶段为a准备4个字节空间并分配初值0,b8个字节
<6>.类的解析
java虚拟机吧类的二进制中的符号引用替换为直接引用。如worker类的gotoWork()方法引用Car类的run()方法
在解释阶段,把符号引用替换为指针,指针指向了Car中run方法在方法区的内存位置
<7>.类的初始化
初始化阶段执行类的初始化语句,为类的静态变量赋予初始值,按照顺序执行
a.在静态变量声明处初始化
b.在静态代码块中初始化
父亲委托机制:类加载器加载累到jvm中,jdk1.2开始使用父亲委托机制,除了根类加载器外,其他的类加载器都只有一个父亲加载器。当Loader1请求加
载sample类时,
若父亲加载器能够加载,则使用父亲加载器,否则是有自身加载器加载。
jvm自带三种加载器:
bootstrap:没有父加载器,加载虚拟机的核心类库,比如java.lang包等。以来底层操作系统,没有继承java.lang.ClassLoader。属于虚拟机实现的一部分。
extend:父加载器时bootstrap,加载jdk目录下jre\lib\ext或者java.et.dirs下加载类库。
system:父加载器是extend,用户自定义的类加载器的默认父加载器,从classpath下加载类,时java.lang.ClassLoader的子类。
(父子加载器并非继承关系,也就是说子加载器不一定继承父加载器,父子加载器可能是同一个加载器的两个实例,但是是父子加载器)
用户自定义的类加载器必须继承ClassLoader。父子加载器形成树形结构,除了根类加载器,其他的有且仅有一个父加载器。
(自定义loader尝试加载sample,若加载了,直接返回sample类的引用,否则,loader请求system加载,system请求extend,extend请求bootstrap。bootstrap
尝试加载,不行抛给extend加载,以此类推。若都无法加载,抛出classnotfoundException)
定义类加载器:成功加载sample类的加载器
初始化加载器:定义类加载器的子加载器都称作初始化加载器
用户自定义类加载器时若没有指定父加载器,则默认将系统类加载器作为他的父加载器。
优点:提高软件的安全性,在这种机制下,用户自定义的类加载器不可能加载应该由父加载器加载的可靠品类,从而防止不可靠甚至恶意的代码代替父加载器加载
可靠代码。
---命名空间:每个加载器都有自己的命名空间,由该加载器和所有父加载器所加载的类构成。同一个命名空间中,不会出现类的完整名字相同的两个类。
---运行时包:有同一个类加载器加载的属于相同包的类组成运行时报。定义类加载器和包名都相同则同一个运行时包。只有属于同一个运行时包的类才能相互访问包
可见的类和类成员。比如用户自定义包com.self.spy和核心类库java.lang.*,不同的运行时包,保证了com.self.spy不能访问java.lang.*中的成员。
自己写类加载器需要继承java.lang.Classloader并且重写findClass方法。
尝试构造这样的一个结构:(案例Sample, Dog, MyClassLoader)
bootstrap
| \
extend loader3
|
system
|
loader1
|
loader2
其中,loader1指定加载路径为serverlib(Dog, Sample),loader2指定加载路径为clientlib(空),loader3为otherlib(Dog, Sample)。在syslib下执行
java MyClassLoader可以看到执行结果。可以直接运行MyClassLoader,相当于loader2可以依靠System加载Dog和Sample
5.类的卸载
由jvm自带的类加载器加载的类在jvm整个生命周期内不会被卸载,用户自定义的类加载器加载的类是可以被卸载的。loader = null;clazz = null;sample = null;