java虚拟机(六)--类的装载

类的字节码在“加载”阶段完成了字段、方法、常量池等信息的解析。
而一个Java文件从被加载到被卸载整个生命过程中,总共经历5个阶段:

	加载-》链接(验证+准备+解析)-》初始化-》使用-》卸载

1.加载:查找并加载类的二进制数据,在元数据区中生成class的数据结构(jvm内部的C++对象)
2、验证、准备、解析:验证加载的类的正确性,(文件格式,元数据,字节码,方法调用是否正确,符号引用)
准备:为类的静态变量分配内存(1.8时,静态变量放在镜相类class中)
解析,把常量池中的符号引用转换为直接引用(也就是把符号引用,转换为目标方法在方法表中的位置)
3、初始化:执行静态变量和静态代码块的初始化。也就是< clinit>方法

引用一个类的静态常量不会引起该类的初始化。
使用-XX:+TraceClassLoading参数可以查看类的加载情况。
访问一个类的静态字段,只会初始化定义该字段的类,而不会初始化其父类。

一个类只有被第一次主动使用时,才会被java虚拟机加载
主动使用的情况(6种)
1、创建类的实例
2、访问类的静态变量
3、调用类的静态方法
4、反射加载
5、初始化一个类的子类
6、java虚拟机启动时被标记为启动类的类

验证阶段:
格式检查-》语义检查-》字节码验证-》符号引用验证。

格式检查:魔数、版本、长度
语义检查:是否继承自final类、是否有父类、抽象方法是否有实现
字节码验证:跳转指令是否指向正确的位置 、操作数类型是否正确
符号引用验证:符号引用的直接引用(类、方法)是否存在。
clinit 方法是带锁的。多线程初始化class时,可能导致死锁。

ClassLoader负责类的加载:

方法作用
loadclass(string name)加载类
defineclass(byte[] bs,int start,int len)加载类
findclass(string name)自定义类的查找逻辑
findLoadedClass查找已经被自己加载的类

jvm会创建3种类加载器:

  1. Bootstrap classLoader 启动类加载器
  2. Extension classLoader 扩展类加载器
  3. AppClassLoader 系统类加载器

当系统需要查找一个类时,会从系统类加载器开始往上查找;当系统需要加载一个类时,会从启动类加载器开始从上往下加载。

启动类加载器由C语言实现,其它类加载器由Java语言实现。
Bootstrap classLoader无法由Java应用程序直接引用,它一般负责如下路径的类库:

  • %JAVA_HOME%/jre/lib
  • -Xbootclasspath 参数指定的目录
  • 系统属性sun.boot.class.path

Extension ClassLoader

  • %JAVA_HOME%/jre/lib/ext
  • 系统属性java.ext.dirs指定的类库

System classLoader

  • 环境变量 classpath
  • -cp
  • 系统属性java.class.path

可以结合这一篇关于源码的展示,来理解类加载流程

类加载的最终结果是在jvm方法区中创建一个instaceKlass实例对象(这个里面也保存有对应的class对象的指针也就是mirror镜像),但是创建完之后,又创建了一个java.lang.Class对象(这个对象保存在堆区,里面保存有指向instanceKlass的指针),这是一个镜像类,用于给Java代码反射用。

所以加载流程就是:

  1. 读取魔数和版本号
  2. 解析常量池
  3. 解析字段信息
  4. 解析方法
  5. 创建Java类的对等内部对象instanceKlass
  6. 创建镜相类

Java程序main主类加载链路:

  1. jvm启动,操作系统调用java.c::main()主函数,执行jvm的初始化逻辑。然后搜索Java程序的main函数所在的类使用LoadClass(env,classname)加载
  2. LoadClass经过一系列函数调用后,获取Java的系统类加载器
  3. 经过多层调用,最后使用appclassLoader加载Java主类

C/C++程序调用Java类方法,都会通过JavaCalls类来实现,该类中定义了各种call_*()接口,这些接口最终都调用call_stub例程,从而辅佐jvm执行Java方法。

在使用new关键字创建一个对象时,如果Java类是第一次被使用,就必须先执行加载、链接、初始化,进入慢分配流程。

Launcher类构造函数中创建了系统类加载器和扩展类加载器。

Landcher类在SystemDictory::java_system_loader()方法中创建,这是jvm中的一个方法。

jvm启动时会预加载很多核心类库,但是这些类库只走了加载阶段,链接阶段把符号引用换成直接引用(也就是对象的内存地址)

new对象策略:

  1. 类尚未被解析,直接进入慢分配
  2. 如果已经被解析,优先在栈上分配(需要开启逃逸分析,并且类成员变量满足要求),其次TLAB上分配,都不行就在eden区分配
  3. eden区分配失败,则进入慢分配流程
  4. 对象如果满足进入老年代的条件,直接分配在老年代

只有触发JIT时,才会进行逃逸分析。
标量替换(基本数据类型和引用类型都是标量)和栈上分配(字段不能太多)这两种优化技术都不会把对象分配在堆上。

TLAB是线程本地分配缓存区,thread local allocation buffer。小对象可以直接创建在里面,这样申请空间时,不需要同步,所以效率高些。每个线程都有这个空间,约占用eden的1%。

jvm向eden区申请内存时,用的技术叫做指针碰撞。基于CPU硬件的CAS原语。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值