本文阅读时间大约9分钟。

写在前面

在Java面试中,在考察完项目经验、基础技术后,我会根据候选人的特点进行知识深度的考察,如果候选人简历上有写JVM(Java虚拟机)相关的东西,那么我常常会问一些JVM的问题。JVM的类加载机制是一个很经典的知识点,围绕这个知识点可以有下面这些难度不同的问题。

  1. 简单讲下JVM中的类加载过程

  2. JVM中的类加载和卸载的时机?

  3. 如何理解JVM中不同类加载器的概念和作用?

  4. 简单讲下JVM中的双亲委派模型?

  5. 什么情况下会破坏双亲委派模型?为什么?可否举个例子?

  6. Tomcat中的类加载机制有了解吗?为什么这么设计?

  7. 实际开发中有遇到哪些类加载器相关的问题?你又是如何解决的?

  8. JVM之上的弱类型语言例如Groovy是如何实现?简单讲下动态类加载机制?

在接下来的几篇文章,我将跟读者一起重新梳理一遍类加载器的相关知识,争取能够妥善解答上面列出的这些问题。

基本概念篇

类的加载和卸载

JVM是虚拟机的一种,它的指令集语言是字节码,字节码构成的文件是class文件。平常我们写的Java文件,需要编译为class文件才能交给JVM运行。可以这么说:C语言代码——>二进制文件——>计算机硬件,就相当于Java代码——>字节码文件——>JVM。JVM将指定的class文件读取到内存里,并运行该class文件里的Java程序的过程,就称之为类的加载;反之,将某个class文件的运行时数据从JVM中移除的过程,就称之为类的卸载

class文件的运行时数据就是C++对象,也称为kclass对象,这些运行时数据在JDK7之前是放在永久代(PermGen),JDK8之后则放在元空间(Metaspace)。

类的生命周期

Java类从被虚拟机加载开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段;其中验证、准备和解析又统称为连接(Linking)阶段。

类的加载的时机

虚拟机规范并未严格规定类加载的时机,跟具体的JVM虚拟机有关。类加载的最佳时机是解析Java字节码类文件中常量池符号的时候,Class.forName()、ClassLoader.loadClass()、反射API和JNI_FindClass都可以触发类加载,Hot JVM自身启动的时候也会触发类加载。

通过JVM参数中加 -verbose:class,可以在应用启动的时候打印类加载的过程,如下图所示:

初始化这个阶段,JVM虚拟机给出了5种必须对类进行“初始化”的情况

  1. 使用new关键字实例化对象的时候、读取或设置一个类的静态字段的时候、调用一个类的静态方法的时候;

  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则要先触发其初始化;

  3. 当初始化一个类的时候,如果发现其父类还没有被初始化,则要先初始化其父类;

  4. 当虚拟机启动时,用户需要指定一个执行的主类(包含main方法的那个类),则虚拟机会优先初始化这个主类;

  5. 在JDK1.7以后,动态语言支持的时候,如果一个java.lang.invoke.MethodHandle实例最后的结果是要执行第1种情况的操作,则也要进行初始化。

类的卸载时机

类的卸载跟采用的垃圾收集算法有关,在CMS中有两种方法卸载不必要的类,一种是等到元空间(Metaspace)满了的时候触发FGC,另一种是使用跟CMS并发收集算法类似的方式,不过对于元空间的阈值和触发CMS并发收集的阈值是独立的。更具体的可以参考之前的文章:CMS学习笔记。在这里,我们只需要记住,JVM中一个类的卸载要满足下面这3个条件:

  1. 该类所有的实例对象都已被回收;

  2. 该类的类加载器对象已经被回收;

  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

类加载器的作用

类的加载是需要类加载器完成的,但是类加载器在JVM中的作用可不止这些。在JVM中,一个类的唯一性是需要这个类本身和类加载一起才能确定的,每个类加载器都有一个独立的命名空间。

不同的类加载器,即使是同一个类字节码文件,最后再JVM里的类对象也不是同一个,下面的代码展示了这个结论:

package jvm;
import java.io.IOException;
import java.io.InputStream;
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException,
InstantiationException {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream inputStream = getClass().getResourceAsStream(fileName);
if (inputStream == null) {
return super.loadClass(name);
}
try {
byte[] b = new byte[inputStream.available()];
inputStream.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
};
Object obj = myLoader.loadClass("jvm.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof jvm.ClassLoaderTest);
ClassLoaderTest classLoaderTest = new ClassLoaderTest();
System.out.println(classLoaderTest.getClass());
System.out.println(classLoaderTest instanceof jvm.ClassLoaderTest);
}
}

上述代码的运行结果是:

可以看出,代码中使用自定义类加载器(myLoader)加载的jvm.ClassLoaderTest类和通过应用程序类加载器加载的类不是同一个类。综上,类加载器在JVM中的作用有:

  1. 将类的字节码文件从JVM外部加载到内存中

  2. 确定一个类的唯一性

  3. 提供隔离特性,为中间件开发者提供便利,例如Tomcat

总结

今天的文章,应该可以回答文章开始提出的前两个问题,下篇再会。

参考资料

  1. https://jrebel.com/rebellabs/do-you-really-get-java-classloaders/

  2. https://stackoverflow.com/questions/2424604/what-is-a-java-classloader

  3. https://docs.oracle.com/javase/9/docs/api/java/lang/ClassLoader.html

  4. https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html

  5. https://blogs.oracle.com/sundararajan/understanding-java-class-loading

  6. 《深入理解Java虚拟机》

  7. 《揭秘Java虚拟机》

  8. 《Java性能权威指南》

下方查看精选文章

本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。


点在看,让更多看见。

彻底搞懂JVM类加载器:基本概念的更多相关文章

  1. 搞懂JVM类加载机制

    有这样一道面试题: class Singleton{ private static Singleton singleton = new Singleton(); public static int v ...

  2. 一夜搞懂 | JVM 类加载机制

    前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习导图 一.为什么要学习类加载机制? 今天想跟大家唠嗑唠嗑Java的类加载机制,这是Java的一个很重要的创 ...

  3. 一夜搞懂 | JVM 字节码执行引擎

    前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 学习导图 一.为什么要学习字节码执行引擎? 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一 ...

  4. jvm类加载器以及双亲委派

    首先来了解几个概念: 类加载: 概念:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验--转换解析--初始化,最终形成能被java虚拟机直接使用的java类型,就是jvm的类加载机制. ...

  5. JVM类加载器的分类

    类加载器的分类 JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader). 从概念上来讲,自定 ...

  6. 深入JVM类加载器机制,值得你收藏

    先来一道题,试试水平 public static void main(String[] args) { ClassLoader c1 = ClassloaderStudy.class.getClass ...

  7. 一篇文章读懂Java类加载器

    Java类加载器算是一个老生常谈的问题,大多Java工程师也都对其中的知识点倒背如流,最近在看源码的时候发现有一些细节的地方理解还是比较模糊,正好写一篇文章梳理一下. 关于Java类加载器的知识,网上 ...

  8. Java虚拟机系列一:一文搞懂 JVM 架构和运行时数据区

    前言 之前写博客一直比较随性,主题也很随意,就是想到什么写什么,对什么感兴趣就写什么.虽然写起来无拘无束,自在随意,但也带来了一些问题,每次写完一篇后就要去纠结下一篇到底写什么,看来选择太多也不是好事 ...

  9. JVM 类加载器命名空间深度解析与实例分析

    一.创建Sample 1.创建实例 public class MyPerson { private MyPerson myPerson; public void setMyPerson(Object ...

随机推荐

  1. Vi编辑网卡

    Vi /etc/sysconfig/network-scripts/ifcfg-ens33  1.光标定位到BOOTPROTO=后面 2.然后按x键进行删除 3.按i键打开编辑模式,输入BOOTPRO ...

  2. Django2.2 Vue 前后端分离 无法访问Cookie

    个人验证后可用配置如下: 环境: - Django 2.2 - djangorestframework 3.9 - django-cors-headers 2.5.3 INSTALLED_APPS = ...

  3. CSP2019滚粗记

    CSP2019滚粗记 从不拖更 Day -inf 考完月考才停课\kel,然后月考又被吊打了. Day -1 和左边的\({\text L}\color{red} {\text{ongge}}\)右边 ...

  4. 2,[VS入门教程] 使用Visual Studio写c语言 入门与技巧精品文~~~~优化篇

    本文导航: 关闭界面特效以提高流畅度 解决调试时出现"无法查找或打开PDB文件"的符号问题 注册微软账号并在vs登录 使用Visual Studio Team Services,同 ...

  5. Leetcode 第137场周赛解题报告

    今天的比赛的题目相对来说比较「直白」,不像前几周都是一些特定的算法,如果你没学过不可能想出来. 做了这些周,对leetcode比赛的题目也发现了一些「规律」. 一般前两道题都很「简单」,只要有想法,直 ...

  6. 用户增长模型AARRR模型

    用户增长模型AARRR模型

  7. c# mongodb时间类型字段保存时相差八个小时解决办法

    /// <summary> /// 添加时间 /// </summary> [BsonDateTimeOptions(Kind = DateTimeKind.Local)] p ...

  8. Java连载13-整数型字面值的强制转换

    一.注意点 1.大容量不能直接赋值给小容量:大容量转化为小容量需要进行,强制类型转换,强制类型转换需要加上“强制类型转换符”,加上强制类型转换符之后编译通过了但是精度会有有可能损失.所以强制类型转换要 ...

  9. git 用 diff 来检查改动

    用 diff 来检查改动 项目的开发是由无数个微小的改动组成的.了解项目开发过程的关键就是要搞清楚每一个改动.当然你可以使用 “git status” 命令或更简单的 “git log” 命令来打印出 ...

  10. vue条件语句、循环语句、计算属性、侦听器监听属性

    因为 v-if 和v-for是一个指令,所以必须将它添加到一个元素上.但是如果想切换多个元素呢?此时可以把一个 <template> 元素当做不可见的包裹元素,并在上面使用 v-if.最终 ...