0032 Java学习笔记-类加载机制-初步
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类的实例");
}
}
参考资料:
0032 Java学习笔记-类加载机制-初步的更多相关文章
- JAVA 学习笔记 - 反射机制
1. JAVA反射机制的概念 2. 怎样实例化一个 Class对象 Class.forName(包名.类名); 对象.getClass(); 类.class; ================== ...
- Java学习笔记-反射机制
Java反射机制实在运行状态时,对于任意一个类,都能够知道这个类的属性和方法,对于任意一个对象,都能够调用他的任意一个属性和方法 获取Class对象的三种方式 Object类中的getClass()方 ...
- Java学习笔记--异常机制
简介 在实际的程序运行过程中,用户并不一定完全按照程序员的所写的逻辑去执行程序,例如写的某个模块,要求输入数字,而用户却在键盘上输入字符串:要求打开某个文件,但是文件不存在或者格式不对:或者程序运行时 ...
- java学习笔记09--反射机制
java学习笔记09--反射机制 什么是反射: 反射是java语言的一个特性,它允许程序在运行时来进行自我检查并且对内部的成员进行操作.例如它允许一个java的类获取他所有的成员变量和方法并且显示出来 ...
- java学习笔记13--反射机制与动态代理
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note13.html,转载请注明源地址. Java的反射机制 在Java运行时环境中,对于任意 ...
- 《Java学习笔记(第8版)》学习指导
<Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...
- Java学习笔记4
Java学习笔记4 1. JDK.JRE和JVM分别是什么,区别是什么? 答: ①.JDK 是整个Java的核心,包括了Java运行环境.Java工具和Java基础类库. ②.JRE(Java Run ...
- 20145230《java学习笔记》第九周学习总结
20145230 <Java程序设计>第9周学习总结 教材学习内容 JDBC JDBC简介 JDBC是用于执行SQL的解决方案,开发人员使用JDBC的标准接口,数据库厂商则对接口进行操作, ...
- java学习笔记之基础篇
java选择语句之switch //switch可以用于等值判断 switch (e) //int ,或则可以自动转化成int 的类型,(byte char short)枚举jdk 7中可以防止字 ...
随机推荐
- 1Z0-053 争议题目解析470
1Z0-053 争议题目解析470 考试科目:1Z0-053 题库版本:V13.02 题库中原题为: 470.Which NLS parameter can be used to change the ...
- 前端开发编辑器(notepad++、sublime text)
1.Notepad++ 正则替换: 如<td>第三节</td> 替换成<td><input type="text" value=" ...
- CLR中的程序集加载
CLR中的程序集加载 本次来讨论一下基于.net平台的CLR中的程序集加载的机制: [注:由于.net已经开源,可利用vs2015查看c#源码的具体实现] 在运行时,JIT编译器利用程序集的TypeR ...
- KVM的前世今生
1.虚拟化技术的演变过程:软件模拟.虚拟化层翻译.容器虚拟化三个阶段 (1)软件模拟的技术方式 软件模拟是通过软件完全模拟CPU.网卡.芯片组.磁盘等计算机硬件,因为是软件模拟,所以理论上可以模拟任何 ...
- Azure Backup (2) Azure备份服务
<Windows Azure Platform 系列文章目录> 本文介绍的是国内由世纪互联运维的Azure China. 本文介绍的Azure管理界面是Classic Model,网址:h ...
- .NET 对象生命周期
GC 垃圾回收 .NET Framework 的垃圾回收器管理应用程序的内存分配和释放.每次您使用 new 运算符创建对象时,运行库都从托管堆为该对象分配内存.只要托管堆中有地址空间可用,运 ...
- Myeclipse启动报错: Invalid 'log4jConfigLocation' parameter
java.lang.IllegalArgumentException: Invalid 'log4jConfigLocation' parameter: class path resource [lo ...
- eclipse 突然 一直在loading descriptor for XXX (XXX为工程名)
问题: eclipse 启动后,啥也不干,就一直在loading descriptor for XXX (XXX为工程名),,其他什么操作都不能操作. 如下图所示,保存文件也无法保存. 这个怎么办? ...
- 转:Java Web应用中调优线程池的重要性
不论你是否关注,Java Web应用都或多或少的使用了线程池来处理请求.线程池的实现细节可能会被忽视,但是有关于线程池的使用和调优迟早是需要了解的.本文主要介绍Java线程池的使用和如何正确的配置线程 ...
- mariadb 10.2.3支持oracle execute immediate语法
在之前的版本包括oracle mysql/percona server版本中,所有的动态SQL都需要通过prepare执行,如下: "; execute stmt; deallocate p ...