JVM虚拟机
- Java虚拟机有自己完善的硬件架构(处理器、堆栈、寄存器等)和指令系统
- Java虚拟机是一种能运行Java bytecode的虚拟机
- JVM并非专属于Java语言,只要生成的编译文件能匹配JVM对载入编译文件格式要求,任何语言都可以交由JVM运行,比如Scala、Groovy、Fantom等,见Java虚拟机*
- JVM虚拟机除了Sun开发的HotSpot外,还有BEA、IBM、微软、等公司都有开发。见《深入理解Java虚拟机(第二版)》
- 查看自己用的JVM:cmd->java -version。我的是“Java HotSpot 64-Bit Server VM(build 25.92-b14 mixed mode)”。
JVM和类
- 当调用java命令运行一个java程序时,就启动了一个Java虚拟机进程,不论该程序多么复杂,占用多大的内存,始终处于该进程中
- JVM进程合适终止?
- 程序运行结束
- 程序执行过程中,遇到未捕获的异常或者错误
- 程序运行中调用了System.exit()或者Runtime.getRuntime().exit(),退出了虚拟机
- 程序所在平台强制结束了JVM进程
- 虚拟机何时加载一个类?
- 第一次使用该类时
- 预加载
类的加载、连接、初始化
- 当系统要使用某个类的时候,会将该类初始化,初始化依次包括加载、连接、初始化三步,一般说类的加载或类的初始化就包含了这三个步骤。
- 类的加载
- 概念:类加载器将.class字节码文件读入内存,并创建一个对应的java.lang.Class对象。加载进内存的每个类都有至少一个与之对象的Class对象
- 类加载器有哪些?
- Bootstrap ClassLoader:根类加载器,只有这个加载器不是用java语言写的;并且只有这个加载器不是ClassLoader的子类的实例
- Extension ClassLoader:扩展类加载器
- System ClassLoader:系统类加载器
- 自定义类加载器:继承ClassLoader抽象类
- 可以从哪些地方加载.class字节码文件?
- 来源于本地文件系统
- 来自于jar包中
- 通过网络加载class文件
- 把一个Java源文件动态编译,并执行加载。
这个不懂?
- 类的连接
- 概念:负责把二进制数据合并到JRE中
- 验证阶段:验证被加载的类是否具有正确的内部结构,并和其他类协调一致
- 准备阶段:为类变量分配内存,并设置默认初始值
- 解析阶段:将类的二进制数据中的符号引用替换为直接引用
- 类的初始化
- 类的初始化,主要是对类变量的初始化
- 如果这个类还没有被加载和连接,那么先加载并连接。这个主要是针对父类
- 如果这个类的直接父类还没有初始化,那就先初始化其父类
- 依次执行类中的初始化语句
- 因此最先被初始化的类总是java.lang.Object。参见:0023 Java学习笔记-面向对象-初始化代码块
- JVM在何时初始化一个类
- 一般说来,在JVM首次使用一个类时,对该类给予初始化,具体包含以下六种情况
- 创建一个类的实例时:new操作符;反射;反序列化
- 调用一个类的类方法
- 访问一个类或接口的类变量
- 用反射方式来强制创建一个类的Class对象:Class.forName("className");注意ClassLoader的loadClass()方法只会加载而不会初始化该类
- 某个类的子类被初始化时,该类也会被初始化
- 直接用java.exe运行某个主类,先初始化该主类。
不懂?
- 不会初始化的情况
- 宏变量:static final变量,并且能在编译阶段就确定它的值。一个类使用另一个类的宏变量,另一个类不会被初始化。
-
示例代码01
:访问宏变量不会初始化它所在的类
- 一般说来,在JVM首次使用一个类时,对该类给予初始化,具体包含以下六种情况
示例代码01:访问宏变量不会初始化它所在的类
package testpack;
public class Test1{
public static void main(String[] args){
System.out.println(A.num); //输出8,没有输出“A类被初始化”,A类没有被初始化
System.out.println(B.num); //输出“B类被初始化”;21;B类被初始化
}
}
class A{
static{
System.out.println("A类被初始化");
}
public static final int num=8; //num的值在编译阶段就能确定下来
}
class B{
static{
System.out.println("B类被初始化");
}
public static final int num=8+Integer.valueOf(13); //num的值不能在编译阶段确定下来
}
类的加载器
- 如何标识一个被载入JVM的类?
- 类名+包名+类加载器名
- 类加载器的层次结构
- Bootstrap ClassLoader:根类加载器,
- 没有父加载器;由C++写成,其他加载器都是Java写成;
- 负责加载
Java核心类库
;也就是系统属性sun.boot.class.path
的值表示的路径下的包; - (HotSpot?)可以在java.exe中用-D参数指定系统属性sun.boot.class.path的值,从而加载指定的附加类;
-
见示例02
:获取根类加载器加载的核心类库
- Extension ClassLoader:扩展类加载器
- 没有父加载器(实际上就是根类加载器?);由Java写成,是ClassLoader的子类;
- 负责加载扩展目录JAR包中的类,即
系统属性java.ext.dirs
或者%JAVA_HOME%/jre/lib/ext
; - 因此可以把自己开发的类,打包成JAR包,放在该目录下
-
见示例03
:扩展类加载器的加载目录,父加载器
- System ClassLoader:系统类加载器
- 父加载器是扩展类加载器;由Java写成,是ClassLoader的子类;
- 也是用户自定义的类加载器的默认父加载器,如果不特别指定的话。
- 负责加载
系统属性java.class.path
、系统CLASSPATH环境变量
指定的目录中的类; - Java命令的-classpath参数可以临时指定CLASSPATH的路径
-
示例04
:系统类加载器的加载路径
- 自定义类加载器:
- 继承ClassLoader抽象类。
- 默认的父加载器是系统类加载器;在自定义的时候,可以在一个方法中指定。见java的类加载器ClassLoader
- 上面说的
父加载器
,并不是类的继承关系,而是加载器间实例间的关系,就是说在一个加载器中可以定义它的父加载器。
- Bootstrap ClassLoader:根类加载器,
示例代码02:根类加载器加载的核心类库
package testpack;
import java.net.URL;
public class Test1{
public static void main(String[] args)throws ClassNotFoundException{
URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs(); //获取根类加载器加载的全部URL数组
for (int i=0;i<urls.length;i++){
System.out.println(urls[i].toExternalForm());
}
System.out.println("-------------下面是系统属性(sun.boot.class.path)的值---------------");
System.out.println(System.getProperty("sun.boot.class.path"));
}
}
输出:
file:/C:/Java/jdk1.8.0_92/jre/lib/resources.jar
file:/C:/Java/jdk1.8.0_92/jre/lib/rt.jar //核心类库java.lang.*位于该jar包中
file:/C:/Java/jdk1.8.0_92/jre/lib/sunrsasign.jar
file:/C:/Java/jdk1.8.0_92/jre/lib/jsse.jar
file:/C:/Java/jdk1.8.0_92/jre/lib/jce.jar
file:/C:/Java/jdk1.8.0_92/jre/lib/charsets.jar
file:/C:/Java/jdk1.8.0_92/jre/lib/jfr.jar
file:/C:/Java/jdk1.8.0_92/jre/classes
-------------下面是系统属性(sun.boot.class.path)的值---------------
file:/C:/Java/jdk1.8.0_92/jre/lib/resources.jar
file:/C:/Java/jdk1.8.0_92/jre/lib/rt.jar
file:/C:/Java/jdk1.8.0_92/jre/lib/sunrsasign.jar
file:/C:/Java/jdk1.8.0_92/jre/lib/jsse.jar
file:/C:/Java/jdk1.8.0_92/jre/lib/jce.jar
file:/C:/Java/jdk1.8.0_92/jre/lib/charsets.jar
file:/C:/Java/jdk1.8.0_92/jre/lib/jfr.jar
file:/C:/Java/jdk1.8.0_92/jre/classes
示例03:扩展类加载器的加载目录,父加载器
package testpack;
public class Test1 {
public static void main(String[] args){
ClassLoader systemLoader=ClassLoader.getSystemClassLoader(); //获取系统类加载器
System.out.println("这是系统类加载器: "+systemLoader); //输出系统类加载器
ClassLoader extensionLoader=systemLoader.getParent(); //获取扩展类加载器
System.out.println("这是扩展类加载器: "+extensionLoader); //输出扩展类加载器
System.out.println("扩展类的父加载器: "+extensionLoader.getParent()); //获取扩展类加载器的父加载器null
System.out.println("扩展类的加载路径: "+System.getProperty("java.ext.dirs")); //获取系统属性java.ext.dirs的值
}
}
输出:
这是系统类加载器: sun.misc.Launcher$AppClassLoader@73d16e93 //说明系统类加载器是AppClassLoader的实例
这是扩展类加载器: sun.misc.Launcher$ExtClassLoader@15db9742 //说明扩展类加载器是ExtClassLoader的实例
扩展类的父加载器: null //以上二者都是URLClassLoader的实例
扩展类的加载路径: C:\Java\jdk1.8.0_92\jre\lib\ext;C:\windows\Sun\Java\lib\ext
示例04:系统类加载器的加载路径
package testpack;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.lang.ClassLoader;
public class Test1 {
public static void main(String[] args)throws IOException{
ClassLoader systemLoader=ClassLoader.getSystemClassLoader(); //获取系统类加载器
System.out.println("系统类加载器: "+systemLoader); //输出系统类加载器
Enumeration<URL> eml=systemLoader.getResources(""); //遍历其加载路径
while(eml.hasMoreElements()){
System.out.println(eml.nextElement());
}
System.out.println("系统属性java.class.path的值: "+System.getProperty("java.class.path"));
}
}
输出:
系统类加载器: sun.misc.Launcher$AppClassLoader@73d16e93
file:/D:/JavaWorkspace/Test/bin/
系统属性java.class.path的值: D:\JavaWorkspace\Test\bin
类的加载机制
- 全盘负责制:
- 当一个类加载器加载一个类时,该类依赖和引用的其他类,也有这个类加载器负责
- 父类委托:
- 先让父加载器加载这个类,如果加载不了,再从自己的类路径中加载。
不懂?
,一般说来,不同的加载器的加载路径都不同吗?
- 先让父加载器加载这个类,如果加载不了,再从自己的类路径中加载。
- 缓存机制:
- 要使用一个类时,先从缓存中查找,如果已经有了,就不另行加载,直接返回;如果没有,再加载
类加载器的8个步骤
- 要加载的类是否已被加载?
- 否
- 如果父类加载器不存在,那么请求使用根类加载器加载
- 成功:返回Class对象
- 失败:抛出ClassNotFoundException
- 如果父类加载器存在,请求使用父加载器加载
- 成功:返回Class对象
- 失败:当前类加载器载入类
- 当前类寻找加载
- 成功:返回Class对象
- 失败:抛出ClassNotFoundException
- 当前类寻找加载
- 如果父类加载器不存在,那么请求使用根类加载器加载
- 是:返回其Class对象
- 否
- 总的来说,先在缓存中找;再让父加载器加载,一直到没有父加载器,就用根加载器加载,如果父加载器失败,那么自己加载,还失败,那就抛出异常:ClassNotFoundException
自定义类加载器
- 继承结构
- java.lang.Object
- java.lang.ClassLoader
- java.security.SecureClassLoader
- java.net.URLClassLoader
- sun.misc.Launcher$ExtClassLoader //扩展类加载器就是这个类的实例
- sun.misc.Launcher$AppClassLoader //系统类加载器就是这个类的实例
- java.net.URLClassLoader
- java.security.SecureClassLoader
- java.lang.ClassLoader
- 所有的类加载器中,除了根加载器外,都是ClassLoader子类的实例。
- ClassLoader类包含了一些protected和static方法
- ClassLoader的主要方法:
- protected Class<?>
loadClass
(String name,boolean resolve);- 根据指定名称加载类,返回指定类的Class对象
- 自定义类加载器时,最好不要重写该方法
- 执行步骤:
- 用findLoadedClass(String)检查是否已加载
- 在父加载器上调用loadClass()方法,若父加载器为null,则用根加载器加载
- 用findClass(String)查找类
- protected Class<?>
findClass
(String name);- 根据名称查找类
- 自定义类加载器时,一般只重写这个方法
- protected final Class<?>
defineClass
(String name, byte[] b, int off, int len)- 将指定的字节码文件读入byte数组,转换为Class对象
- final,不可重写
- protected final Class<?>
findSystemClass
(String name)- 从本地文件系统装入字节码文件
- static ClassLoader
getSystemClassLoader
()- 返回系统类加载器
- final ClassLoader
getParent
()- 返回该加载器的父加载器
- final,不可重写
- protected final void
resolveClass
(Class<?> c)- 链接指定的类c
- 不懂?
- protected final Class<?>
findLoadedClass
(String name)- 如果已经加载了名为name的类,则返回其Class实例
- protected Class<?>
其他
- 关于类加载器实例、类加载器实例的类、Class对象、普通类、普通类的对象
- 简单的说,加载器是实例,他们的类有AppClassLoader和ExtClassLoader,都是URLClassLoader的子类
- ExtClassLoader类的实例就是扩展类加载器,ExtClassLoader自身在加载的时候也有Class对象
- AppClassLoader类的实例就是系统类加载器,AppClassLoader自身在加载的时候也有Class对象
- ExtClassLoader和AppClassLoader二者没有继承关系,只是前者的实例是后者的实例的父加载器
- 系统类加载器,加载一个普通类,创建对应的Class对象,连接,初始该普通类,再创建实例
- 看下面的示例代码
- 什么情况下需要自定义类加载器?见JVM——自定义类加载器 CSDN
- 参考:深入探讨Java类加载器 IBM 成富
package testpack;
public class Test1 {
public static void main(String[] args){
A a=new A(); //创建普通类A的实例a
a.show(); //调用a的show()方法
ClassLoader cl=a.getClass().getClassLoader(); //通过实例a得到A类的Class对象,再获得该Class对象的加载器
System.out.println("实例a的类A的Class对象的加载器是: "+cl); //输出加载器,是AppClassLoader类的实例
Class appClazz=cl.getClass(); //通过系统类加载器实例获得它的类的Class对象
while(appClazz!=null){
System.out.println(appClazz); //输出该Class对象
appClazz=appClazz.getSuperclass(); //获得这个Class对象的父类的Class对象,一直到Object没有父类
}
}
}
class A{
public void show(){
System.out.println("这是A类的实例");
}
}
参考资料: