一篇带给你JVM 类加载过程认知

一个类型被加载到臆造机内存中脱手,到卸载出内存为止、它的通盘人命周期将会履历加载、考据、准备、认知、运行化、使用、卸载七个阶段。其中考据、准备、认知为联结
REF_invokeStatic句柄对应的类莫得被运行化则运行化。
其它加载情况当 Java 臆造机运行化一个类时,条目它悉数的父类都被运行化,单这一条文矩并不适用于接口。
在运行化一个类时,并不会先运行化它所已毕的接口 在运行化一个接口时,并不会先运行化它的父类接口 因此,一个父接口并不会因为他的子接口或者已毕了类的运行化而运行化,只好当步调初度被使用特定接口的静态变量时,才会导致该接口的运行化。只好面前线法拜访的静态变量或静态步调确乎在面前类或面前接口界说时,才可以为是对接口或类的主动使用。
调用 ClassLoader 类的 loadClass 步调加载一类,并不是对类的主动使用,不会导致类的运行化。
测试例子 1:public class Test_2 extends Test_2_A { static { System.out.println("子类静态代码块"); } { System.out.println("子类代码块"); } public Test_2() { System.out.println("子类构造步调"); } public static void main(String[] args) { new Test_2(); } } class Test_2_A { static { System.out.println("父类静态代码块"); } { System.out.println("父类代码块"); } public Test_2_A() { System.out.println("父类构造步调"); } public static void find() { System.out.println("静态步调"); } } //代码块和构造步调延迟轨则 //1).父类静态代码块 //2).子类静态代码块 //3).父类代码块 //4).父类构造步调 //5).子类代码块 //6).子类构造步调测试例子 2:
public class Test_1 { public static void main(String[] args) { System.out.println(Test_1_B.str); } } class Test_1_A { public static String str = "A str"; static { System.out.println("A Static Block"); } } class Test_1_B extends Test_1_A { static { System.out.println("B Static Block"); } } //输出效果 //A Static Block //A str类加载经过 加载
在硬盘上查找何况通过 IO 读入字节码文献,使用到该类的时辰才会被加载,举例调用 main 步调, new 关键字调用对象等,在加载阶段会在内存中生成这个类的 java.lang.Class 对象, 看成步调区这个类的各式数据的拜访进口。
考据校验字节码文献的正确性
准备给类的静态变量分拨内存,何况赋予默许值
认知将标记援用替换为径直援用,该节点会把一些静态步调(标记援用,比如 main() 步调)替换为指向数据所存内存的指针或句柄等(径直援用),这即是所谓的静态集合过程(类加载时间完成),动态集合是在步调运行时间完成的将标记援用替换为径直援用。
运行化对类的静态变量运行化为指定的值,延迟静态代码块。
类加载器 **_教育类加载器(Bootstrap Class Loader) _**认真加载 \lib\ 目次或者被 -Dbootclaspath 参数指定的类, 比如: rt.jar, tool.jar 等 。 拓展类加载器(Extension Class Loader) 认真加载 \lib\ext\ 或 -Djava.ext.dirs 选项所指定目次下的类和 jar包。 应用步调类加载器(System Class Loader) 认真加载 CLASSPATH 或 -Djava.class.path所指定的目次下的类和 jar 包。 自界说类加载器:认真加载用户自界说包旅途下的类包,通过 ClassLoader 的子类已毕 Class 的加载。测试文献:
public class TestJVMClassLoader { public static void main(String[] args) { System.out.println(String.class.getClassLoader()); System.out.println(DESKeyFactory.class.getClassLoader()); System.out.println(TestJVMClassLoader.class.getClassLoader()); System.out.println(); ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); ClassLoader extClassLoader = appClassLoader.getParent(); ClassLoader bootstrapClassLoader = extClassLoader.getParent(); System.out.println("bootstrapClassLoader: " + bootstrapClassLoader); System.out.println("extClassLoader: " + extClassLoader); System.out.println("appClassLoader: " + appClassLoader); System.out.println(); System.out.println("bootstrapLoader 加载以下文献:"); URL[] urls = Launcher.getBootstrapClassPath().getURLs(); for (URL url : urls) { System.out.println(url); } System.out.println(); System.out.println("extClassLoader 加载以下文献:"); System.out.println(System.getProperty("java.ext.dirs")); System.out.println(); System.out.println("appClassLoader 加载以下文献:"); System.out.println(System.getProperty("java.class.path")); } }双亲委派机制
什么是双亲委派机制?
一个类加载器收到了类加载的苦求, 它最初不会我方去尝试我方去加载这个类,而是把这个苦求委派给父类加载器去完成,每一个档次的类加载器都是如斯,因此悉数的苦求最终都应该传送到最顶层的启动类加载器中,只好当父加载器反映我方无法完成这个加载苦求(即搜索范畴中莫得找到所需的类)时,子加载器才会尝试我方完成加载。
类加载和双亲委派模子如下图所示
咱们再来望望 ClassLoader 类的 loadClass 步调
// loadClass protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 最初查验面前类是否被加载 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { // 淌若父类类加载器不为空,先尝试父类加载来加载 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) { // 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; } } // 类加载器的包含关联 public abstract class ClassLoader { private static native void registerNatives(); static { registerNatives(); } // 面前 ClassLoader 和 parent ClassLoader 的包含关联 private final ClassLoader parent; }回来: 不是树形结构(只是逻辑树形结构),而是包含/包装关联。 加载轨则,应用类加载器,拓展加载器,系统加载器。 淌若有一个类加载器概况得胜加载 Test 类,那么这个类加载器被称为界说类加载器,悉数可能复返 Class 对象援用的类加载器(包括界说类加载器)都被称为运行类加载器。 盘算双亲委派机制的主见? 保证 Java 中枢库的类型安全:悉数的java 应用都会至少援用 java.lang.Object 类, 也即是说在运行期, java.lang.Object 的这个类会被加载到 Java 臆造机中,淌若这个加载过程是由 Java 应用我方的类加载器所完成的,那么很有可能会在 JVM 中存在多个版块的 java.lang.Object 类,而且这些类之间依然不兼容的。互不主张的(恰是定名空间进展撰述用)借助于双亲托付机制,Java 中枢库中的类加载使命都是由启动类加载器谐和来完成的。从而确保了Java 应用所使用的都是归并个版块的 Java 中枢类库,他们之间是互相兼容的。 不错确保 Java 中枢库所提供的类不会被自界说的类所替代。 不同的类加载器不错为疏通类(binary name)的类创建特等的定名空间。疏通称号的类不错并存在Java臆造机中,只需要不同的类加载器来加载他们即可,不同的类加载器的类之间是不兼容的,这十分于在JAVA臆造机里面创建了一个又一个互相守密的Java类空间,这类本领在许多框架中赢得了实际欺诈。 自界说类加载器
自界说类加载器加载类,底下是一个爽脆的 Demo
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; public class ClassLoaderTest extends ClassLoader { private static String rxRootPath; static { rxRootPath = "/temp/class/"; } @Override public Class findClass(String name) { byte[] b = loadClassData(name); return defineClass(name, b, 0, b.length); } /** * 读取 .class 文献为字节数组 * * @param name 全旅途类名 * @return */ private byte[] loadClassData(String name) { try { String filePath = fullClassName2FilePath(name); InputStream is = new FileInputStream(new File(filePath)); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buf = new byte[2048]; int r; while ((r = is.read(buf)) != -1) { bos.write(buf, 0, r); } return bos.toByteArray(); } catch (Throwable e) { e.printStackTrace(); } return null; } /** * 全落幕名调养为文献旅途 * * @param name * @return */ private String fullClassName2FilePath(String name) { return rxRootPath + name.replace(".", "//") + ".class"; } public static void main(String[] args) throws ClassNotFoundException { ClassLoaderTest classLoader = new ClassLoaderTest(); String className = "com.test.TestAA"; Class clazz = classLoader.loadClass(className); System.out.println(clazz.getClassLoader()); // 输出效果 //cn.xxx.xxx.loader.ClassLoaderTest@3764951d } }Tomcat 类加载器 Tomcat 中的类加载器模子
tomcat 的几个主要类加载器:
commonLoader:Tomcat 最基本的类加载器, 加载旅途中的 class 不错被 Tomcat 容器自己以及各个 WebApp 拜访。 catalinaLoader:Tomcat 容器特有的类加载器 加载旅途中的 class 关于 Webapp 不主张; sharaLoader: 各个Webapp 分享的类加载器, 加载旅途中的 class 关于悉数 webapp 可见, 可是关于 Tomcat 容器不主张。 webappLoader: 各个 Webapp 特有的类加载, 加载旅途中的 class 只对面前 webapp 可见, 比如加载 war 包里面推敲的类,每个 war 包应用都有我方的 webappClassLoader 对象,对应不同的定名空间,已毕互相守密,比如 war 包中不错引入不同的 spring 版块,已毕多个 spring 版块 应用的同期运行。 回来:从图中的委派关联中不错看出:
Commonclassloader 能加载的类都不错被 Catalinaclassloader和 Sharedclassloadert 使用, 从费力毕了公有类库的共用,而Catalinaclassloader 和 Sharedclassloader我方能加载的类则与对方互相守密 Webappclassloader 不错使用 Shared Loader 加载到的类,但各个 Webappclassloader 实例之间互相守密而 Jasper Loader 的加载范畴只是是这个 JSP 文献所编译出来的那一个 . class 文献,它出现的主见即是为了被丢弃: 当 Web 容器检测到 JSP 文献被修改时,会替换掉现在的 Jasperloader 的实例,并通过再成立一个新的 Jsp 类加载器来已毕 JSP 文献的热加载功能。
Tomcat这种类加载机制抵触了java保举的双亲委派模子了吗? 谜底是: 抵触了
tomcat不是这么已毕, tomcat为了已毕守密性, 莫得遵照这个商定, 每个 webapp Loader加载我方的目次下的 class'文献,不会传递给父类加载器,阻扰了双亲委派机制
参考汉典《潜入知晓 Java 臆造机》 第三版 周志明
Apache Tomcat Documentation