大的来讲,当启动一个JAVA程序时,一个JVM即启动了,当程度退出时,JVM也随之消亡。
java
程序退出指:1. 全部的非daemon线程都终止了 设计模式
2. 某个线程调用了类Runtime或者System的exit方法数组
当同时启动多个JAVA程序时,即启动多个JVM,每一个JAVA程序都运行于本身的JVM中。
安全
一个JVM表现为一个进程,如我同时启动了3个JAVA程序,有3个javaw的进程网络
JVM的是经过main()函数做为入口启动的。数据结构
类的生命周期dom
当咱们编写一个java的源文件后,通过编译会生成一个后缀名为class的文件,这种文件叫作字节码文件,只有这种字节码文件才可以在java虚拟机中运行,java类的生命周期就是指一个class文件从加载到卸载的全过程jvm
一个java类的完整的生命周期会经历加载、链接、初始化、使用、和卸载五个阶段,固然也有在加载或者链接以后没有被初始化就直接被使用的状况,如图所示函数
下面依次讲解
spa
1. 装载:
类的加载就是把类的.class文件中的二进制数据读入到内存中。把它存放在java运行时数据区的方法区内,而后在堆区建立一个java.lang.Class对象,用来封装类在方法区内的数据结构,并做为方法区中这个类的信息的入口。
加载来源:
经常使用的:1.本地加载,根据类的全路径名找到相应的class文件,而后从class文件中读取文件内容
2.从jar文件中读取
还有:1.从网络中获取,好比10年前十分流行的Applet。
2.根据必定的规则实时生成,好比设计模式中的动态代理模式,就是根据相应的类自动生成它的代理类。
3.从非class文件中获取,其实这与直接从class文件中获取的方式本质上是同样的,这些非class文件在jvm中运行以前会被转换为可被jvm所识别的字节码文件。
加载方式:
类的加载是由加载器完成的,可分为两种:
1。 java虚拟机自带的加载器,包括启动类加载器,拓展类加载器和系统类加载器。
2。 用户自定义的类加载器,是java.lang.ClassLoader类的子类的实例。用户能够经过它来制定类的加载器。
加载时机:
对于加载的时机,各个虚拟机的作法并不同,可是有一个原则,就是当jvm“预期”到一个类将要被使用时,就会在使用它以前对这个类进行加载。好比说,在一段代码中出现了一个类的名字,jvm在执行这段代码以前并不能肯定这个类是否会被使用到,因而,有些jvm会在执行前就加载这个类,而有些则在真正须要用的时候才会去加载它,这取决于具体的jvm实现。咱们经常使用的hotspot虚拟机是采用的后者,就是说当真正用到一个类的时候才对它进行加载。
加载阶段是类的生命周期中的第一个阶段,加载阶段以后,是链接阶段。
2. 链接:
这个阶段的主要任务就是作一些加载后的验证工做以及一些初始化前的准备工做,能够细分为三个步骤:验证、准备和解析。有一点须要注意,有时链接阶段并不会等加载阶段彻底完成以后才开始,而是交叉进行,可能一个类只加载了一部分以后,链接阶段就已经开始了。可是这两个阶段总的开始时间和完成时间老是固定的:加载阶段老是在链接阶段以前开始,链接阶段老是在加载阶段完成以后完成。
验证:保证被加载的类有正确的内部结构,而且与其余类协调一致。若是jvm检查到错误,就会抛出相应的Error对象。
类验证的内容:
1。 类文件的结构检查,确保类文件听从java类文件的固定格式。
2。 语义检查确保自己符号java语言的语法规定,如验证final类型的类没有子类等
3。 字节码验证:确保字节码能够被jvm安全的执行
4 。二进制兼容的验证,确保相互引用的之间协调一致,如Worker类的goWork方法会调用Car类的run方法,jvm在验证Worker类时,会检查方法区内是否存在car类的run方法,如不存在会抛出NoSuchMethodError错误。
-----疑问:由java编译器生成的java类的二进制数据确定是正确的,为何还要进行类的验证? 由于java虚拟机并不知道某个特定的.class文件究竟是如何被建立的,这个.class文件有多是由正常的java编译器生成的,也多是由黑客特制的,黑客视图经过它来破坏jvm环境。类的验证能提升程序的健壮性,确保程序被安全的执行。
准备:为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。注意,静态变量的初值为jvm默认的初值,而不是咱们在程序中设定的初值。jvm默认的初值是这样的:
* 基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0。
* 引用类型的默认值为null。
* 常量的默认值为咱们程序中设定的值,如咱们定义final static int a = 100,则准备阶段中a的初值就是100。
解析:把常量池中的符号引用转换为直接引用。
符合引用?直接引用? 在Worker类的gotowork方法中会引用Car类run方法。
public void gotowork() { car.run();//这段代码在Worker类的二进制数据中表示为符号引用 }
在Worker类的二进制数据中,包含了一个对Car类的run方法的的符号引用,它有run方法的全名和相关描述符号组成,在解析阶段jvm会把这个符号引用替换为一个指针,该指针指向Car类的run方法在方法区里的内存地址,这个指针就是直接引用,car.run()就是符合引用。
链接阶段完成以后会根据使用的状况(直接引用仍是被动引用)来选择是否对类进行初始化
3. 初始化:
若是一个类被直接引用,就会触发类的初始化。在java中,直接引用的状况有:
1. 经过new关键字实例化对象,访问或设置类的静态变量,调用类的静态方法。
2. 经过反射方式执行以上三种行为。
3. 初始化子类的时候,会触发父类的初始化。
4. 做为程序入口直接运行时(也就是直接调用main方法)。
除了以上四种状况,其余使用类的方式叫作被动引用,而被动引用不会触发类的初始化。请看主动引用的示例代码:
import java.lang.reflect.Field; import java.lang.reflect.Method; class InitClass{ static { System.out.println("初始化InitClass"); } public static String a = null; public static void method(){} } class SubInitClass extends InitClass{} public class Test1 { /** * 主动引用引发类的初始化的第四种状况就是运行Test1的main方法时 * 致使Test1初始化,这一点很好理解,就不特别演示了。 * 本代码演示了前三种状况,如下代码都会引发InitClass的初始化, * 但因为初始化只会进行一次,运行时请将注解去掉,依次运行查看结果。 * @param args * @throws Exception */ public static void main(String[] args) throws Exception{ // 主动引用引发类的初始化一: new对象、读取或设置类的静态变量、调用类的静态方法。 // new InitClass(); // InitClass.a = ""; // String a = InitClass.a; // InitClass.method(); // 主动引用引发类的初始化二:经过反射实例化对象、读取或设置类的静态变量、调用类的静态方法。 // Class cls = InitClass.class; // cls.newInstance(); // Field f = cls.getDeclaredField("a"); // f.get(null); // f.set(null, "s"); // Method md = cls.getDeclaredMethod("method"); // md.invoke(null, null); // 主动引用引发类的初始化三:实例化子类,引发父类初始化。 // new SubInitClass(); } }
上面的程序演示了主动引用触发类的初始化的四种状况。
在类的初始化阶段,只会初始化与类相关的静态赋值语句和静态代码块,也就是有static关键字修饰的信息,而没有static修饰的赋值语句和执行语句在实例化对象的时候才会运行。
在初始化静态赋值语句和静态代码块时,是按顺序执行的。若是有父类则先初始化父类的。
顺序执行指,不论是赋值语句仍是代码块,谁在前就先初始化谁
class InitClass{ // 1 static{ // 2 System.out.println("运行父类静态代码"); // 3 } // 4 public static Field1 f1 = new Field1(); // 5 public static Field1 f2; // 6 }
上面初始化顺序为 3, 5。 6不会初始化,由于其是声明操做,没赋值。
class InitClass{ // 1 public static Field1 f1 = new Field1(); // 2 public static Field1 f2; // 3 static{ // 4 System.out.println("运行父类静态代码"); // 5 } // 6 }
上面初始化顺序为 2, 5。
4. 使用:
类的使用包括主动引用和被动引用,主动引用在初始化的章节中已经说过了,下面咱们主要来讲一下被动引用:
1. 引用父类的静态字段,只会引发父类的初始化,而不会引发子类的初始化。
2. 定义类数组,不会引发类的初始化。
3. 引用类的常量(final,其在加载的准备阶段就赋值好了),不会引发类的初始化。
4. 调用ClassLoader类的loadClass()方法加载一个类,并非对类的主动引用,不会致使类的初始化。当程序调用Class类的静态方法forName("ClassA")时,才是对ClassA的主动使用,将致使classA被初始化,他的静态代码块被执行
见
class InitClass{ static { System.out.println("初始化InitClass"); } public static String a = null; public final static String b = "b"; public final static int c =(int)Math.random();// 非编译时常量 public static void method(){} } class SubInitClass extends InitClass{ static { System.out.println("初始化SubInitClass"); } } public class Test4 { public static void main(String[] args) throws Exception{ // String a = SubInitClass.a;// 引用父类的静态字段,只会引发父类初始化,而不会引发子类的初始化 // String b = InitClass.b;// 使用类的常量不会引发类的初始化,由于在加载解析时就把InitClass.b解析为常量值了. // int c = InitClass.c; // 此处也是类的常量,可是其在运行期才知道,因此是会触发初始化的。但无论在什么阶段初始化,final标示的变量在初始化后就不会变,如不是final的,每次new都会变 SubInitClass[] sc = new SubInitClass[10];// 定义类数组不会引发类的初始化 ClassLoader loader = ClassLoader.getSystemClassLoader();// 得到系统的类加载器 System.out.println("系统类加载器:" + loader); Class objClass = loader.loadClass("InitClass"); // 此处不会初始化InitClass objClass = Class.forName("InitClass");// 此处才会初始化InitClass } }
最后总结一下使用阶段:使用阶段包括主动引用和被动引用,主动饮用会引发类的初始化,而被动引用不会引发类的初始化。
5. 卸载:
在类使用完以后,若是知足下面的状况,类就会被卸载:
1. 该类全部的实例都已经被回收,也就是java堆中不存在该类的任何实例。
2. 加载该类的ClassLoader已经被回收。
3. 该类对应的java.lang.Class对象没有任何地方被引用,没法在任何地方经过反射访问该类的方法。
若是以上三个条件所有知足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了。
以上是类的整个生命周期。