一、JVM的工作过程
类加载机制
1、类加载的时机
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载(Loading)、 验证(Verification)、 准备( Preparation)、解析( Resolution)、初始化(Initialization)、 使用(Using) 和卸载(Unloading)七个阶段。其中验证、准备和解析三个部分统称为连接(Linking), 这七个阶段的发生顺序如下所示。
加载(Loading)—> 验证(Verification)—> 准备( Preparation)—>解析( Resolution)~—>初始化(Initialization)—>使用(Using) —>卸载(Unloading)
主动初始化的6种方式:
●创建对象实例: new 对象的时候,会对类的初始化,前提这个类没有被初始化
●调用类的静态属性或为静态属性赋值。
●调用类的静态方法
●通过class 文件反射创建对象。心
●初始化一个类的子类:使用子类的时候先初始化父类
●Java虚拟机启动时被标记为启动类的类:比如main方法所在的类
注: Java 类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到JVM中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。
不会进行初始化的情况
●在同一个类加载器下面只能初始化类-一次,如果已经初始化了就不必要初始化了。
●在编译的时候能确定下来的静态变量(编译常量),不会对类进行初始化。比如final 修饰的静态变量。
2、类加载器
各个加载器的工作责任:
Bootstrap ClassLoader:
负责加载$JAVA_ HOME中jre/lib/rt.jar里所有的class, 由C++实现,不是ClassLoader 子类
Extension ClassLoader:
负责加载java平台中扩展功能的一些jar包,包括$JAVA_ HOME中jre/lib/* .jar或-Djava.ext.dirs指定目录下的jar包
App ClassLoader:
负责加载classpath中指定的jar包及目录中class
3、双亲委派模型
双亲委派模型的工作过程如下:
1.当前类加载器从自己已经加载的类中查询是否此类已经加载,如果已经加载则返回原来已经加载的类
2.如果没有找到,就去委托父类加载器去加载。父类加载器也会采用同样的策略,查看自己已经加载过的类中是否包含这个类,有就返回,没有就委托其父类去加载,直到委托到启动类加载器为止。因为如果父类加载器为空了,就代表使用启动类加载器作为父加载器去加载该类。( 也就是看到的String 类加载器为null)
3.如果启动类加载 器加载失败,就会使用扩展类加载器来尝试加载,继续失败则会使用AppClassLoader来加载,继续失败就会抛出一个异常的 ClassNotFoundException。
使用双亲委派模型的好处:
1、安全性,避免用户自己编写的类动态替换Java的一些核心类。如果不采用双亲委派模型的加载方式进行类的加载工作,那我们就可以随时使用自定义的类来动态替代Java核心API中定义的类。例如:如果黑客将“病毒代码”植入到自定义的String类当中,随后类加载器将自定义的String类加载到JVM上,那么此时就会对JVM产生意想不到“病毒攻击”。而双亲委派的这种加载方式就可以避免这种情况,因为String类已经在启动时就被引导类加载器进行了加载。
2、避免类的重复加载,因为JVM判定两个类是否是同一-个类, 不仅仅根据类名是否相同进行判定,还需要判断加载该类的类加载器是否是同一个类加载器,相同的class文件被不同的类加载器加载得到的结果就是两个不同的类。
4、类加载的详细过程
加载器加载到JVM中步骤:
加载:
查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象。
连接:
连接又包含三块内容:验证、准备、初始化。
1)验证,文件格式、元数据、字节码、符号引用验证;
2)准备,为类的静态变量分配内存,并将其初始化为默认值;
3)解析,把类中的符号引用转换为直接引用
初始化,为类的静态变量赋予正确的初始值。
类加载的过程:
加载(Loading)–>验证(Verification)–>准备 (Prepartion)–>
解析(Resolution)–>初始化(Initialization)–>使用(Using)–>
卸载(Unloading)
二、JVM基础知识
1、Java跨平台性解释
注:当我们提到Java的跨平台性,就会想到JVM,但是能跨平台的是Java程序,而不是JVM。JM是用C/C++开发的,是编译后的机器码,不能跨平台,不同平台下需要安装不同版本的JVM.
我们编写的Java 源码,编译后会生成一种.class 文件,称为字节码文件。Java 虚拟机(JVM) 就是负责将字节码文件翻译成特定平台下的机器码然后运行,也就是说,只要在不同平台上安装对应的JM,就可以运行字节码文件,即运行我们编写的Java 程序。
而这个过程,我们编写的Java程序没有做任何改变,仅仅是通过JVM这一“中间层” ,就能在不同平台上运行,真正实现了“一次编译,到处运行”的目的。
2、JVM介绍
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
3、JDK/JVM/JRE三者关系
JRE(JavaRuntimeEnvironment, Java 运行环境),也就是Java平台。所有的Java程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可。
JDK(Java Development Kit)是程序开发者用来来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中, JRE也是安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。
JVM(JavaVirtualMachine, Java 虚拟机)是JRE的一部分。 它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
4、JVM的生命周期
启动和消亡:
JVM负责运行-一个Java程序。当启动-一个Java程序时,- 一个虚拟机实例也就诞生了。当该程序关闭退出,这个虚拟机实例也就随之消亡。
JVM运行起点:
Java 虚拟机实例通过调用某个初始类的main()方法来运行-一个Java程序。而这个main()方法必须是共有的(public)、静态的(static)、返回值为void,并且接受一个 字符串数组作为参数。任何拥有这样一个main()方法的类都可以作为Java程序运行的起点。
JVM两种线程:
守护线程和非守护线程。守护线程通常是由虛拟机自己使用的,比如执行垃圾收集任务的线程。。但Java程序也可以把创建的线程标记为守护线程。而Java程序中的初始线程-- main()的线程是非守护线程。
只要还有任何非守护线程在运行,那么这个Java程序也在继续运行。当该程序中所有的非守护线程都终止时,虚拟机实例将自动退出。假若安全管理器允许,程序本身也能够通过调用Runtime类或者System类的exit()方法来退出。