JVM核心类加载器及类加载的全过程

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: JVM核心类加载器及类加载的全过程

运行环境:


下面说明一下我的运行环境。我是在mac上操作的. 先找到mac的java地址. 从~/.bash_profile中可以看到

java的home目录是: /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home


1187916-20200627180251652-1130784281.png


一. 类加载的过程



1.1 类加载器初始化的过程


假如现在有一个java类 com.lxl.jvm.Math类, 里面有一个main方法


package com.lxl.jvm;
public class Math {
    public static int initData = 666;
    public static User user = new User();
    public int compute() {
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
        return c;
    }
    public static void main(String[] args) {
        Math math = new Math();
        math.compute();
    }
}

这个方法很简单, 通常我们直接执行main方法就ok, 可以运行程序了, 那么点击运行main方法, 整个过程是如何被加载运行的呢? 为什么点击执行main, 就能得到结果呢?

先来看看答题的类加载流程(宏观流程), 如下图:


1187916-20200627191708469-1781924332.png


备注:


1. windows上的java启动程序是java.exe, mac下是java


2. c语言部分,我们做了解, java部门是需要掌握的部分.


第一步: java调用底层的jvm.dll文件创建java虚拟机(这一步由C++实现) . 这里java.exe是c++写的代码, 调用的jvm.dll也是c++底层的一个函数. 通过调用jvm.dll文件(dll文件相当于java的jar包), 会创建java虚拟机. java虚拟机的启动都是c++程序实现的.


第二步:在启动虚拟机的过程中, 会创建一个引导类加载器的实例. 这个引导类的加载器是C语言实现的. 然后jvm虚拟机就启动起来了.


第三步: 接下来,C++语言会调用java的启动程序.刚刚只是创建了java虚拟机, java虚拟机里面还有很多启动程序. 其中有一个程序叫做Launcher. 类全称是sun.misc.Launcher. 通过启动这个java类, 会由这个类引导加载器加载并创建很多其他的类加载器. 而这些加载器才是真正启动并加载磁盘上的字节码文件.


第四步:真正的去加载本地磁盘的字节码文件,然后启动执行main方法.(这一步后面会详细说,到底是怎么加载本地磁盘的字节码文件的。)


第五步:main方法执行完毕, 引导类加载器会发起一个c++调用, 销毁JVM

以上就是启动一个main方法, 这个类加载的全部过程


下面, 我们重点来看一下, 我们的类com.lxl.Math是怎么被加载到java虚拟机里面去的? 

 

1.2 类加载的过程


上面的com.lxl.jvm.Math类最终会生成clas字节码文件. 字节码文件是怎么被加载器加载到JVM虚拟机的呢?


类加载有五步:加载, 验证, 准备, 解析, 初始化. 那么这五步都是干什么的呢?我们来看一下

我们的类在哪里呢? 在磁盘里(比如: target文件夹下的class文件), 我们先要将class类加载到内存中. 加载到内存区域以后, 不是简简单单的转换成二进制字节码文件,他会经过一系列的过程. 比如: 验证, 准备, 解析, 初始化等. 把这一些列的信息转变成内元信息, 放到内存里面去. 我们来看看具体的过程

1187916-20200627213226895-792217364.png

第一步: 加载.


将class类加载到java虚拟机的内存里去, 在加载到内存之前, 会有一系列的操作。第一步是验证字节码。1187916-20200627202413228-1718690608.png


第二步:验证


验证字节码加载是否正确, 比如:打开一个字节码文件。打眼一看, 感觉像是乱码, 实际上不是的. 其实,这里面每个字符串都有对应的含义. 那么文件里面的内容我们能不能替换呢?当然不能, 一旦替换, 就不能执行成功了. 所以, 第一步:验证, 验证什么呢?

验证字节码加载是否正确: 格式是否正确. 内容是否符合java虚拟机的规范.


第三步:准备


验证完了, 接下来是准备. 准备干什么呢? 比如我们的类Math, 他首先会给Math里的静态变量赋值一个初始值. 比如我们Math里有两个静态变量


public static int initData = 666;
public static User user = new User();

在准备的过程中, 就会给这两个变量赋初始值, 这个初始值并不是真实的值, 比如initData的初始值是0. 如果是boolean类型, 就赋值为false. 也就是说, 准备阶段赋的值是jvm固定的, 不是我们定义的值.如果一个final的常量, 比如public static final int name="zhangsan", 那么他在初始化的时候, 是直接赋初始值"zhangsan"的. 这里只是给静态变量赋初始值


第四步:解析


接下来说说解析的过程. 解析的过程略微复杂, 解析是将"符号引用"转变为直接引用.

什么是符号引用呢?

比如我们的程序中的main方法. 写法是固定的, 我们就可以将main当成一个符号. 比如上面的initData, int, static, 我们都可以将其称之为符号, java虚拟机内部有个专业名词,把他叫做符号. 这些符号被加载到内存里都会对应一个地址. 将"符号引用"转变为直接引用, 指的就是, 将main, initData, int等这些符号转变为对应的内存地址. 这个地址就是代码的直接引用. 根据直接引用的值,我们就可以知道代码在什么位置.然后拿到代码去真正的运行.

将符号引用转变为"内存地址", 这种有一个专业名词, 叫静态链接. 上面的解析过程就相当于静态链接的过程. 类加载期间,完成了符号到内存地址的转换. 有静态链接, 那么与之对应的还有动态链接.


什么是动态链接呢?


public static void main(String[] args) {
        Math math = new Math();
        math.compute();
    }

比如:上面这段代码, 只有当我运行到math.compute()这句话的时候, 才回去加载compute()这个方法. 也就是说, 在加载的时候, 我不一定会把compute()这个方法解析成内存地址. 只有当运行到这行代买的时候, 才会解析.


我们来看看汇编代码


javap -v Math.class
Classfile /Users/luoxiaoli/Downloads/workspace/project-all/target/classes/com/lxl/jvm/Math.class
  Last modified 2020-6-27; size 777 bytes
  MD5 checksum a6834302dc2bf4e93011df4c0b774158
  Compiled from "Math.java"
public class com.lxl.jvm.Math
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#35         // java/lang/Object."<init>":()V
   #2 = Class              #36            // com/lxl/jvm/Math
   #3 = Methodref          #2.#35         // com/lxl/jvm/Math."<init>":()V
   #4 = Methodref          #2.#37         // com/lxl/jvm/Math.compute:()I
   #5 = Fieldref           #2.#38         // com/lxl/jvm/Math.initData:I
   #6 = Class              #39            // com/lxl/jvm/User
   #7 = Methodref          #6.#35         // com/lxl/jvm/User."<init>":()V
   #8 = Fieldref           #2.#40         // com/lxl/jvm/Math.user:Lcom/lxl/jvm/User;
   #9 = Class              #41            // java/lang/Object
  #10 = Utf8               initData
  #11 = Utf8               I
  #12 = Utf8               user
  #13 = Utf8               Lcom/lxl/jvm/User;
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               Lcom/lxl/jvm/Math;
  #21 = Utf8               compute
  #22 = Utf8               ()I
  #23 = Utf8               a
  #24 = Utf8               b
  #25 = Utf8               c
  #26 = Utf8               main
  #27 = Utf8               ([Ljava/lang/String;)V
  #28 = Utf8               args
  #29 = Utf8               [Ljava/lang/String;
  #30 = Utf8               math
  #31 = Utf8               MethodParameters
  #32 = Utf8               <clinit>
  #33 = Utf8               SourceFile
  #34 = Utf8               Math.java
  #35 = NameAndType        #14:#15        // "<init>":()V
  #36 = Utf8               com/lxl/jvm/Math
  #37 = NameAndType        #21:#22        // compute:()I
  #38 = NameAndType        #10:#11        // initData:I
  #39 = Utf8               com/lxl/jvm/User
  #40 = NameAndType        #12:#13        // user:Lcom/lxl/jvm/User;
  #41 = Utf8               java/lang/Object
{
  public static int initData;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC
  public static com.lxl.jvm.User user;
    descriptor: Lcom/lxl/jvm/User;
    flags: ACC_PUBLIC, ACC_STATIC
  public com.lxl.jvm.Math();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/lxl/jvm/Math;
  public int compute();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: bipush        10
         9: imul
        10: istore_3
        11: iload_3
        12: ireturn
      LineNumberTable:
        line 8: 0
        line 9: 2
        line 10: 4
        line 11: 11
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      13     0  this   Lcom/lxl/jvm/Math;
            2      11     1     a   I
            4       9     2     b   I
           11       2     3     c   I
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class com/lxl/jvm/Math
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #4                  // Method compute:()I
        12: pop
        13: return
      LineNumberTable:
        line 15: 0
        line 16: 8
        line 17: 13
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0  args   [Ljava/lang/String;
            8       6     1  math   Lcom/lxl/jvm/Math;
    MethodParameters:
      Name                           Flags
      args
  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: sipush        666
         3: putstatic     #5                  // Field initData:I
         6: new           #6                  // class com/lxl/jvm/User
         9: dup
        10: invokespecial #7                  // Method com/lxl/jvm/User."<init>":()V
        13: putstatic     #8                  // Field user:Lcom/lxl/jvm/User;
        16: return
      LineNumberTable:
        line 4: 0
        line 5: 6
}
SourceFile: "Math.java"

使用这个指令, 就可以查看Math的二进制文件. 其实这个文件,就是上面那个二进制代码文件.


看看这里面有什么东西?


类的名称, 大小,修改时间, 大版本,小版本, 访问修饰符等等


Last modified 2020-6-27; size 777 bytes
  MD5 checksum a6834302dc2bf4e93011df4c0b774158
  Compiled from "Math.java"
public class com.lxl.jvm.Math
  minor version: 0
  major version: 52

还有一个Constant pool 常量池. 这个常量池里面有很多东西. 我们重点看中间哪一行. 第一列表示一个常量的标志符, 这个标识符可能在其他地方会用到. 第二列就表示常量内容.


Constant pool:
   #1 = Methodref          #9.#35         // java/lang/Object."<init>":()V
   #2 = Class              #36            // com/lxl/jvm/Math
   #3 = Methodref          #2.#35         // com/lxl/jvm/Math."<init>":()V
   #4 = Methodref          #2.#37         // com/lxl/jvm/Math.compute:()I
   #5 = Fieldref           #2.#38         // com/lxl/jvm/Math.initData:I
   #6 = Class              #39            // com/lxl/jvm/User
   #7 = Methodref          #6.#35         // com/lxl/jvm/User."<init>":()V
   #8 = Fieldref           #2.#40         // com/lxl/jvm/Math.user:Lcom/lxl/jvm/User;
   #9 = Class              #41            // java/lang/Object
  #10 = Utf8               initData
  #11 = Utf8               I
  #12 = Utf8               user
  #13 = Utf8               Lcom/lxl/jvm/User;
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               Lcom/lxl/jvm/Math;
  #21 = Utf8               compute

这些标识符在后面都会被用到, 比如main方法


public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class com/lxl/jvm/Math
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #4                  // Method compute:()I
        12: pop
        13: return
      LineNumberTable:
        line 15: 0
        line 16: 8
        line 17: 13
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0  args   [Ljava/lang/String;
            8       6     1  math   Lcom/lxl/jvm/Math;
    MethodParameters:
      Name                           Flags
      args

这里面就用到了#2 #3 #4 ,这都是标识符的引用.


第一句: new了一个Math(). 我们看看汇编怎么写的?


0: new           #2                  // class com/lxl/jvm/Math

new + #2. #2是什么呢? 去常量池里看, #2代表的就是Math类


#2 = Class              #36            // com/lxl/jvm/Math

这里要说的还是math.compute()这个方法, 不是在类加载的时候就被加载到内存中去了, 而是运行main方法的时候, 执行到这行代码才被加载进去, 这个过程叫做动态链接.

类加载的时候, 我们可以把"解析"理解为静态加载的过程. 一般像静态方法(例如main方法), 获取其他不变的静态方法会被直接加载到内存中, 因为考虑到性能, 他们加载完以后就不会变了, 就直接将其转变为在内存中的代码位置.


而像math.compute()方法, 在加载过程中可能会变的方法(比如compute是个多态,有多个实现), 那么在初始化加载的时候, 我们不会到他会调用谁, 只有到运行时才能知道代码的实现, 所以在运行的时候在动态的去查询他在内存中的位置, 这个过程就是动态加载


第五步: 初始化


对类的静态变量初始化为指定的值. 执行静态代码块. 比如代码


public static int initData = 666;

在准备阶段将其赋值为0, 而在初始化阶段, 会将其赋值为设定的666  


1.3 类的懒加载


类被加载到方法区中以后,主要包含:运行时常量池, 类型信息, 字段信息, 方法信息, 类加载器的引用, 对应class实例的引用等信息.


什么意思呢? 就是说, 当一个类被加载到内存, 这个类的常量,有常量名, 类型, 域信息等; 方法有方法名, 返回值类型, 参数类型, 方法作用域等符号信息都会被加载放入不同的区域.

注意: 如果主类在运行中用到其他类,会逐步加载这些类, 也就是说懒加载. 用到的时候才加载.


package com.lxl.jvm;
public class TestDynamicLoad {
    static {
        System.out.println("********Dynamic load class**************");
    }
    public static void main(String[] args) {
        new A();
        System.out.println("*********load test*****************");
        B b = null; // 这里的b不会被加载, 除非new B();
    }
}
class A {
    static {
        System.out.println("********load A**************");
    }
    public A(){
        System.out.println("********initial A**************");
    }
}
class B {
    static {
        System.out.println("********load B**************");
    }
    public B(){
        System.out.println("********initial B**************");
    }
}

这里定义了两个类A和B, 当使用到哪一个的时候, 那个类才会被加载, 比如:main方法中, B没有被用到, 所以, 他不会被加载到内存中.


运行结果


********Dynamic load class**************
********load A**************
********initial A**************
*********load test*****************

我们看到A类被加载了,而B类没有被加载,原因是B类只声明了,没有用到。

总结几点如下:

  1. 静态代码块在构造方法之前执行
  2. 没有被真正使用的类不会被加载


二. 类加载器


2.1 类加载器的类型


类主要通过类加载器来加载, java里面有如下几种类加载器


1. 引导类加载器(Bootstrap ClassLoader)


在上面类加载流程中,说到在 [启动虚拟机的过程中, 会创建一个引导类加载器的实例] 这个引导类加载器的目的是什么呢?加载类

引导类加载器主要负责加载最最核心的java类型。 这些类库位于jre目录的lib目录下**. 比如:rt.jar, charset.jar等,


2. 扩展类加载器(Ext ClassLoader)


扩展类加载器主要是用来加载扩展的jar包。 加载jar的目录位于jre目录的lib/ext扩展目录中的jar包


3. 应用程序类加载器(App CloassLoader)


主要是用来加载用户自己写的类的。 负责加载classPath路径下的类包


4. 自定义类加载器


负责加载用户自定义路径下的类包

引导类加载器是由C++帮我们实现的, 然后c++语言会通过一个Launcher类将扩展类加载器(ExtClassLoader)和应用程序类加载器(AppClassLoader)构造出来, 并且把他们之间的关系构建好.


2.2 案例


案例一:测试jdk自带的类加载器


package com.lxl.jvm;
import sun.misc.Launcher;
import java.net.URL;
public class TestJDKClassLoader {
    public static void main(String[] args) {
        /**
         * 第一个: String 是jdk自身自带的类,位于jre/lib核心目录下, 所以, 他的类加载器是引导类加载器
         * 第二个: 加密类的classloader, 这是jdk扩展包的一个类
         * 第三个: 是我们当前自己定义的类, 会被应用类加载器加载
         */
        System.out.println(String.class.getClassLoader());                     System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
    }
}

我们来看这个简单的代码, 运行结果:

null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
解析:
 第一个: String 是jdk自身自带的类, 所以, 他的类加载器是引导类加载器,引导类加载器是c++代码,所以这里返回null
 第二个: 加密类的classloader, 这是jdk扩展包的一个类, jdk扩展包里面使用的是extClassLoader类加载器加载的
 第三个: 是我们当前自己定义的类, 会被AppClassLoader应用程序加载器加载.

我们看到ExtClassLoader和AppClassLoader都是Launcher类的一部分. 那Launcher类是什么东西呢?

上面有提到, Launcher类是jvm启动的时候由C++调用启动的一个类. 这个类引导加载器加载并创建其他的类加载器。

那么,第一个bootstrap引导类加载器, 那引导类加载器返回的为什么是null呢?

因为bootstrap引导类加载器, 他不是java的对象, 他是c++生成的对象, 所以这里是看不到的

案例二: BootstrapClassLoad和ExtClassLoader、AppClassLoader的关系

1187916-20200628144806628-589943450.png


如上图,左边是C语言程序代码实现, 右边是java代码实现。这里是跨语言调用,JNI实现了有c++向java跨语言调用。c语言调用的第一个java类是Launcher类。

从这个图中我们可以看出,C++调用java创建JVM启动器, 其中一个启动器是Launcher, 他实际是调用了sun.misc.Launcher类的getLauncher()方法. 那我们就从这个方法入手看看到底是如何运行的?

我们看到Lanucher.java类是在核心的rt.jar包里的,Lanucher是非常核心的一个类。

image.png



我们看到getLauncher()类直接返回了launcher. 而launcher是一个静态对象变量, 这是一个单例模式

C++调用了getLauncher()-->直接返回了lanucher对象, 而launcher对象是在构建类的时候就已经初始化好了. 那么,初始化的时候做了哪些操作呢?接下来看看他的构造方法.


1187916-20200628150030761-405200018.png

在构造方法里, 首先定义了一个ExtClassLoader. 这是一个扩展类加载器, 扩展类加载器调用的是getExtClassLoader(). 接下来看一看getExtClassLoader这个方法做了什么?

1187916-20200628163613077-212599620.png


这是一个典型的多线程同步的写法。

在这里, 判断当前对象是否初始化过, 如果没有, 那么就创建一个ExtClassLoader()对象, 看看createExtClassLoader()这个方法做了什么事呢?

1187916-20200628163818377-2147069863.png

doPrivileged是一个权限校验的操作, 我们可以先不用管, 直接看最后一句, return new Launcher.ExtClassLoader(var1). 直接new了一个ExtClassLoader, 其中参数是var1, 代表的是ext扩展目录下的文件.

1187916-20200628164153699-67136716.png

在ExtClassLoader(File[] var1)这个方法中, 这里第一步就是调用了父类的super构造方法. 而ExtClassLoader继承了谁呢? 我们可以看到他继承了URLClassLoader.


1187916-20200628164328030-860213917.png

而URLClassLoader是干什么用的呢? 其实联想一下大概能够猜数来, 这里有一些文件路径, 通过文件路径加载class类.

我们继续看调用的super(parent), 我们继续往下走, 就会看到调用了ClassLoader接口的构造方法:

image.png


这里设置了ExtClassLoader的parent是谁? 注意看,我们发现, ExtClassLoader的parent类是null.

image.png


这就是传递过来的parent类加载器, 那么这里的parent类加载器为什么是null呢? 因为, ExtClassLoader的父类加载器是谁呢? 他是Bootstrap ClassLoader. 而BootStrap ClassLoader是C++的类加载器, 我们不能直接调用它, 所以, 设置为null.


其实, ExtClassLoader在初始化阶段就是调用了ExtClassLoader方法, 初始化了ExtClassLoader类


接下来,我们回到Launcher的构造方法, 看看Launcher接下来又做了什么?


1187916-20200628165433071-2026979652.png

可以看到, 接下来调了AppClassLoader的getAppClassLoader(var1), 这个方法. 需要注意一下的是var1这个参数. var1是谁呢? 向上看, 可以看到var1是ExtClassLoader.

1187916-20200628165938469-1676277803.png


这是AppClassLoader, 应用程序类加载器, 这个类是加载我们自己定义的类的类加载器. 他也是继承自URLClassLoader.


我们来看看getAppClassLoader(final ClassLoader var0)方法. 这个方法的参数就是上面传递过来的ExtClassLoader


这里第一句话就是获取当前项目的class 文件路径, 然后将其转换为URL. 并调用了Launcher.AppClassLoader(var1x, var0), 其中var1x是class类所在的路径集合, var0是扩展的类加载器ExtClassLoader, 接下来, 我们进入到这个方法里看一看

1187916-20200628170425337-1889803398.png


AppClassLoader直接调用了其父类的构造方法, 参数是class类路径集合, 和ExtClassLoader

1187916-20200628170554270-1818581711.png

1187916-20200628170635557-567845896.png

最后, 我们看到, 将ExtClassLoader传递给了parent变量. 这是定义在ClassLoader中的属性, 而ClassLoader类是所有类加载器的父类. 因此, 我们也可以看到AppClassLoader的父类加载器是ExtClassLoader


同时, 我们也看到了, C++在启动JVM的时候, 调用了Launcher启动类, 这个启动类同时加载了ExtClassLoader和AppClassLoader.


public static void main(String[] args) {
  ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
  ClassLoader extClassLoader = appClassLoader.getParent();
  ClassLoader bootstrapClassLoad = extClassLoader.getParent();
  System.out.println("bootstrap class loader: " + bootstrapClassLoad);
  System.out.println("ext class loader " + extClassLoader);
  System.out.println("app class loader "+ appClassLoader);
}

通过这个demo, 我们也可以看出, appClassLoader的父类是extClassLoader, extClassLoader的父类是bootstrapClassLoader


输出结果:


bootstrap class loader: null
ext class loader sun.misc.Launcher$ExtClassLoader@2a84aee7
app class loader sun.misc.Launcher$AppClassLoader@18b4aac2


通过上面的源码分析,我们发现引导类加载器创建并加载了扩展类加载器和应用类加载器。而扩展类加载器的父加载器是引导类加载器。应用类加载器的父加载器是扩展类加载器。这个结构,决定了后面类的加载方式,也就是双亲委派机制。

相关文章
|
4月前
|
安全 前端开发 Java
【JVM的秘密揭秘】深入理解类加载器与双亲委派机制的奥秘!
【8月更文挑战第25天】在Java技术栈中,深入理解JVM类加载机制及其双亲委派模型是至关重要的。JVM类加载器作为运行时系统的关键组件,负责将字节码文件加载至内存并转换为可执行的数据结构。其采用层级结构,包括引导、扩展、应用及用户自定义类加载器,通过双亲委派机制协同工作,确保Java核心库的安全性与稳定性。本文通过解析类加载器的分类、双亲委派机制原理及示例代码,帮助读者全面掌握这一核心概念,为开发更安全高效的Java应用程序奠定基础。
93 0
|
3月前
|
安全 Java 应用服务中间件
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
|
2月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
51 3
|
3月前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
117 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
3月前
|
Arthas Java 测试技术
JVM —— 类加载器的分类,双亲委派机制
类加载器的分类,双亲委派机制:启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器;JDK8及之前的版本,JDK9之后的版本;什么是双亲委派模型,双亲委派模型的作用,如何打破双亲委派机制
JVM —— 类加载器的分类,双亲委派机制
|
2月前
|
前端开发 Java 应用服务中间件
JVM进阶调优系列(1)类加载器原理一文讲透
本文详细介绍了JVM类加载机制。首先解释了类加载器的概念及其工作原理,接着阐述了四种类型的类加载器:启动类加载器、扩展类加载器、应用类加载器及用户自定义类加载器。文中重点讲解了双亲委派机制,包括其优点和缺点,并探讨了打破这一机制的方法。最后,通过Tomcat的实际应用示例,展示了如何通过自定义类加载器打破双亲委派机制,实现应用间的隔离。
|
4月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
28 3
|
4月前
|
数据库 C# 开发者
WPF开发者必读:揭秘ADO.NET与Entity Framework数据库交互秘籍,轻松实现企业级应用!
【8月更文挑战第31天】在现代软件开发中,WPF 与数据库的交互对于构建企业级应用至关重要。本文介绍了如何利用 ADO.NET 和 Entity Framework 在 WPF 应用中访问和操作数据库。ADO.NET 是 .NET Framework 中用于访问各类数据库(如 SQL Server、MySQL 等)的类库;Entity Framework 则是一种 ORM 框架,支持面向对象的数据操作。文章通过示例展示了如何在 WPF 应用中集成这两种技术,提高开发效率。
63 0
|
4月前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
197 0
|
4月前
|
开发者 C# Windows
WPF布局大揭秘:掌握布局技巧,轻松创建响应式用户界面,让你的应用程序更上一层楼!
【8月更文挑战第31天】在现代软件开发中,响应式用户界面至关重要。WPF(Windows Presentation Foundation)作为.NET框架的一部分,提供了丰富的布局控件和机制,便于创建可自动调整的UI。本文介绍WPF布局的基础概念与实现方法,包括`StackPanel`、`DockPanel`、`Grid`等控件的使用,并通过示例代码展示如何构建响应式布局。了解这些技巧有助于开发者优化用户体验,适应不同设备和屏幕尺寸。
100 0