java序列化与反序列学习(二)

类的动态加载

类加载

类加载的时机

类加载,即虚拟机加载.class文件。什么时候虚拟机需要开始加载一个类呢?虚拟机对此没有规范约束,交给虚拟机把握。

Javac原理

javac是用于将源码文件.java编译成对应的字节码文件.class。

其步骤是:源码——>词法分析器组件(生成token流)——>语法分析器组件(语法树)——>语义分析器组件(注解语法树)——>代码生成器组件(字节码)。

类加载过程

先在方法区找class信息,有的话直接调用,没有的话则使用类加载器加载到方法区(静态成员放在静态区,非静态成功放在非静态区),静态代码块在类加载时自动执行代码,非静态的不执行;先父类后子类,先静态后非静态;静态方法和非静态方法都是被动调用,即不调用就不执行。

类加载的流程如下图:

image-20211110190140776

  • 加载
    • 通过类的全名限定获取定义此类的二进制字节流。
    • 将字节流代表的静态存储结构转化为方法区的运行时数据结构。
    • 在内存(方法区)生成一个代表这个类的class对象,作为方法区这个类的各种数据访问入口。

    加载和连接是交叉进行的,加载未完成可能连接已经开始了。

  • 验证

    检查class是否符合要求,非必须阶段,对程序的运行期没有影响,-Xverif:none 关闭(可以提高启动速度)

    • 文件格式验证(魔数、常量类型);

    • 元数据验证(语义分析);

    • 字节码验证(语义合法);
    • 符号引用验证;
  • 准备

    正式为类变量(static成员变量)分配内存并设置类变量初始值(零值)的阶段,这个变量所使用的内存都将在方法区进行分配,这时候的内存分配仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起在堆中进行分配。

  • 解析

    虚拟机将常量池内的符号引用替换为直接引用的过程。

  • 初始化

    初始化阶段是执行类构造器\<clinit>()方法的过程,虚拟机会保证一个类的类构造器\<clinit>()在多线程环境中被正确的加锁,同步;如果多个线程同时初始化一个类,那么只会有一个线程区执行这个类的类构造器,其他线程阻塞等待,直到\<clinit>()方法完毕,同一个类加载器,一个类只会被初始化一次。

代码分析

  • 类加载的时候会执行代码

  • 初始化:静态代码块

  • 实例化:构造代码块、无参构造函数

构建一个Person类

package loadclassTest;

import java.io.Serializable;

public class Person implements Serializable {
    public String name;
    private int age;

    public static int id;

    static {
        System.out.println("静态代码块");
    }

    public static void staticMethod(){
        System.out.println("静态方法");
    }

    {
        System.out.println("构造代码块");
    }

    public Person() {
        System.out.println("无参构造");
    }

    public Person(String name, int age) {
        System.out.println("有参构造");
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

测试

new Person(); //输出  静态代码块  构造代码块  无参构造
new Person("a", 18); //输出 静态代码块  构造代码块  有参构造
Person.staticMethod();  //输出 静态代码块  静态方法
Person.id = 1; //输出 静态代码块

可以看到无论是无参构造、有参构造、调用静态方法还是给静态变量赋值都会调用静态代码块

尝试直接调用class加载类Class c = Person.class;,发现没有输出,没有进行初始化只进行了加载

动态加载

可以使用Class.forName来动态加载类

Class<?> c = Class.forName("Person"); //输出  静态代码块

可以看到Class.forName进行了初始化,我们看下底层代码,进来看到调用了forName0函数

image-20211110194541408

跟进看下发现是由native修饰的,参数里有个initialize,应该是判断是否初始化

native:一个native方法就是一个Java调用非Java代码的接口。一个native方法是指该方法的实现由非Java语言实现,比如用C或C++实现。

image-20211110194631436

往上找能找到传3个参数的forName函数

image-20211110194950285

传入类名,是否初始化布尔值以及加载类,当initialize传入false时,没有输出

ClassLoader cl = ClassLoader.getSystemClassLoader();
Class<?> c = Class.forName("Person", false, cl);

调用newInstance()函数可以进行初始化
c.newInstance(); //输出  静态代码块  构造代码块  无参构造

这里需要了解什么是ClassLoader

参考连接:https://www.cnblogs.com/makai/p/11081879.html

类加载分析

ClassLoader cl = ClassLoader.getSystemClassLoader();
System.out.println(cl);

//输出sun.misc.Launcher$AppClassLoader@18b4aac2

可以看到ClassLoader.getSystemClassLoader()实际上获取的是AppClassLoader的类

打断点进行调试,跟进去分析

ClassLoader cl = ClassLoader.getSystemClassLoader();
Class<?> c = cl.loadClass("Person");

首先进入ClassLoader

image-20211118171842481

调用的是AppClassLoader的loadClass方法,然后继续跟进

image-20211118172248418

中间是一些安全检查的代码,走到后面调用父类的loadClass

image-20211118172947295

跟进然后又回到了ClassLoader,继续往下走,调用了findLoadedClass,看这个类是否加载过,这里就是双亲委派的流程,询问Extension ClassLoader、Bootstrap ClassLoader是否加载过,如果没有加载就加载

image-20211118173600366

image-20211118173450479

走到下面判断parant,parant不是父类,是父属性,然后存在ExtClassLoader,调用ExtClassLoader的loadClass

image-20211118173726833

由于ExtClassLoader没有loadClass方法所以又回到了ClassLoader

image-20211118174141974

image-20211118174350065

继续往下走,parent为null了

image-20211118174411969

然后就会去Bootstrap里寻找,因为这是个普通的类,不会用系统加载类去加载,所以还是没找到

image-20211118174655215

继续往下走,走到findClass

image-20211118174916365

由于ClassLoader的findClass的方法是需要重写的一个方法,所以会走到子类findClass

image-20211118175157339

本来是应该走到ExtClassLoader里的findClass,但实际上却走到了URLClassLoader的findClass里

image-20211118185559616

这是因为ExtClassLoader和AppClassLoader都没有findClass方法,而且都继承了URLClassLoader

image-20211118190010219

image-20211118190017811

由于现在是在ExtClassLoader里,肯定是找不到普通类的,所有继续往下走,然后退回到AppClassLoader里了,接着调用findClass

image-20211118190306597

又回到了URLClassLoader,继续跟进

image-20211118190336428

分析findClass

protected Class<?> findClass(final String name)
        throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    String path = name.replace('.', '/').concat(".class");  // 这里是获取类路径比如:Person.clss
                    Resource res = ucp.getResource(path, false);  // ucp是一个URLClassPath类,在当前加载路径去找这个类
                    if (res != null) {
                        try {
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        } catch (ClassFormatError e2) {
                            if (res.getDataError() != null) {
                                e2.addSuppressed(res.getDataError());
                            }
                            throw e2;
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

这里找到了这个类

image-20211118194317806

最后调用了defineClass,实际上是在这里进行了类的加载,继续跟进,走到了URLClassLoader的defindClass方法,中间都是一些检验的代码,最后又调用了一个defindClass

image-20211118194628362

走到了URLClassLoader的父类SecureClassLoader,又调用了一个defindClass,跟进

image-20211118194936325

然后又回到了ClassLoader的defindClass,这里调用了defineClass1

image-20211118195128680

而defineClass1是由native修饰的,是由c++写的,最后的类加载是在这完成的,到这就结束了

image-20211118195323203

流程:

  • 继承关系
    • ClassLoader->SecureClassLoader->URLClassLoader->AppClassLoader
  • 调用
    • loadClass->findClass(重写的方法)->defineClass(从字节码加载类)

使用URLClassLoader进行任意类加载

知识点

  • 支持的协议
    • file
    • http

测试

file

首先定义一个Hello类,进行编译,然后移动到别的目录,在把原本的类删了,进行测试

image-20211118202258471

public class Hello {
    static {
        System.out.println("Hello");
    }
}

成功进行加载

image-20211118201802053

尝试下弹出计算器

import java.io.IOException;

public class Test {
    static {
        try {
            Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

image-20211118202338733

http

在class路径起个http服务

image-20211118202645643

直接请求当前路径的类,成功加载

image-20211118202743197

同时也收到了请求

image-20211118202827127

使用defindClass进行任意类加载

image-20211118203920991

这里成功加载类

image-20211118204633764

分析

由于defineClass是私有类,只能使用反射进行获取对象,然后调用方法,实现类的加载

评论

  1. youyouyou
    Android Chrome 89.0.4389.72
    2年前
    2022-1-14 19:30:00

    (╯‵□′)╯︵┴─┴

发送评论 编辑评论


|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇