JVM - 1.类加载子系统

1.类加载子系统

1.作用

  • 1.负责从文件系统或网络中加载字节码(.class)文件,即将物理磁盘上的字节码文件加载到内存中,生成供程序使用的类对象
    在这里插入图片描述
  • 2.字节码文件要求在文件开头有特定的文件标识(CA FE BA BE)
  • 3.类加载器(ClassLoader)只负责字节码文件的加载,是否可运行,由执行引擎(Execution Engine)决定
  • 4.类加载器是指特定的加载器,而类加载子系统是一个系统流程的统称
  • 5.加载生成的的类信息存放在称为方法区的内存空间中
  • 6.除了类的信息外,方法区还会存放运行时常量池(字节码文件中的Constant pool在运行时加载到内存中称为运行时常量池)信息,可能还包括字符串字面量数字常量,参考附录1

2.角色

在这里插入图片描述

  • 1.Car类通过编译器(javac)编译后生成Car.calss字节码文件并存在于本地磁盘上
  • 2.程序执行时字节码文件通过类加载器加载到JVM中,生成一个类对象
  • 3.通过该类对象可获取到类的构造器,根据该构造器可实例化出多个实例,通过实例的getClass方法也可以获取类对象本身
    在这里插入图片描述
    在这里插入图片描述
  • 4.字节码文件加载到JVM中,被称为DNA元数据模板,放在方法区
  • 5..class文件 -> JVM -> 元数据模板,该过程通过类加载器(Class Loader)实现
  • 6.物理磁盘上的字节码文件通过二进制流的方式加载到内存

3.类的加载过程

在这里插入图片描述
在这里插入图片描述

1.加载

  • 1.通过一个类的全限定名获取定义此类的二进制字节流
    在这里插入图片描述
  • 2.将这个字节流所代表的静态存储结构转化为方法区运行时数据结构
  • 3.方法区:抽象概念;落地实现:1.7及以前叫永久代,之后叫元空间
  • 4.并在内存中生成一个代表这个类的java.lang.Class对象,作为方法区该对象各种数据的访问入口
    在这里插入图片描述
    在这里插入图片描述

2.链接

1.验证(Verify)
  • 1.确保class文件的字节流包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机的自身安全
  • 2.主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证
  • 3.例:文件标识验证(CA FE BA BE),使用Binary Viewer工具进行查看
    在这里插入图片描述
2.准备(Prepare)
  • 1.为类变量/静态变量分配内存空间并且设置该类变量默认初始值
    • 1.成员变量:定义在方法体和语句块之外,不属于任何一个方法,作用域是整个类
      • 1.静态变量/类变量:用 static 修饰的成员变量
      • 2.全局变量/实例变量:无 static 修饰的成员变量
    • 2.局部变量:定义在方法或代码块中的变量,作用域是其所在的代码块
  • 2.例如:
    • 1.private static int a = 1,准备阶段会赋默认初始值为0,即a=0,然后在初始化(initial)阶段会赋值a = 1
  • 3.注意
    • 1.不同类型的类变量默认初始值不同
    • 2.这里不包含final修饰的static类变量,因为final修饰的是常量而不是变量,常量后期不会再被修改,所以在编译阶段就已经分配值,准备阶段只是显示初始化
    • 3.这里不会为实例变量默认初始化,因为当前还没创建对象,只是加载过程,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆
3.解析(Resolve)
  • 1.将常量池内的符号引用转换为直接引用的过程
  • 2.事实上解析操作往往会在JVM执行完初始化后再执行
  • 3.符号引用:一组符号来描述所引用的目标,符号引用的字面量形式明确定义在《java虚拟机规范》的class文件格式中
  • 4.直接引用:直接指向目标的指针,相对偏移量或一个间接定位到目标的句柄
  • 5.解析动作主要针对类或接口,字段,类方法,接口方法,方法类型等
  • 6.通过反编译可以查看class文件中的符号引用和直接引用
package com.java;

public class HelloApp {
   private static int a = 1;

   public HelloApp() {
   }

   public static void main(String[] args) {
       System.out.println(a);
   }
}
如下如示:Constant pool是常量池,其中以#开头的是符号引用,其余的是直接引用

F:\文档\笔记\代码\JVMDemo\out\java>javap -v HelloApp.class
Classfile /F:/文档/笔记/代码/JVMDemo/out/java/HelloApp.class
 Last modified 2022-11-9; size 608 bytes
 MD5 checksum 5964d34bba8f8bf4e817be8fe95a17fe
 Compiled from "HelloApp.java"
public class com.java.HelloApp
 minor version: 0
 major version: 52
 flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
  #1 = Methodref          #6.#23         // java/lang/Object."<init>":()V
  #2 = Fieldref           #24.#25        // java/lang/System.out:Ljava/io/PrintStream;
  #3 = Fieldref           #5.#26         // com/atguigu/java/HelloApp.a:I
  #4 = Methodref          #27.#28        // java/io/PrintStream.println:(I)V
  #5 = Class              #29            // com/atguigu/java/HelloApp
  #6 = Class              #30            // java/lang/Object	<=符号引用 
  #7 = Utf8               a	<=直接引用
  #8 = Utf8               I
  #9 = Utf8               <init>
 #10 = Utf8               ()V
 #11 = Utf8               Code
 #12 = Utf8               LineNumberTable
 #13 = Utf8               LocalVariableTable
 #14 = Utf8               this
 #15 = Utf8               Lcom/atguigu/java/HelloApp;
 #16 = Utf8               main
 #17 = Utf8               ([Ljava/lang/String;)V
 #18 = Utf8               args
 #19 = Utf8               [Ljava/lang/String;
 #20 = Utf8               <clinit>
 #21 = Utf8               SourceFile
 #22 = Utf8               HelloApp.java
 #23 = NameAndType        #9:#10         // "<init>":()V
 #24 = Class              #31            // java/lang/System
 #25 = NameAndType        #32:#33        // out:Ljava/io/PrintStream;
 #26 = NameAndType        #7:#8          // a:I
 #27 = Class              #34            // java/io/PrintStream
 #28 = NameAndType        #35:#36        // println:(I)V
 #29 = Utf8               com/atguigu/java/HelloApp
 #30 = Utf8               java/lang/Object
 #31 = Utf8               java/lang/System
 #32 = Utf8               out
 #33 = Utf8               Ljava/io/PrintStream;
 #34 = Utf8               java/io/PrintStream
 #35 = Utf8               println
 #36 = Utf8               (I)V
{
 public com.java.HelloApp();
   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 7: 0
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0       5     0  this   Lcom/atguigu/java/HelloApp;

 public static void main(java.lang.String[]);
   descriptor: ([Ljava/lang/String;)V
   flags: ACC_PUBLIC, ACC_STATIC
   Code:
     stack=2, locals=1, args_size=1
        0: getstatic     #2                  // Field >java/lang/System.out:Ljava/io/PrintStream;
        3: getstatic     #3                  // Field a:I
        6: invokevirtual #4                  // Method >java/io/PrintStream.println:(I)V
        9: return
     LineNumberTable:
       line 12: 0
       line 13: 9
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0      10     0  args   [Ljava/lang/String;

 static {};
   descriptor: ()V
   flags: ACC_STATIC
   Code:
     stack=1, locals=0, args_size=0
        0: iconst_1
        1: putstatic     #3                  // Field a:I
        4: return
     LineNumberTable:
       line 8: 0
}
SourceFile: "HelloApp.java"

3.初始化

  • 1.初始化阶段就是执行类构造器方法(<clinit>())的过程,可使用jclasslib查看(下载地址
  • 2.此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作(类变量的显式赋值)和静态代码块中的语句合并而来,注意不包含静态方法
  • 3.类构造器方法中指定按语句在源文件中出现的顺序执行
  • 4.<clinit>()不同于类的构造器,类的构造器方法对应的是init()方法
  • 5.若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕
  • 6.虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁(多线程下如果有一个线程加载,则其他加载线程会被阻塞;即保证类只会被加载一次,加载后的类对象保存在方法区)
  • 7.<clinit>()只有在类中有对静态变量静态代码块操作时才会有,其他情况不会存在(已测试静态方法不会存在)
public class HelloApp2 {
   private static int num = 1; //prepare:num = 0 ---> initial : num = 1 ---> num = 2

   static {
       num = 2;
       number = 20;
   }

   private static int number = 10; //prepare:number = 0 ---> initial : number = 20 ---> number = 10


   public static void main(String[] args) {
       System.out.println(num);  // num = 2
       System.out.println(number); // number = 10
   }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 8.非法的前项引用,可以提前赋值,但不是不能提前引用

在这里插入图片描述

4.类加载器

  • 1.JVM支持两种类型的类加载器:
    • 1.引导类加载器(Bootstrap ClassLoader)
    • 2.自定义类加载器(User-Defined ClassLoader)
  • 2.一般来说自定义类加载器指的是程序中由开发人员自定义的类加载;但是《Java虚拟机规范》中将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
  • 3.程序中最常见的三个类加载器
    • 1.Bootstrap Class Loader:引导类加载器
    • 2.Extension Class Loader:扩展类加载器
    • 3.System Class Loader:系统类加载器
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
  • 4.以上四者之间(引导,扩展,系统,自定义)是包含关系,不是上层下层,也不是子父类的继承关系
  • 5.引导类加载器通过C/C++语言编写无法直接获取;扩展类加载器包含系统类加载器
    public class ClassLoaderTest {
        public static void main(String[] args) {
    
            //获取系统类加载器
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
            System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
    
            //获取其上层:扩展类加载器
            ClassLoader extClassLoader = systemClassLoader.getParent();
            System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19
    
            //获取其上层:获取不到引导类加载器
            ClassLoader bootstrapClassLoader = extClassLoader.getParent();
            System.out.println(bootstrapClassLoader);//null
    
            //对于用户自定义类来说:默认使用系统类加载器进行加载
            ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
            System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
    
            //String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载
            ClassLoader classLoader1 = String.class.getClassLoader();
            System.out.println(classLoader1);//null
    
            ClassLoader classLoader2 = Integer.class.getClassLoader();
            System.out.println(classLoader2);//null
        }
    }
    
  • 7.通过下列代码可以动态获取到引导类,扩展类,系统类加载器负责加载的类,其中越底层能加载的类就越多
    public class ClassLoaderTest1 {
        public static void main(String[] args) {
            System.out.println("**********引导类加载器**************");
            //获取BootstrapClassLoader能够加载的api的路径
            URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
            for (URL element : urLs) {
                System.out.println(element.toExternalForm());
            }
            //从上面的路径中随意选择一个类,来看看他的类加载器是什么:引导类加载器
            ClassLoader classLoader = Provider.class.getClassLoader();
            System.out.println(classLoader);
    
            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@1540e19d
    
    		System.out.println("***********系统类加载器*************");
    		String appDirs = System.getProperty("java.class.path");
    		for (String path : appDirs.split(";")) {
    			System.out.println(path);
    		}
    		
         	//从上面的路径中随意选择一个类,来看看他的类加载器是什么:系统类加载器
         	ClassLoader classLoaderTest = ClassLoaderTest1.class.getClassLoader();
            System.out.println(classLoaderTest);//sun.misc.Launcher$AppClassLoader@18b4aac2
        }
    }
    

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

  • 1.该类加载器使用C/C++语言实现,是JVM的一部分,通过Java代码是无法获取的
  • 2.该类加载器用来加载Java的核心库,提供JVM自身需要的类
    • 1.JAVA_HOME/jre/lib目录下的rt.jarresources.jar
    • 2.sun.boot.class.path路径下的内容
  • 3.该类加载器并不继承自java.lang.ClassLoader,没有父加载器
  • 4.该类加载器也用来加载扩展类和系统类加载器,并指定为他们的父类加载器
  • 5.出于安全考虑,引导类加载器只加载包名为javajavaxsun等开头的类

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

  • 1.Java语言编写,由sun.misc.Launcher$ExtClassLoader实现(内部类),该加载器是JVM自带的
  • 2.ExtClassLoader派生于ClassLoader抽象类
  • 3.该类加载器的父类加载器为引导类加载器
  • 4.从java.ext.dirs系统属性所指定的目录中加载类库或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库
  • 5.如果用户创建的jar包放在此目录(jre/lib/ext)下,也会自动由扩展类加载类加载,主要用来加载核心包外的扩展目录下的jar包
    在这里插入图片描述

3.系统类加载器(System Class Loader)

  • 1.Java语言编写,由sun.misc.Launcher$AppClassLoader实现(内部类),该加载器是JVM自带的
  • 2.AppClassLoade派生于ClassLoader抽象类
  • 3.该类加载器的父类加载器为扩展类加载器
  • 4.该类加载器负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
  • 5.该类加载器是程序中默认的类加载器,一般来说Java应用的类都是由它来完成加载
  • 6.通过ClassLoader.getSystemClassLoader()方法可以获取到该类加载器
    在这里插入图片描述

4.用户自定义类加载器

  • 1.Java开发可以自定义类加载器,定制类的加载方式
  • 2.自定义类加载器的优势
    • 1.隔离加载类(不同中间件的加载是隔离的,确保加载jar包时相同名称的路径不会冲突)
    • 2.修改类加载的方式(修改为需要的时候动态的加载)
    • 3.扩展加载源(本地磁盘,网络,扩展其他加载源)
    • 4.防止源码泄露(自定义类加载器实现加密解密)
  • 3.实现步骤
    • 1.通过继承抽象类java.class.ClassLoader的方式,实现自定义类加载器
    • 2.JDK1.2之前,自定义类加载器会去继承ClassLoader类并重写loadClass()方法,从而实现自定义的类加载类
    • 3.JDK1.2之后,不建议覆盖loadClass()方法,建议把自定义的类加载逻辑写在findClass()方法中
    • 4.编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样可以避免去编写findClass()方法以及获取字节码流的方式,使自定义类加载器编写更加简洁
    package com.java;
    
    import java.io.FileNotFoundException;
    
    /**
     * 自定义用户类加载器
     */
    public class CustomClassLoader extends ClassLoader {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
    
            try {
                byte[] result = getClassFromCustomPath(name);
                if(result == null){
                    throw new FileNotFoundException();
                }else{
    				//如果不为null则继续调用该方法
                    return defineClass(name,result,0,result.length);
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
    
            throw new ClassNotFoundException(name);
        }
    
    	//根据指定路径已二进制流的方式读入到内存中形成字节数组
        private byte[] getClassFromCustomPath(String name){
            //从自定义路径中加载指定类:细节略
            //如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。
            return null;
        }
    
        public static void main(String[] args) {
            CustomClassLoader customClassLoader = new CustomClassLoader();
            try {
                Class<?> clazz = Class.forName("One",true,customClassLoader);
                Object obj = clazz.newInstance();
                System.out.println(obj.getClass().getClassLoader());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

5.ClassLoader

  • 1.ClassLoader是一个抽象类,除引导类加载器其余的类加载器都继承自ClassLoaser
  • 2.sun.misc.LauncherJVM的入口应用,ExtClassLoaderAppClassLoader都是Launcher的内部类
  • 3.加载类生成Class的方式:
    • 1.通过loadClass方法传入一个想要加载的路径然后返回Class的类实例
    • 2.通过findClassdefineClass配合传入一个想要加载的路径然后返回Class的类实例

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

1.获取ClassLoader的途径

在这里插入图片描述
在这里插入图片描述

6.双亲委派机制

  • 1.Java虚拟机class文件采用的是按需加载的方式,当需要使用该类时才会将它的class文件加载到内存生成Class对象
  • 2.Java虚拟机加载某个类的class文件时,采用的双亲委派机制,即把请求交由父类处理,它是一种任务委派模式

1.工作原理

  • 1.如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行
  • 2.如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的引导类加载器
  • 3.如果父类加载器可以完成类加载任务,则成功返回,如果父类加载器无法完成此加载任务,子类才会尝试自己去加载,这就是双亲委派机制
    在这里插入图片描述

2.实例

  • 1.本地创建一个和Java.lang.String同样包级的String类,如果该类被加载,则会输出静态代码块中的内容
    package java.lang;
    
    public class String {
        static{
            System.out.println("我是自定义的String类的静态代码块");
        }
    }
    
  • 2.创建一个测试类,调用Java.lang.String
    public class StringTest {
    
        public static void main(String[] args) {
            java.lang.String str = new java.lang.String();
            System.out.println("hello");
    
            StringTest test = new StringTest();
            System.out.println(test.getClass().getClassLoader()); //自定义的类加载器一般是系统类加载器
    		System.out.println(str.getClass().getClassLoader()); //核心类库加载器是引导类加载器
        }
    }
    
  • 3.测试结果发现自动调用的是核心类库中String类而不是本地中的String类(只限测试使用),因此可防止恶意攻击导致项目崩溃
    在这里插入图片描述
  • 4.上述自定义String类添加main方法并执行,因为双亲委派机制,最后本地String类的加载交给引导类加载器去加载核心类库中的String类,而该类不存在main方法,所以会报错
    在这里插入图片描述
  • 5.系统类接口由引导类加载器加载,实现其功能的第三方的jar包加载一般是通过线程上下文类加载器加载,默认为系统类型加载器
    在这里插入图片描述

3.优势

  • 1.避免类的重复加载
  • 2.保护程序的安全,防止核心API被随意篡改,防止自定义的类使JVM崩溃
    • 例:自定义类java.lang.String
  • 3.禁止自定义包名与系统包名冲突,因为该包名下的类由引导类加载器加载,但是该类并不存在于引导类加载器要加载的路径中
    在这里插入图片描述

4.沙箱安全机制

  • 1.沙箱:是一个限制程序运行的环境
  • 2.沙箱机制:是将Java代码限定在虚拟机JVM特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏,可参考Java沙箱机制
  • 3.简单来说
    • 1.上述自定义String类,加载的时候会优先使用引导类加载器加载
    • 2.引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java\lang\String.class)
    • 3.上述报错信息说没有main方法就是因为加载的是rt.jar包中的String类,这样可以保证对java核心源代码的保护,这就是沙箱安全机制

5.判断两个Class对象是否为同一个类

  • 1.JVM中表示两个Class对象是否为同一个类存在两个必要条件
    • 1.类的完整类名必须一致,包括包名
    • 2.加载这个类的classLoader(指:ClassLoader实例对象)必须相同(例:上述两个String类不同,因为类加载器不同)
  • 2.JVM中即使两个类对象(Class对象)来源同一个class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个对象也是不相等的
  • 3.JVM必须知道一个类型是由引导类加载器加载还是由自定义类加载器加载的
  • 4.如果一个类型是由自定义类加载器加载的,JVM会将这个类加载器的一个引用作为类型信在这里插入代码片息的一部分保存在方法区中,引导类除外因为该类标识为null
  • 5.当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的(动态链接)

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

  • 1.Java程序对类的使用方式分为:主动使用和被动使用
    • 1.主动使用:分为七种情况
      • 1.创建类的实例
      • 2.访问某个类或接口的静态变量,或者对该静态变量赋值
      • 3.调用类的静态方法
      • 4.反射(Class.forName等)
      • 5.初始化一个类的子类
      • 6.Java虚拟机启动被标明启动类的类
      • 7.JDK7开始提供的动态语言支持:
        • 1.java.lang.invoke.MethodHandle实例的解析结果
        • 2.REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化
    • 2.被动使用
      • 1.除以上七种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化
  • 2.不管是主动使用还是被动使用都会被加载,只要使用都会被加载,但是只有主动使用会执行类加载的初始化步骤
  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值