JVM (二)类加载子系统

711 阅读8分钟

目录

JVM详细结构 image-20201102192901123

1.类加载子系统的作用

  • 1.类加载子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识即16进制CA FE BA BE;
  • 2.加载后的Class类信息存放于一块成为方法区的内存空间。除了类信息之外,方法区还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)

2.类的加载过程

image-20201119211937081

类加载子系统:负责将java代码生成的class文件加载进内存,加载的类的信息存放于称为方法块的内存空间,除了加载类信息之外,方法区中还会存放运行时 的常量池信息,该常量池包含字符串常量和数字常量

class文件加载进内存需要经历三个步骤:

  • Loading:

    • 1.通过类加载器找到class文件,通过类的全限定类名,获取该类的字节流。
    • 2.将字节流所代表的静态存储结构转化为方法区的运行时数据结构
    • 3.在方法区中生成一个代表该类的一个java.lang.Calss对象,该Class对象包含了这个类的上下文信息,作为方法去各种数据访问的接口
  • Linking:

    • 1.验证(verify):保证class文件中的信息不会危害jvm的运行,不合法的字节码文件回报错
    • 2.准备(perpare):为类变量分配内存,并且设置该变量的默认值。即零值,
      • 准备阶段不包含final修饰的static(静态常量),因为final变量在编译时就会分配值,准备阶段会直接初始化
      • 不会为实例变量分配初始化,类变量会分配到方法区中,实例变量会随对象一起分配至java堆中
    • 3.解析(resolve):将常量池中的符号引用转化为直接引用
  • Initiallization:

    • 初始化阶段就是执行类构造器方法<clinit>()的过程,编译器将自动收集类中所有的类变量赋值语句和静态代码快中的赋值语句合并成clinit()方法.
    • 构造器方法中初始化指令是按照源文件中的声明顺序进行执行的
    • 子类的clinit()方法执行之前会先执行父类的clinit()方法
    • 虚拟机必须保证一个类的clinit方法在多线程下被同步加锁

3.类加载子系统

3.1类加载子系统的分类

1.JVM支持两种类型的加载器,分别为引导类加载器C/C++实现(BootStrap ClassLoader)和自定义类加载器由Java实现

2.从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。

3.注意上图中的加载器划分关系为包含关系,并不是继承关系

4.按照这样的加载器的类型划分,在程序中我们最常见的类加载器是:引导类加载器BootStrapClassLoader、自定义类加载器(Extension Class Loader、System Class Loader、User-Defined ClassLoader)

3.2各种类加器的作用

1.对于用户自定义类来说:将使用系统类System Class Loader加载器中的AppClassLoader进行加载

2.java核心类库都是使用引导类加载器BootStrapClassLoader加载的

  • 启动类加载器(引导类加载器)Bootstrap ClassLoader

    • 1.使用c/c++语言实现

    • 2.用来加载java核心类库,加载JAVA_HOME/jre/lib/rt.jar/resources.jar或sun.boot.class.path路径下的内容

    • 3.并不继承至java.lang.ClassLoader,没有父加载器

    • 4.加载扩展类(Extension ClassLoader)和应用程序类加载器(System Class Loader)

    • 5.为了安全,只考虑加载包名为java,javax,sun等开头的类

  • 扩展类加载器 (自定义类加载器)Extension ClassLoader

    • 1.由java语言进行编写,由sun.misc.Launcher$ExtClassLoader实现

    • 2.派生于ClassLoader类

    • 3.从java.ext.dirs系统属性所指定 的目录中加载类库,如果用户创建的jar包放在此目录下,也会由拓展类加载器自动加载

  • 系统应用程序加载器(系统类加载器)AppClassLoader

    • 1.由java语言编写

    • 2.负责加载环境变量classpath或系统属性java.class.path指定路径下的类库。

    • 3.该类加载器是程序中默认的类加载器,一般来说,java应用中的类都是由他来加载完成的

    • 4.通过ClassLoader getSystemClassLoader()方法可以获取到该类加载器

  • 自定义类加载器

    • 1.通过继承抽象类java.lang.ClassLoader类的方式,实现自己的类加载器,以满足一些特殊的需求

    • 2.把自定义的类加载逻辑编写在findClass()方法中

    • 3.没有太过负责的需求,可以直接继承URLClassLoader类

    • 4.主要的作用:

      • 隔离加载类
      • 修改类加载的方式
      • 拓展加载源
      • 防止源码泄漏
    /**
 * 虚拟机自带加载器
 */
public class ClassLoaderTest1 {
    public static void main(String[] args) {
        System.out.println("********启动类加载器*********");
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        //获取BootStrapClassLoader能够加载的api路径
        for (URL e:urls){
            System.out.println(e.toExternalForm());
        }

        //从上面的路径中随意选择一个类 看看他的类加载器是什么
        //Provider位于 /jdk1.8.0_171.jdk/Contents/Home/jre/lib/jsse.jar 下,引导类加载器加载它
        ClassLoader classLoader = Provider.class.getClassLoader();
        System.out.println(classLoader);//null

        System.out.println("********拓展类加载器********");
        String extDirs = System.getProperty("java.ext.dirs");
        for (String path : extDirs.split(";")){
            System.out.println(path);
        }
        //从上面的路径中随意选择一个类 看看他的类加载器是什么:拓展类加载器
        ClassLoader classLoader1 = CurveDB.class.getClassLoader();
        System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@4dc63996
    }
}

3.2CLassLoader的常用方法

image-20201103112007807

获取ClassLoader的途径

image-20201103112140476

4.双亲委派机制

4.1双亲委派机制的作用:

  • 避免类被重复加载,相同的类使用相同的类加载器进行加载

  • 防止核心API被篡改,核心类库,由启动类加载器进行加载,防止自定义的类与核心类重名而被加载,危害jvm的运行

image-20201103112432826

5.判断两个类是否相等

5.1在jvm中表示两个class对象是否为同一个类存在的两个必要条件

  • 类的完整类名必须一致,包括包名,即使类的完整类名一致

  • 同时要求加载这个类的ClassLoader(指ClassLoader实例对象)必须相同;是引导类加载器、还是定义类加载器,换句话说,在jvm中,即使这两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的.

5.2对类加载器的引用

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

6.类的主动使用与被动使用

java程序对类的使用方式分为:主动使用和被动使用,判断的依据即是否调用了clinit()方法

  • 主动使用在类加载系统中的第三阶段initialization即初始化阶段调用了clinit()方法

  • 而被动使用不会去调用clinit()方法

主动使用,分为七种情况

1.创建类的实例

2.访问某各类或接口的静态变量,或者对静态变量赋值

3.调用类的静态方法

4.反射 比如Class.forName(com.dsh.jvm.xxx)

5.初始化一个类的子类

6.java虚拟机启动时被标明为启动类的类

7.JDK 7 开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic句柄对应的类没有初始化,则初始化

除了以上七种情况,其他使用java类的方式都被看作是对类的被动使用,都不会导致类的初始化。

7沙箱安全机制

java安全模型的核心就是Java沙箱。沙箱机制就是讲Java代码限定在虚拟机JVM特定的运行范围中,并且严格限制代码对本地资源的访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。

沙箱主要限制系统资源访问,例如:CPU、内存、文件系统、网络。不同级别的啥想对这些资源访问的限制也可以不一样

当前最新的安全机制实现,引入了域(Domain)的概念。

虚拟机吧所有代码加载到不同的系统域和应用域:

  • 系统域部分专门负责与关键资源进行交互。
  • 应用域则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域(Protected Domain),对应不一样的权限(Permission)。存在于不同域中的类文件就具有了当前域的全部权限,下图是最新安全模型

通俗来说就是虚拟机把代码加载到拥有不同权限的域里,然后代码就拥有了该域的所有权限。这样就能控制不同代码拥有不同调用操作系统和本地资源的权限

组成沙箱的基本组件:

1.字节码校验器(bytecode verifier):确保Java类文件遵循Java语言规范。可以帮助Java程序实现内存保护 。核心类不经过字节码校验

2.类装载器:其中类装载器在3个方面对Java沙箱起作用

  • 防止恶意代码干涉善意代码(双亲委派机制)
  • 守护被信任的类库边界
  • 它将代码归入保护域,确定了代码可以进行哪些操作
  • 类装载器采用的机制是双亲委派机制: 1.从最内层JVM自带的类加载器开始加载,外层恶意同名类得不到加载从而无法调用

2.由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内部类,破坏代码就自然无法生效

3.存取控制器(access controller):存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定可以由用户指定。

4.安全管理器(security manager):是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高

5.安全软件包(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:安全提供者 消息摘要 数字签名 加密 鉴别