2、类加载-方法区

类的加载阶段分别有:

加载,验证,准备,解析,初始化

其中只有加载是java层面完成的,将class文件载入到内存,后续都是通过调动native方法完成。

JAVA层面的类加载

类是由类加载系统创建的,没有类加载器,也就没法执行任何java程序,所以只能由C++先构件类加载系统。

用户执行main.class → C++初始化虚拟机 → C++创建BootstrapClassLoader →

C++使用BootstrapClassLoader加载sun.misc.Launcher类 →

C++创建Launcher类 →

Launcher在构造函数中构件完整的类加载系统 →

C++通过Launcher.getClassLoader找到AppClassLoader,用于加载main所在的类 →

C++调用静态方法mian → 用户程序开始执行

public Launcher() {
    //先创建ExtClassLoader
    //extClassLoader的父级加载器是bootstrapClassLoader, 但并不是通过parent属性传递的
    //而是在loadClass方法中通过代码处理的,因为bootstrapClassLoader是C++对象,无法直接引用。
    Launcher.ExtClassLoader var1 = Launcher.ExtClassLoader.getExtClassLoader();
    //然后创建AppClassLoader,将extClassLoader设置为appClassLoader的父加载器
    //将appClassLoader设置为系统默认加载器
    this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);

    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    SecurityManager var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
    System.setSecurityManager(var3);
}

public ClassLoader getClassLoader() {
    return this.loader;
}

双亲委派

BootstrapClassLoader

↑ ↓

ExtClassLoader

↑ ↓

AppClassLoader

java的类加载:

protected Class<?> loadClass(String name)
{
    //检查是否已被加载过
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        //若没有被加载过,委托给父级加载
        if (parent != null) {
            c = parent.loadClass(name, false);
        } else {
            //若没有父级(只有extClassLoader没有父)
            //委托给引导类加载器
            c = findBootstrapClassOrNull(name);
        }
        if (c == null) {
            //所有上级都没加载,自身开始加载
            c = findClass(name);
        }
    }
    return c;
}

双亲委派组成了金字塔式的结构,通过最底部的加载器进行类加载,底部的加载器先不做任何处理直接委托给上级加载,直至最顶级;当顶级不处理时,才交给下级处理,下级才开始尝试使用findClass进行类加载。金字塔中越高层的加载器,加载的优先级就越高,

若是从金字塔顶部(BootstrapClassLoader)开始,就可以少遍历一次,为什么没有这么设计呢?

因为顶部加载器只负责加载java运行时的内部类,数量不多; 而用户类则可能很庞大,若是从顶级加载器开始,找内部类会很快, 但找用户类就比较麻烦。若是用户实现了很多的自定义加载器,组成了树形结构,又产生了分支问题。

作用

沙箱安全:内部类通过上级加载器加载,用户无法再覆盖类定义

实现类名的唯一性:通过类名询问了所有的类加载器,保证了不会被重复加载。

自定义类加载器

网络加载类:重写findClass的字节码加载部分。

热加载类:通过废弃类对应的classloader,从而使类重新被加载。

打破双亲委派的应用

通过类名实现了唯一性,虽然有好处,但也存在不方便的地方。

比如同一个类名,不同版本的类需要同时存在。

tomcat通过打破双亲委派实现了web容器的互相隔离,同一个类名,可以多版本共存。

class字节码如何转存到方法区

class文件结构

类Cat

 

按16进制打开后

class文件的前半部分具有一定的阅读性,都是各种类名和字符串,它是class文件的常量区,编译器将我们的java文件中固定不变的部分(各种名称:包名,类名,属性名,方法名,引用的类名,方法名,java的常量等)全部提取到一块,并赋予一个编号#1 #2 #3等

在指令逻辑部分,就可以直接使用#1 #2等编号来代替常量

常量池后面几乎都是复杂的结构体(给C++使用),需要通过工具转成一定的结构才能阅读。

通过javap命令可以美化class文件的展示

javap -v Cat.class

Classfile /E:/maomaotou/test/target/classes/com/maomaotou/test/Cat.class
  Last modified 2023-3-14; size 577 bytes
  MD5 checksum 084a57aed2d276b98b66f5871a47afd8
  Compiled from "Cat.java"
public class com.maomaotou.test.Cat
  minor version: 0
  major version: 49
  flags: ACC_PUBLIC, ACC_SUPER
  //常量池,几乎是拆解了的java文件零件盒
Constant pool:
   #1 = Methodref          #10.#24        // java/lang/Object."<init>":()V
   #2 = String             #25            // mi
   #3 = Fieldref           #9.#26         // com/maomaotou/test/Cat.name:Ljava/lang/String;
   #4 = Class              #27            // java/lang/StringBuilder
   #5 = Methodref          #4.#24         // java/lang/StringBuilder."<init>":()V
   #6 = String             #28            // i am
   #7 = Methodref          #4.#29         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #4.#30         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Class              #31            // com/maomaotou/test/Cat
  #10 = Class              #32            // java/lang/Object
  #11 = Utf8               name
  #12 = Utf8               Ljava/lang/String;
  #13 = Utf8               <init>
  #14 = Utf8               ()V
  #15 = Utf8               Code
  #16 = Utf8               LineNumberTable
  #17 = Utf8               LocalVariableTable
  #18 = Utf8               this
  #19 = Utf8               Lcom/maomaotou/test/Cat;
........
{
//构造函数
  public com.maomaotou.test.Cat();
  //返回void
    descriptor: ()V
    //public权限
    flags: ACC_PUBLIC
    Code:
    //stack操作数栈深度为2(可见操作数栈是编译器已经确定了)
    //locals本地变量表数量为1    
      stack=2, locals=1, args_size=1
      //取this
         0: aload_0
         //调用父类构造函数
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         //设置name属性
         5: ldc           #2                  // String mi
         7: putfield      #3                  // Field name:Ljava/lang/String;
        10: return
        //指令行与java代码行映射,用于报错时提示java代码行
      LineNumberTable:
        line 3: 0
        line 4: 4
        //本地变量表详情
        //start 指令行  length 长度  slot 槽 name变量名 
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   Lcom/maomaotou/test/Cat;

  public java.lang.String say();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #4                  // class java/lang/StringBuilder
         3: dup
         4: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
         7: ldc           #6                  // String i am
         9: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        12: aload_0
        13: getfield      #3                  // Field name:Ljava/lang/String;
        16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        22: areturn
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      23     0  this   Lcom/maomaotou/test/Cat;
}
//源文件
SourceFile: "Cat.java"

验证阶段

主要验证的是class文件规范,格式、语义、指令是否正常等; 但是它无法对class文件与java文件是否一致做验证, 如果在class文件修改code区指令, 并且符合规范,则会被正常加载。 比如将某个update操作,改为delete,就能实现破坏性行为。

准备

为静态变量分配内存。

解析

在上面看到的class文件中,常量池是通过123编号关联指令的,当要加入到运行时内存时,有很多class文件一同被加入,肯定不能再用这个编号了。解析阶段就是将这些符号引用(#1 #2),转为直接引用(内存地址)。

初始化

为静态变量初始化值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值