1 类的加载

类的加载指的是将类的 .class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构。

类的加载的最终产品是位于堆区中的 Class对象, Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

类加载器并不需要等到某个类被首次主动使用时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了 .class 文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

加载 .class 文件的方式:

  • 从本地系统中直接加载
  • 通过网络下载.class文件
  • 从zip,jar等归档文件中加载 .class 文件
  • 从专有数据库中提取 .class 文件
  • 将Java源文件动态编译为 .class 文件

执行顺序

  • 类加载子系统负责从文件系统或是网络中加载 .class 文件,class文件在文件开头有特定的文件标识。
  • 把加载后的class类信息存放于方法区,除了类信息之外,方法区还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
  • ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定;
  • 如果调用构造器实例化对象,则该对象存放在堆区;

2 类的生命周期

  • 加载(Loading)
  • 连接(Linking)
    • 验证(Verification)
    • 准备(Preparation)
    • 解析(Resolution)
  • 初始化(Initialization)
  • 使用(Using)
  • 卸载(Unloading)

加载阶段(Loading)

此阶段用于查到相应的类(通过类名进行查找)并将此类的字节流转换为方法区运行时的数据结构,然后再在内存中生成一个能代表此类的 java.lang.Class 对象,作为其他数据访问的入口。

(需要注意的是加载阶段和连接阶段的部分动作有可能是交叉执行的,比如一部分字节码文件格式的验证,在加载阶段还未完成时就已经开始验证了。)

  • 预加载:虚拟机启动时加载,加载的是 JAVA_HOME/lib/ 下的 rt.jar 下的 .class 文件 (可以写一个空的 main 函数,设置虚拟机参数为 -XX:+TraceClassLoading 来获取类加载信息)
  • 运行时加载:虚拟机在用到一个 .class 文件的时候,会先去内存中查看一下这个 .class 文件有没有被加载,如果没有就会按照类的全限定名来加载这个类。
    • 获取 .class 文件的二进制流 (没有规定二进制字节流要必须从哪里来或者怎么来,所以留下了可扩展的空间)
    • 将类信息、静态变量、字节码、常量这些 .class 文件中的内容放入方法区中
    • 在内存中生成一个代表这个 .class 文件的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。一般这个Class是在堆里的,不过HotSpot虚拟机比较特殊,这个Class对象是放在方法区中的

验证阶段(Verification)

此步骤主要是为了验证字节码的安全性,如果不做安全校验的话可能会载入非安全或有错误的字节码,从而导致系统崩溃,它是 JVM 自我保护的一项重要举措。

验证的主要动作大概有以下几个:

  • 文件格式校验包括常量池中的常量类型、Class 文件的各个部分是否被删除或被追加了其他信息等;
  • 元数据校验包括父类正确性校验(检查父类是否有被 final 修饰)、抽象类校验等;
  • 字节码校验,此步骤最为关键和复杂,主要用于校验程序中的语义是否合法且符合逻辑;
  • 符号引用校验,对类自身以外比如常量池中的各种符号引用的信息进行匹配性校验。

准备阶段(Preparation)

此阶段是用来初始化并为类中定义的静态变量分配内存的,这些静态变量会被分配到方法区上。

HotSpot 虚拟机在 JDK 1.7 之前都在方法区,而 JDK 1.8 之后此变量会随着类对象一起存放到 Java 堆中。

  • 这时候进行内存分配的仅仅是类变量(被static修饰的变量),而不是实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆中
  • 这个阶段赋初始值的变量指的是那些不被 final 修饰的 static 变量
    • 比如 public static int value = 123,在准备阶段过后是 0 而不是 123 ,给 value 赋值为123的动作将在初始化阶段才进行
    • 比如 public static final int value = 123; ,在准备阶段,虚拟机就会给 value 赋值为123。

解析阶段(Resolution)

此阶段主要是用来解析类、接口、字段及方法的,解析时会把符号引用替换成直接引用。

  • 符号引用:是指以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可;
  • 直接引用:是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。

符号引用和直接引用有一个重要的区别:使用符号引用时被引用的目标不一定已经加载到内存中;而使用直接引用时,引用的目标必定已经存在虚拟机的内存中了。

初始化(Initialization)

初始化阶段 JVM 就正式开始执行类中编写的 Java 业务代码了。到这一步骤之后,类的加载过程就算正式完成了。

3 Java类加载器

  • 启动类加载器(BootStrapClassLoader):C/C++ 实现
  • 其他类加载器(Extension Class Loader、System Class Loader、User-Defined ClassLoader):Java 实现,规范定义自定义加载器是指派生于抽象类 ClassLoder 的类加载器

启动类加载器(Bootstrap ClassLoader)

C/C++ 实现,嵌套在 JVM 中,并不继承自 java.lang.ClassLoader,没有父加载器。用来加载Java的核心类库(JAVA_HOME/jre/lib/rt.jar、resource.jar 或 sun.boot.class.path 路径下的内容),用于提供JVM自身需要的类。

扩展类加载器(Extension ClassLoader)

Java 实现,派生于 ClassLoader。由 sun.misc.Launcher$ExtClassLoader 实现。用来从 java.ext.dirs 系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext 子目录(扩展目录)下加载类库。如果用户创建的JAR 放在此目录下,也会自动由扩展类加载器加载。

应用类加载器(Application ClassLoader),又称系统类加载器

Java 实现,派生于 ClassLoader,父类加载器为扩展类加载器。由 sun.misc.Lanucher$AppClassLoader 实现。程序中默认的类加载器,一般来说,Java 应用的类都是由它来完成加载的,它负责加载环境变量 classpath 或系统属性 java.class.path 指定路径下的类库。通过 ClassLoader#getSystemClassLoader() 方法可以获取到该类加载器。

用户自定义类加载器(User-Defined ClassLoader)

在日常的 Java 开发中,类加载几乎是由三种加载器配合执行的,在必要时我们还可以自定义类加载器,来定制类的加载方式。

类加载器包含示意图

ClassLoader 相关类图

重点关注 ClassLoader、ExtClassLoader、AppClassLoader。

4 双亲委派模型

过程:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即 ClassNotFoundException ),子加载器才会尝试自己去加载。

好处:使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。同时也避免了多份同样字节码的加载。

Java 中实现双亲委派的代码都集中在 java.lang.ClassLoaderloadClass() 方法中,简单分析如下:

public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
} protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded // 1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) { // 2. 判断一下是否有父加载器,若有交给父加载器加,否则调用 BootstrapClassLoader 加载。
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
} if (c == null) { // 3. 如果第二步骤依然没有找到指定的类,那么调用当前类加载器的 findClass 方法来完成类加载。
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name); // this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
} // 自定义类加载器就可以重写 finalClass 方法
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

我们分析下 ExtClassLoader 与 AppClassLoader 源码,这两个类都是 sun.misc.Launcher 类的静态内部类

Launcher 类的初始化

/**
* This class is used by the system to launch the main application. Launcher类
*/
public class Launcher {
private static URLStreamHandlerFactory factory = new Factory();
private static Launcher launcher = new Launcher(); // 1. Luncher 类加载后会从此静态变量调用到默认的构造方法
private static String bootClassPath =
System.getProperty("sun.boot.class.path"); public static Launcher getLauncher() {
return launcher;
} private ClassLoader loader; public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader(); // 2. 扩展类加载器
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
} // Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl); // 3. 应用类加载器(系统类加载器)
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
} // Also set the context class loader for the primordial thread. // 4. 设置 ContextClassLoader 为应用类加载器(系统类加载器)
Thread.currentThread().setContextClassLoader(loader); // Finally, install a security manager if requested
String s = System.getProperty("java.security.manager");
if (s != null) {
// init FileSystem machinery before SecurityManager installation
sun.nio.fs.DefaultFileSystemProvider.create(); SecurityManager sm = null;
if ("".equals(s) || "default".equals(s)) {
sm = new java.lang.SecurityManager();
} else {
try {
sm = (SecurityManager)loader.loadClass(s).newInstance();
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
} catch (ClassNotFoundException e) {
} catch (ClassCastException e) {
}
}
if (sm != null) {
System.setSecurityManager(sm);
} else {
throw new InternalError(
"Could not create SecurityManager: " + s);
}
}
} // ... /*
* The class loader used for loading installed extensions. 关于 ExtClassLoader
*/
static class ExtClassLoader extends URLClassLoader { // ... private static ExtClassLoader createExtClassLoader() throws IOException {
try {
// Prior implementations of this doPrivileged() block supplied
// aa synthesized ACC via a call to the private method
// ExtClassLoader.getContext(). return AccessController.doPrivileged(
new PrivilegedExceptionAction<ExtClassLoader>() {
public ExtClassLoader run() throws IOException {
final File[] dirs = getExtDirs(); // 4. 获取扩展类加载器加载的目录文件
int len = dirs.length;
for (int i = 0; i < len; i++) {
MetaIndex.registerDirectory(dirs[i]);
}
return new ExtClassLoader(dirs); // 5. 创建一个扩展类加载器进行返回
}
});
} catch (java.security.PrivilegedActionException e) {
throw (IOException) e.getException();
}
} void addExtURL(URL url) {
super.addURL(url);
} /*
* Creates a new ExtClassLoader for the specified directories.
*/
public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
SharedSecrets.getJavaNetAccess().
getURLClassPath(this).initLookupCache(this);
} private static File[] getExtDirs() {
String s = System.getProperty("java.ext.dirs"); // 1. 将加载变量 java.ext.dirs 的值指示的路径下的类
File[] dirs;
if (s != null) {
StringTokenizer st =
new StringTokenizer(s, File.pathSeparator); // 2. 按照 File.pathSeparator 进行 split
int count = st.countTokens();
dirs = new File[count];
for (int i = 0; i < count; i++) {
dirs[i] = new File(st.nextToken());
}
} else {
dirs = new File[0];
}
return dirs; // 3. 返回 File 对象数组
} // ... } /**
* The class loader used for loading from java.class.path. 关于 AppClassLoader
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader { // ... public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
final String s = System.getProperty("java.class.path"); // 1. 加载 java.class.path
final File[] path = (s == null) ? new File[0] : getClassPath(s); // Note: on bugid 4256530
// Prior implementations of this doPrivileged() block supplied
// a rather restrictive ACC via a call to the private method
// AppClassLoader.getContext(). This proved overly restrictive
// when loading classes. Specifically it prevent
// accessClassInPackage.sun.* grants from being honored.
//
return AccessController.doPrivileged(
new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
return new AppClassLoader(urls, extcl);
}
});
} final URLClassPath ucp; /*
* Creates a new AppClassLoader
*/
AppClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent, factory);
ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
ucp.initLookupCache(this);
} /**
* Override loadClass so we can checkPackageAccess. // 0. 重写 loadClass 方法:checkPackageAccess 之后调用 super.loadClass
*/
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
int i = name.lastIndexOf('.');
if (i != -1) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPackageAccess(name.substring(0, i));
}
} if (ucp.knownToNotExist(name)) {
// The class of the given name is not found in the parent
// class loader as well as its local URLClassPath.
// Check if this class has already been defined dynamically;
// if so, return the loaded class; otherwise, skip the parent
// delegation and findClass.
Class<?> c = findLoadedClass(name);
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
throw new ClassNotFoundException(name);
} return (super.loadClass(name, resolve));
} // ...
}
}

5 自定义类加载器

自定义类加载器的目的:

  • 隔离加载类:模块隔离,把类加载到不同的应用选中。比如 Tomcat 这类 Web 应用服务器,内部自定义了好几中类加载器,用于隔离 Web 应用服务器上的不同应用程序。
  • 修改类加载方式:除了 Bootstrap 加载器外,其他的加载并非一定要引入。根据实际情况在某个时间点按需进行动态加载。
  • 扩展加载源:可以实现从其他途径加载 class 文件。
  • 防止源码泄漏:Java 代码容易被编译和篡改,可以进行编译加密,类加载需要自定义还原加密字节码。

通过对双亲委派模型源码的解读,我们可以分析出两种自定义类加载器的做法:

  • 重写 loadClass 方法:不推荐,因为会破坏双亲委派模型
  • 重写 findClass 方法:推荐

采用 findClass 方法自定义类加载器实现:

public class MyClassLoader extends ClassLoader{

    private String dir;
public static final String fileType = ".class"; public MyClassLoader(String dir) {
this.dir = dir;
} public MyClassLoader(ClassLoader parent, String dir) {
super(parent);
this.dir = dir;
} @Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 定义输入和输出流
BufferedInputStream bis = null;
ByteArrayOutputStream baos = null; String fileName = dir + name + fileType;
try {
bis = new BufferedInputStream(new FileInputStream(fileName));
baos = new ByteArrayOutputStream(); // 读取字节数据
int len;
byte[] data = new byte[1024];
while ((len = bis.read(data)) != -1) {
baos.write(data, 0, len);
}
byte[] byteCode = baos.toByteArray(); // 字节数组转为Class对象
Class<?> definedClass = defineClass(null, byteCode, 0, byteCode.length);
return definedClass;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
} return null;
}
}

测试

public class MyClassLoaderTest {

    public static void main(String[] args) {
MyClassLoader classLoader = new MyClassLoader("D:/");
try {
Class<?> dogClass = classLoader.loadClass("Test");
System.out.println(dogClass.getClassLoader().getClass().getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}

内容为之前学习笔记整理,如果有问题请指正!

JVM简明笔记3:类加载机制的更多相关文章

  1. JVM学习笔记之类加载机制【八】

    一.类加载时机 1.1 触发类初始化的六个场景: 加载? 1.遇到new.getstatic.putstatic或invokestatic这四条字节码指令时 如果类型没有进行过初始化,则需要先触发其初 ...

  2. 【JVM.6】虚拟机类加载机制

    一.概述 虚拟机类加载机制:虚拟机把描述类的数据从Class文件中加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型. 与那些在编译时需要进行连接工作的语言不同 ...

  3. 深入理解Java虚拟机笔记——虚拟机类加载机制

    目录 概述 动态加载和动态连接 类加载的时机 类的生命周期 被动引用 例子一(调用子类继承父类的字段) 例子二(数组) 例子三(静态常量) 类加载的过程 加载 验证 准备 解析 符号引用 直接引用 初 ...

  4. JVM内存模型与类加载机制

    一. java虚拟机的内存模型如图: 补习一下jvm内存模型中的各个组成部分 堆: 我们new出来的对象全部放在堆中,他是jvm所能够动态分配的最大的一块空间 优点: 内存动态分配,生命周期不必事先告 ...

  5. jvm字节码和类加载机制

    Class类文件的结构 任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(类和接口也可以用反射的方式通过类加载器直接生成) Class文件时一组以 ...

  6. 玩命学JVM(二)—类加载机制

    前言 Java程序运行图: 上一篇玩命学JVM(一)-认识JVM和字节码文件我们简单认识了 JVM 和字节码文件.那JVM是如何使用字节码文件的呢?从上图清晰地可以看到,JVM 通过类加载器完成了这一 ...

  7. JVM(三)-java虚拟机类加载机制

    概述: 上一篇文章,介绍了java虚拟机的运行时区域,Java虚拟机根据不同的分工,把内存划分为各个不同的区域.在java程序中,最小的运行单元一般都是创建一个对象,然后调用对象的某个 方法.通过上一 ...

  8. 《深入理解java虚拟机》第七章读书笔记——虚拟机类加载机制

    系列文章目录和关于我 一丶虚拟机类加载机制是什么 java虚拟机将描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可用被虚拟机直接使用的java类型. 二丶类加载时 ...

  9. 深入理解JVM虚拟机-7虚拟机类加载机制

    虚拟机把描述类的数据从Class文件夹加载到内存,并对数据进行小燕.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制. 下面所说的Class文件不是具体的某个文件 ...

  10. (转)《深入理解java虚拟机》学习笔记6——类加载机制

    Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程. 在加载阶段,java虚拟机需要完成以下 ...

随机推荐

  1. idea branch 分支比较 | git 查看分支命令 `git branch -vv`

    git 查看分支命令 git branch -vv

  2. idea 暂存 Stash Changes Git/Repository/Stash Changes 恢复暂存 UnStash Changes

    idea 暂存 Stash Changes Git/Repository/Stash Changes 恢复暂存 UnStash Changes git stash save "save me ...

  3. ubuntu环境下python下使用OpenCV库读取USB摄像头的画面

    一 概念 OpenCV是一个开源的计算机视觉和机器学习软件库.它可以使用pip命令行中的以下命令安装:"pip install opencv-python" 这个做视觉处理,非常的 ...

  4. 可穿戴智能手环解决方案之BLE的ADV广播协议解读

    一 概念 直接上英文原文,怕自己的翻译误导大家. When a BLE device is advertising, it periodically transmits packets, which ...

  5. python下进行10进制转16进制不带0x并且将16进制转成小端序

    前记   python涉及到和硬件互交的部分,一般是需要发送十六进制的帧长的.所以,python这个转换还是经常使用的.笔者在这里遇到了一个问题.就做一个记录吧. 基本方法:  假如你熟悉python ...

  6. 自我总结的git的使用

    git是什么 git是一个分布式版本控制工具,github是代码托管平台. git有什么用 保存文件的所有修改记录 使用版本号进行区分 随时可浏览历史版本记录 可还原到历史指定版本 对比不同版本的文件 ...

  7. View之invalidate,requestLayout,postInvalidate

    目录介绍 01.invalidate,requestLayout,postInvalidate区别 02.invalidate深入分析 03.postInvalidate深入分析 04.request ...

  8. 浅析三维模型OBJ格式轻量化压缩集群处理方法

    浅析三维模型OBJ格式轻量化压缩集群处理方法 三维模型的OBJ格式轻量化压缩是指通过一系列技术和方法将三维模型的文件大小进一步减小,以提高模型在计算机中的加载.传输和存储效率.集群处理技术是指利用多台 ...

  9. Mysql访问问题,远程连接提示:Host 'xxx' is not allowed to connect to this MySQL server。是mysql未开启mysql远程访问权限导致

    1.MySql服务器共享问题 对于在车间工作者,如果远程Mysql,我们这里假定网线连接 GRANT ALL PRIVILEGES ON *.* TO 'root'@'192.168.1.3' IDE ...

  10. JS原生2048小游戏源码分享

    最近在学习算法方面的知识,看到了一个由算法主导的小游戏,这里给大家分享下代码: 效果: 代码: <head> <meta charset="UTF-8"> ...