前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM-10.类加载

JVM-10.类加载

作者头像
悠扬前奏
发布2019-05-28 12:50:16
3680
发布2019-05-28 12:50:16
举报

JVM-10.类加载

1. 类加载时机

类的生命周期

类从加载到内存到写在出内存,生命周期包括:

  • 加载(Loading)
  • 验证(Verification)
  • 准备(Preparation)
  • 解析(Resolution)
  • 初始化(Initialization)
  • 使用(Using)
  • 卸载(Unloading) 其中验证,准备,解析,三个步骤统称为连接(Linking)。
  • 加载,验证,准备,初始化和卸载5个阶段的顺序是确定的
  • 解析阶段可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定
  • 这些阶段按顺序开始,但是不是按顺序“进行”,“完成”,这些阶段通常相互交叉的混合式进行。在一个阶段的执行过程中调用,激活另外一个阶段
  • 加载的时机根据JVM不同而不同
  • 有且只有以下5种情况必须初始化:
    • 遇到new, getstatic, putstatic I或者incokestatic这4条字节码指令时,如果类没有初始化过,需要先触发初始化。它们对应的Java场景分别是:使用new关键字实例化对象、读取或者设置一个类的静态子弹(被final修饰,在编译器吧结果放入常量池的静态字段除外),调用一个雷的静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用的时,如果类没有初始化过,先触发其初始化
    • 初始化一个类的时,如果其父类没有进行过初始化,先触发其父类的初始化
    • 虚拟机启动的时,需要初始化main()方法所在的主类
    • 使用JDK1.7动态语言支持,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic方法句柄,并且这个方法所对应的类没有进行过初始化,需要先触发其初始化
  • HotSpot虚拟机可以用-XX:+TraceClassLoading参数观察此操作会导致子类的加载
  • 接口的初始化稍有不同:在一个类的初始化时,要求其父类全部初始化过了,但是借口初始化时,不要求其父类全部初始化过了,只有使用到父接口时(如引用接口中定义的常量)才会初始化

2. 类加载过程

2.1 加载

  • 加载阶段,JVM做三件事:
    1. 通过一个类的全限定名来获取此类的二进制字节流
    2. 将这个字节流代表的静态存储结构转化为方法区的运行时数据结构
    3. 在内存中生成一个代表这个类的java.lang.Clas对象,(HotSpot将Class对象存放在方法区)这个对象作为程序访问方法区这些类型数据访问的外部接口。
  • 加载阶段和连接阶段的部分内容交叉进行,但是开始时间保持固定先后顺序

2.2 验证

  • 验证是连接阶段的第一步,目的是确保Class文件中的字节流包含的信息符合当前虚拟机要求,且不会危害虚拟机自身安全
  • 包含四种验证动作:
    1. 文本格式验证:验证字节流是否符合Class文件的规范,且能被当前版本的JVM处理,包含的验证点有:
    • 是否以Magic Number0xCAFEBABE开头
    • 主次版本号是否在当前JVM处理范围之内
    • 常量池中的常量是否有不被支持的常量类型(检查常量tag标志)
    • 指向常量的各种索引值中是否有指向不存在的常量后者不符合类型的常量
    • CONSTANT_Utf8_Info型的常量是否有不符合UTF8编码的数据
    • Class文件中各部分和文件本身是否有被删除或者附加的其他信息
    • ……
    1. 元数据验证:对字节码的语义分析,以保证其描述的信息符合Java语言规范,包括的验证点有:
    • 这个类是否有父类(除了java.lang.Object之外)
    • 这个类的父类是否继承了不被允许继承的类(以final修饰的类)
    • 如果这个类不是抽象类,是否实现了其父类或者接口之中要求的所有方法
    • 类中的字段,方法是否和父类产生矛盾(覆盖父类final字段,不合规范的重载)
    1. 字节码验证:通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的,对类的方法器进行校验分析,保证被校验的类的方法在运行时不会做出危害虚拟机安全的事件:
    • 保证任意时刻操作栈的数据类型与指令代码都能配合工作
    • 保证跳转指令不会跳转到方法体以外的字节码指令上
    • 保证方法体中的类型转换有效 4.符号引用验证:在连接的第三个阶段——解析阶段发生。对类自身以外(常量池中的各种符号引用)的信息进行匹配校验,包括:
    • 符号引用中通过字符串描述的全限定名能否找到对应的类
    • 在指定类中是否存在符合方法的字段描述符和简单名称所描述的方法和字段
    • 符号引用中的类,字段,方法的访问性(private,protected,public,default)是否可以被当前类访问 可用-Xverify:none参数关闭大部分类验证措施,缩短虚拟机类加载时间

2.3 准备

准备是为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中分配。

  • 这时进行的内存分配仅包括类变量(static修饰),不包括实例变量,实例变量会在对象实例化时,随着对象一起分配在Java堆中
  • 这时的初始值通常指数据类型的零值,包括static修饰的值,因为此时没有任何Java方法执行,putstatic指令是在程序编译时,存放在类构造器<clinit>()方法中。

2.4 解析

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

  • 符号引用(Symbolic Reference):用一组符号类描述所引用的目标,符号引用可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。
  • 直接引用(Direct Reference):可以使直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。
  • 未规定解析阶段发生的时间,只要在部分操作符号引用的字节码指令之前,先对它们的符号进行引用解析。
  • 除incokeddynamic指令外,JVM可以对第一次解析的结果进行缓存来避免解析动作重复进行。
  • 解析动作主要针对类或接口,字段,类方法,接口方法,方法类型,方法句柄和调用点限定七类符号进行,分别对应常量池的七种常量类型。

2.5 初始化

  • 初始化才真正执行类中定义的Java字节码
  • 可以说初始化是执行类构造器<clinit>()方法的过程,构造器<clinit>()方法有以下特点:
    • 由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并而成,收集顺序是由语句在源文件中出现的顺序决定的,静态语句块只能访问定义在静态语句块之前的变量,定义在之后的变量,静态语句块可以赋值,但不能访问
  • <clinit>()方法和类的构造函数不同,它不需要显式调用父类构造器。
  • 父类的<clinit>()方法肯定会执行,所以父类的静态语句块要先于子类的变量赋值操作
  • <clinit>()对于类或者接口不是必须的,如果一个类没有静态语句块也没有对变量的赋值操作,编译器可以不生成<clinit>()方法
  • 接口不能使用静态语句块,但是仍有变量初始化的赋值操作。执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法,父接口的变量使用时,父接口才会初始化。接口的实现类在初始化的时候也不用执行接口的<clinit>()方法。接口只有自己的变量使用时,才会初始化。
  • JVM保证一个类的<clinit>()方法在多线程中被正确的加锁,同步。

3 类加载器

“通过一个类的全限定名来获取此类的二级制字节流”的操作可以被JVM外部来实现,执行这个操作的代码被称为“类加载器”。

  • 任何类都需要由加载它的类加载器和这个类本身一同确立在JVM中的唯一性,每一个类加载器,都有一个独立的类名称空间(namespace)。
  • JVM有两类加载器:
    • 启动类加载器(Bootstrap ClassLoader),用C++实现,是虚拟机的一部分,负责将存放在<JAVA_HOME>/lib目录中,或者被-Xbootclasspath参数指定的路径中的,并且虚拟机识别的类库加载到虚拟机内存中。
    • 其他加载器,用Java语言实现,独立于JVM,都继承自抽象类java.lang.ClassLoader,有两类:
      • 扩展类加载器(Extension ClassLoader),由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库
      • 应用程序类加载器(Applecation ClassLoader):由sum.misc.Laucher$AppClassLoader实现,也是ClassLoader中getSystemClassLoader方法的返回值,所以被称为系统类加载器,负责加载用户类路径上指定的类库,开发者可以直接使用。也是程序默认的类加载器。
  • 双亲委派模型:如果一个类加载器收到了类加载的请求,现将这个请求委派给父类加载器去完成,每个层次的类加载器都如此,所以所有加载的请求都会被传动到顶层的启动类加载器中,只有父加载器反馈自己无法完成加载请求时,子加载器才会尝试自己去加载。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.04.20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JVM-10.类加载
    • 1. 类加载时机
      • 类的生命周期
        • 2. 类加载过程
          • 2.1 加载
          • 2.2 验证
          • 2.3 准备
          • 2.4 解析
          • 2.5 初始化
        • 3 类加载器
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档