转载

Java基础知识点4

Java字节码

Java源文件编译之后,生成的class文件,它供虚拟机解释执行的二进制字节码文件。

其结构如下:

类型 名称 说明 长度
u4 magic 魔数,识别Class文件格式,0XCAFEBABE 4个字节
u2 minor_version 副版本号,如0x0000 2个字节
u2 major_version 主版本号,如0x0034 2个字节
u2 constant_pool_count 常量池计数 2个字节
cp_info constant_pool 常量池 n个字节
u2 access_flags 访问标志 2个字节
u2 this_class 类索引 2个字节
u2 super_class 父类索引 2个字节
u2 interfaces_count 接口计数 2个字节
u2 interfaces 接口索引集合 2个字节
u2 fields_count 字段个数 2个字节
field_info fields 字段集合 n个字节
u2 methods_count 方法计数器 2个字节
method_info methods 方法集合 n个字节
u2 attributes_count 附加属性计数 2个字节
attribute_info attributes 附加属性集合 n个字节

class文件只有两种数据类型:无符号数和表。

|数据类型|定义|说明| |:–|:–| |无符号数|无符号数可以用来描述数字、索引引用、数量值或按照utf-8编码构成的字符串值|其中无符号数属于基本的数据类型。以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节| |表|表是由多个无符号数或其他表构成的复合数据结构|所有的表都以“_info”结尾。由于表没有固定长度,所以通常会在其前面加上个数说明|

常量池

常量池主要存放两大类常量:

  • 字面量:文本字符串,声明为final的常量值
  • 符号引用:类和接口的全限定名,字段的名称和描述符,方法的名称和描述符

描述符

描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。

  • B:基本数据类型byte
  • C:基本数据类型char
  • D:基本数据类型double
  • F:基本数据类型float
  • I:基本数据类型int
  • J:基本数据类型long
  • S:基本数据类型short
  • Z:基本数据类型boolean
  • V:基本数据类型void
  • L:对象类型,如Ljava/lang/Object

javap

javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。

-help  --help  -?         输出此用法消息
 -version                 版本信息其实是当前javap所在jdk的版本信息不是class在哪个jdk下生成的
 -v  -verbose             输出附加信息包括行号本地变量表反汇编等详细信息
 -l                       输出行号和本地变量表
 -public                  仅显示公共类和成员
 -protected               显示受保护的/公共类和成员
 -package                 显示程序包/受保护的/公共类 和成员 (默认)
 -p  -private             显示所有类和成员
 -c                       对代码进行反汇编
 -s                       输出内部类型签名
 -sysinfo                 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)
 -constants               显示静态最终常量
 -classpath <path>        指定查找用户类文件的位置
 -bootclasspath <path>    覆盖引导类文件的位置

类加载器

类加载器(ClassLoader)是用来加载Class的。它负责将Class的字节码形式转换成内存形式的Class对象。主要作用:

  • 负责将 Class 加载到 JVM 中
  • 审查每个类由谁加载(父优先的等级加载机制)
  • 将 Class 字节码重新解析成 JVM 统一要求的对象格式

Java语言系统自带有三个类加载器:

Java基础知识点4

  • Bootstrap ClassLoader:最顶层的加载类,主要加载核心类库,%JRE_HOME%/lib下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动JVM时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。
  • Extention ClassLoader:扩展的类加载器,加载目录%JRE_HOME%/lib/ext目录下的jar包和class文件。还可以加载-Djava.ext.dirs选项指定的目录。
  • Appclass Loader:也称为SystemAppClass 加载当前应用的classpath的所有类

加载器特点

传递性

虚拟机的策略是使用调用者 Class 对象的 ClassLoader 来加载当前未知的类。所有延迟加载的类都会由初始调用 main 方法的这个 ClassLoader 全全负责,它就是 AppClassLoader。

双亲委派

每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个组合的关系),每个 ClassLoader 都很懒,尽量把工作交给父亲做,父亲干不了了自己才会干。每个 ClassLoader 对象内部都会有一个 parent 属性指向它的父加载器。

动态性

程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到JVM中,其它类等到JVM用到的时候再加载。而用到时再加载这也是java动态性的一种体现。

类与加载器

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。比较两个类是否”相等”,只有再这两个类是有同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

自定义加载器

Java中提供的默认ClassLoader,只加载指定目录下的jar和class,如果我们想加载其它位置的类或jar时,需要自定义加载器。

ClassLoader 里面有三个重要的方法,调用顺序为:loadClass -> findClass -> defineClass

  • loadClass():是加载目标类的入口,它首先会查找当前 ClassLoader 以及它的双亲里面是否已经加载了目标类,如果没有找到就会让双亲尝试加载
  • findClass():如果双亲都加载不了,就会调用 findClass() 让自定义加载器自己来加载目标类。ClassLoader 的 findClass() 方法是需要子类来覆盖的,不同的加载器将使用不同的逻辑来获取目标类的字节码。
  • defineClass():拿到这个字节码之后再调用 defineClass() 方法将字节码转换成 Class 对象。

定义自已的类加载器分为两步:

  • 继承java.lang.ClassLoader
  • 重写父类的findClass方法

Class加载过程

类从被加载到虚拟机内存中开始,直到卸载出内存为止,它的整个生命周期包括了:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析这三个部分统称为连接(linking)。

  • 顺序确定的:加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的
  • 顺序不确认的:解析阶段在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定。

类的实例化与类的初始化是两个完全不同的概念:

  • 类的实例化是指创建一个类的实例(对象)的过程
  • 类的初始化是指为类中各个类成员(被static修饰的成员变量)赋初始值的过程,是类生命周期中的一个阶段。

加载方式

JVM加载class文件的两种方法:

  • 隐式加载:程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到JVM中
  • 显式加载:通过Class.forname()、ClassLoader().loadClass()等方法显式加载需要的类,或者我们自己实现的 ClassLoader 的 findlass() 方法。

Class.forName vs ClassLoader.loadClass

这两个方法都可以用来加载目标类,它们之间有一个小小的区别,那就是 Class.forName() 方法可以获取原生类型的 Class,而 ClassLoader.loadClass() 则会报错。

Class<?> x = Class.forName("[I");

Thread.contextClassLoader

contextClassLoader是线程上下文类加载器,是从父线程那里继承过来的,用途如下:

  • 它可以做到跨线程共享类,只要它们共享同一个 contextClassLoader。父子线程之间会自动传递 contextClassLoader,所以共享起来将是自动化的。
  • 如果不同的线程使用不同的 contextClassLoader,那么不同的线程使用的类就可以隔离开来。

加载异常

在类加载过程与初始化过程中,会出现如下异常:

  • ClassNotFoundExecption:当 JVM 要加载指定文件的字节码到内存时,并没有找到这个文件对应的字节码,也就是这个文件并不存在。解决方法就是检查在当前的 classpath 目录下有没有指定的文件。
  • NoClassDefFoundError:可能的情况就是使用new关键字、属性引用某个类、继承了某个接口或者类,以及方法的某个参数中引用了某个类,这时就会触发JVM或者类加载器实例尝试加载类型的定义,但是该定义却没有找到,影响了执行路径。换句话说,在编译时这个类是能够被找到的,但是在执行时却没有找到。解决这个错误的方法就是确保每个类引用的类都在当前的classpath下面。
  • UnsatisfiedLinkError:通常是在 JVM 启动的时候,如果 JVM 中的某个 lib 删除了,就有可能报这个错误。
  • ExceptionInInitializerError:在初始化时出异常,如给静态成员赋值出错。
  • NoSuchMethodError:NoSuchMethodError代表这个类型确实存在,但是一个不正确的版本被加载了,出现使用的方法不存在。

Java9变化

Java9模块化之后,对ClassLoader有所改造,对应的ClassLoader加载各自对应的模块:

  • Bootstrap ClassLoader:加载lib/modules下基本的modules,如java.base ,jdk.net等20个modules
  • Extention ClassLoader:更名为Platform Classloader,加载lib/modules下其它的30个modules
  • Application classloader加载-cp,-mp指定的类,也会加载lib/modules下25个moduels

注:以上内容收集于互联网多篇文章,在此感谢原作者们。

原文  http://lanlingzi.cn/post/technical/2019/0928_java_base_4/
正文到此结束
Loading...