JVM之类装载子系统

本文深入探讨Java类加载过程,包括加载、链接、初始化阶段,以及类加载器的种类和双亲委派机制。解析如何避免类的重复加载,防止恶意攻击,确保沙箱安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

返回主博客

目录

概述

类加载过程

类加载器

双亲委派机制


概述

   类加载子系统负责将我们的class文件加载到JVM的内存模型中,在方法区加入他的类信息,在堆中创建对应的java.lang.Class对象。这个类就可以被java程序所使用了。

类加载过程

分为 加载->链接->初始化 三个阶段

 

  • 加载

通过类的全限定名,从文件系统(或者网络等其他方式)加载Class文件,将这些字节流的文件信息,转化为运行时的数据结构,生成java.lang.Class对象。作为方法区这个类的各种数据的访问入口。

加载的类信息放置在方法区,除了类信息,方法区还会存放运行时常量池(其实就是Class文件的常量池)信息,可能包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池映射)

 

  • 链接:

验证-> 准备 -> 解析 三个阶段

验证(verify)

字节码合法性:格式,元数据,字节码,引用符号。

准备(prepare)

为类变量分配内存以及为静态变量的赋默认初始值(即0值) 不包含final + static修饰的,final修饰的在编译阶段就已经赋值,所以准备阶段会显示初始化,而不是赋默认值。不会为实例变量分配初始化。

解析(Resolve)

将常量池引用(字面量形式)创建为直接引用

主要针对常量池中的Constant_Class_info,Constant_Fieldref_info,Constant_methodref_info,对接口,类方法,接口方法,方法类型等,对进行解析,将class文件中的这些字面量信息生成直接引用。

invokevirtual的处理。生成虚方法表,便于函数重写时知道调用哪个类的方法。

事实上解析往往伴随实例化完成的。

 

  • 初始化:

执行clinit (就是对类的static区的代码运行)

虚拟机必须保证同一个类的clinit是被加锁的。

 

补充:类和对象的创建和赋值顺序

1、父类和子类的 final static 的基本数据类型赋值(准备阶段) ->

    父类static执行或static{}(谁在按顺序执行) -> 执行 子类static执行

2、子类对象开始构造 -> 父类对象开始构造 -> 父类对象属性赋值 -> 子类对象属性赋值。

 

类加载器

主要有

启动类加载器(bootstrap ClassLoader),扩展类加载器(extClassLoader),应用类加载器(SystemClassLoader/AppClassLoader)

他们并非继承关系,这里说的parent 是 子类加载器会有一个成员变量叫parent的。比如AppClassLoader 的 parent属性中放的是ExtClassLoader。

 

分为两类:

引导类加载器(由C和C++编写的)

自定义类加载器(所有派生于ClassLoader的都是自定义加载器,比如extClassLoader,AppClassLoader)

代码案例:拿到ClassLoader.getSystemClassLoader(); 并打印他们的parent

        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        System.out.println(classLoader);
        //sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(classLoader.getParent());
        //sun.misc.Launcher$ExtClassLoader@85ede7b
        System.out.println(classLoader.getParent().getParent());
        //null
        System.out.println(ClassLoaderTest.class.getClassLoader());
        //sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(String.class.getClassLoader());
        //null
        System.out.println(Integer.class.getClassLoader());
        //null
        System.out.println(ArrayList.class.getClassLoader());
        //null

BootStrapClassLoader是获取不到的,并且是C和C++编写的,因此运行结果为null,他只负责加载java核心类库中的类,如String,Integer,ArrayList等。

 

  • 引导类加载器(BootStrapClassLoader /启动类加载器)

C和C++编写

启动java核心类库:jdk的,sun的,resource.jar的

不继承ClassLoader,无父类加载器(肯定的不是java语言编写的)

加载扩展类和应用类加载器,并指定为他们的父类加载器

出于安全BootStrapClassLoader只加载java,javax,sun开头的(并且这些类即时被我们自定义了,JVM也只会加载jdk中的)

 

  • 虚拟机自带类加载器(ExtClassLoader)

在cun.misc.Launcher实现,从java.ext.dir目录中或jdk安装目录jre/lib/ext目录下加载类,如果用户自定义类在此目录下,也会由自定义类加载器加载。

  • 虚拟机自带类加载器(AppClassLoader)

在cun.misc.Launcher实现,负责环境变量或者java.class.path下的类库,是系统中默认的类加载器。

 可以由ClassLoader.getSystemClassLoader()获取

 

  • 用户自定义类加载器

基本使用上面三种类加载器就够了,但是有些场景是需要的:

1、隔离加载类: 某些框架当中需要使用中间件,中间件和应用模块隔离的,需要将类加载到不同环境当中,确保应用jar包和中间件的不冲突。比如中间件有自己的jar,但是可能会有类的冲突。很多主流框架都会自定义类加载器。

2、修改类的加载方式。

3、扩展加载源,可能考虑从其他来源加载类

4、防止源码泄露,可以对字节码加密。解密的时候可以用自定义类加载器。

 

  • 如何自定义类加载器?

继承ClassLoader或者URLClassLoader

1.2之前可以重写loadClass,之后建议findClass配合defineClass实现。

 

  • 关于ClassLoader

主要接口

获取ClassLoader的方式

clazz.getClassLoader() /Class.forName("aaa/bbb.XXX").geClassLoader;

Thread.currentThread().getContextClassLoader()

ClassLoader.getSystemClassLoader()

DriverManager.getCallderClassLoader()

 

双亲委派机制

按需加载,需要用到才加载,是一种任务委派模式:

  1. 如果一个类收到类加载请求,他不会立即加载,而是交给其父类加载器,
  2. 如果父类加载器还存在父类加载器,则进一步委派,依次递归。一直到引导类加载器。
  3. 如果父类加载器可以完成类加载则加载返回,否则交给子类。

例子1

如果我们自定义在java.lang下建一个String类,还是会加载核心类库的String防止恶意攻击,因为委托到BootStrapClassLoader的时候会识别到其为java.lang下面的。如果在这个自定义的类下面运行main方法,便找不到这个方法,因为加载的是核心类库下面的String

例子2、

我们实现rt.jar下面的接口,那么接口由BootStrapClassLoader加载,而实现类由AppClassLoader加载。

 

双亲委派优势

  1. 避免重复加载(比如有继承时)
  2. 防止恶意攻击

体现了沙箱安全机制 :将对核心类库的保护,成为沙箱安全机制

 

补充

判断java中两个Class对象是不是同一个类的条件:

  1. 类全限类名一样
  2. ClassLoader是一样的

JVM必须知道一个类是由启动类加载器加载的还是启动类加载加载的,如果一个类是用户类加载器加载的,JVM会将类加载器的引用作为类的一部分信息保存在方法区当中,当解析一个类型到另一个类型引用的时候,JVM则必须保证两个类加载器是相同的。(动态链接)

 

JVM中对类的使用分为主动和被动:(主要看有没有调用其clinit方法)

主动创建方式:

  1. 创建一个类的实例
  2. 访问其静态变量
  3. 访问其静态方法
  4. 反射
  5. 初始化其子类
  6. 表明为启动类的

1.7提供的动态语言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值