第VI章 - 「深い理解のJava仮想マシンの」研究は5ノート

第VI章クラスファイルの構造

1、独立の礎

  • さまざまな仮想マシンおよびすべてのプラットフォームが異なるプラットフォームを使用するには、プログラム格納形式を統一している - バイトコードのビルディングブロックは、プラットフォームに依存しないです。
  • 言語の独立性を達成するための基礎は、まだ仮想マシンのストレージとバイトコードフォーマットであるJava仮想マシンとJavaバインディングを含む任意の言語でのみ、この特定のクラスファイルのバイナリファイル形式に関連付けることができません。
  • クラスファイルは、Java仮想マシンの命令セットとシンボルテーブルだけでなく、他のサポート情報の数が含まれています。

2、クラスファイルの構造

概要:

  • クラス・ファイルは、オクテットベースのバイナリストリームの集合であり、各データ項目は、コンパクトクラスファイルの正確な順序で配置されています。
  • クラスファイル形式は、このダミーデータ構造の構成2つのデータテーブルと符号なしのタイプを含むと同様のデータを格納する擬似C言語のデータ構造を使用します。
  • 符号なしの8バイトの符号なしの1バイト、2バイト、4つのバイトを、表現するためにU1、U2、U4、U8から、基本データ型に属します。符号なしの数は、UTF-8文字列値の符号に従って設定数、インデックス値または参照番号7を記述するために使用することができます。
  • 複数のテーブルは、データ項目のテーブル構成として、符号なしまたは他の複合データ型です。すべてのテーブルは、習慣的に「_info」で終わるされています。記述データは、全クラスファイルの階層関係の複合構造は、本質的に表で表します。

マジック数:

  • 各クラスファイルのヘッダマジックナンバーは魔法の数字のクラスファイルが0xCAFEBABEで、役割はファイルがクラスファイルを受け入れるように参照することができる仮想マシンであるかどうかを決定することで、4です。

ファイルのクラスバージョン:

  • マジックナンバーが記憶されている4バイトに続く第五及び第六のバイトはマイナーバージョン番号、7及び8は、メジャーバージョン番号であり、クラスファイルのバージョン番号です。

定数プール:

  • メジャーバージョン番号が続く定数プールエントリであり、定数プールは倉庫クラスリソースファイルとして理解することができ、それは他のプロジェクトに関連付けられたクラスファイルデータ構造の最大の種類であるが、最大クラス・ファイル・スペース・データ・アイテムのいずれかを占めそれは、データテーブルクラスファイルの種類である一方、最初に表示されます。
  • 定数プールの一定の値が固定されていない起因する、データの種類に配置するように定数プールエントリを先頭から数えて、U2容量定数プールカウント値を表します。
  • そのようなテキスト文字列などのJava言語レベルの概念に比較的近い二つの主な定数、リテラルとシンボリック参照、リテラル定数を格納する定数プールは、定数プールfinalとして宣言しました。
  • そして、参照シンボル名記述子は、クラスやインタフェース、名前と記述子フィールドとメソッドの完全修飾名を含んでいます。
  • 各プロジェクトフラグの種類に応じて決まる定数プールテーブル内の定数、です。
    コードをコピー
    パッケージcom.ecut.clazz。パブリッククラスTestClassを{ プライベートint型メートル。公共INT INC(){ 戻りM + 1 }}
    
    
    コードをコピー

    進数に対応するバイトコード・ファイルは、以下:

    22の一定の容量値は、0x0Aを一定のフラグは以下の表によれば、21の定数の数を表し、対応するアイテムタイプCONSTANT_Methodref_info、参照の方法のクラスのこのタイプのシンボルを表す、構造の種類に対応する項目2つの索引があり、最初のインデックスは、18進である第2のインデックスのために0x0004は、0x0012に小数すなわち4です。第2の定数は、第1のインデックスは小数点3 0x0003、19進である第2の指標のために0x0013、すなわちプロジェクトCONSTANT_Fieldref_infoのこのタイプ、です。
  • 残りの定数は、ファイル出力TestClass.class内容をバイトコードにてjavapクラスファイルのバイトコードツールを使用することができます。-verbose TestClass.classコマンドを使用してjavap。

アクセスフラグ:

  • 2つのバイトが続く定数プール、終了後にアクセスフラグを表す。このフラグは、それは抽象的であるかどうか、パブリックであるかどうかをクラスまたはインタフェース型を含むクラスまたはインタフェースレベルのアクセス情報の一部を識別するために使用されます。フラグと次の表のフラグの特定の意味:
  • 16ビットフラグの合計access_flagのみ電流8を画定し、使用することができる、フラグ0を使用していないが、常に必要とされます。
  • そして、JDK1.2以降に変更公衆TestClassをコンパイラを使用してコンパイルされて、それは100001 = 0x21でのaccess_flagフラグの値でなければなりません。

クラスインデックス、親インデックス、インタフェースインデックス:

  • このクラスの完全修飾名を決定するためのクラスインデックス式は、このクラスの親クラスの完全修飾名を決定するための親クラスのインデックスは、インデックスセットは、クラスへのインタフェースを記述するために使用される左で声明を実装し、これらのインターフェースを実装右ミニチュアセットに配置されたインターフェース。

テーブルのセットフィールド:

  • クラスまたはインタフェース内で宣言された変数を記述するために使用されます。フィールドは、クラスのインスタンス変数とクラス変数レベルを含むが、メソッド内で宣言されたローカル変数を含んでいません。
  • フィールドコレクションは、スーパークラスまたは親クラスインターフェースからフィールドから継承されたリストにないが、それは本来、このようなクラス内でアクセスできるようになり、外側のクラスを維持するために、クラスの外にインスタンスフィールドを追加するなどのフィールドが、リストされていないかもしれ。
  • フィールドには情報が含まれています。スコープ(保護された、プライベート、パブリック)フィールド、クラス変数やインスタンス変数(static修飾子)、変動(決勝)、同時性(volatile修飾子、メインメモリから読み出さ強制するかどうか)、 (transient修飾子)シリアライズすることができ、フィールドのデータ型(基本型、オブジェクト、配列)、フィールド名。
  • 2 name_indexとdescript_indexが含まaccess_flagプロジェクトを、配置フィールド修飾子は、彼らは、単純なフィールド名とフィールド記述子を表す、定数プールへの参照です。
  • 以下に示すように、フィールドテーブルの構造:
  • 定数プールに結合定数前に、クラスインデックスCOM / ECUT / clazz / TestClassを、親インデックスのjava / LANG /オブジェクトを理解していました。TestClassをMを含むint型は、修飾プライベートあります。                                                 

メソッド表コレクション:

  • この方法は、フラグテーブルのセット、インデックス名、記述子インデックス、一連の属性にアクセスすることを含みます。親クラスのメソッドは、サブクラスメソッドでオーバーライドされていない場合は、親クラスメソッドテーブルセットからの情報のためノー方法。
  • 以下に示すようにメソッドテーブル構造:

属性のテーブルセット:

  • 在class文件中,字段表方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
  • 属性集合的结构如下图:
  • 结合常量池对文件进行分析可得,方法容量为2包含了两个方法,第一个方法时public修饰void init,该方法包含了一个属性code,code操作数栈最大值为1.第二个方法时public修饰的int inc()该方法也包含了一个属性code。

  • Exception属性列举出方法中可能抛出的异常,也就是方法描述时在throws关键字后面列出的异常。
  • LineNumberTable属性用于描述Java源码行号和字节码行号之间的对应关系。
  • LocalVariableTable属性用来描述栈帧中局部变量与Java源码中定义的变量之间的关系。
  • SourceFile属性用来记录生成这个class源码文件名称。

3、字节码指令

概述:

  • Java虚拟机的指令由一个字节长度的,代表着某中特定操作含义的数字(称为操作码)以及跟随其后的零至多个代表此操作所需参数(称为操作数)而构成。
  • Java虚拟机采用面向操作数栈的架构所以大多数指令都不包含操作数只有一个操作码。
  • 在Java虚拟机的指令集中大多数指令都包含了其操作锁对应的数据类型信息。

加载和存储指令:

  • 加载存储指令用来将数据在栈帧中的局部变量和操作数栈之间来回传输。
  • 将一个局部变量加载到操作数栈iload。
  • 将一个数值从操作数栈中存储到局部变量表istore。
  • 将一个常量加载到操作数栈bipush。
  • 扩充局部变量表的访问索引的指令wide。

运算或算法指令:

  • 运算或算法指令用于对两个操作数栈上的值进行某中特定的运算,并把结果重新存入操作数栈的栈顶。

类型转换指令:

 

  • 类型转换指令可以将两种不同的数值类型进行相互转换,这种转换操作一般用于实现用户代码中的显示类型转换操作。
  • int——》long——》float——》double。

对象创建和访问指令:

  • 虽然类实例和数组都是对象但是使用不同的指令来完成创建。
  • 创建类实例的指令 new。
  • 创建数组的指令 newarray。
  • 访问类字段和实例字段 putfield 、getfield、getstatic。

操作数栈管理指令:

  • 将操作数栈的栈顶一个元素出栈pop。
  • 将栈顶两个数值交换swap。

控制转移指令:

  • 在有条件或者无条件的修改PC寄存器的值。
  • 条件分支ifea、iflt。
  • 复合条件分支lookupswich。
  • 无条件goto 。

方法调用和返回指令:

  • 方法调用指令和返回值无关,而方法返回指令是根据返回值类型来区分的。
  • invokevirtual用于调用对象的实例方法。
  • invokestatic用于调用类方法。

异常处理指令:

  • 在Java程序中显示抛出异常的操作都有athrow指令来完成。
  • Java虚拟规范还规定许多运行时异常会在其他指令检测到异常时自动抛出。

同步指令:

  • Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor)来支持的。

推荐博客链接:

https://blog.csdn.net/u010349169/column/info/jvm-principle

转载请于明显处标明出处:

https://www.cnblogs.com/AmyZheng/p/10537013.html

第六章 类文件结构

1、无关性的基石

  • 各种不同平台的虚拟机与所有平台都统一使用程序存储格式——字节码是构成平台无关的基石。
  • 实现语言无关性的基础仍然是虚拟机和字节码存储格式,Java虚拟机不和包括Java在内的任何语言绑定只能与class文件这种特定的二进制文件格式所关联。
  • class文件包含了Java虚拟机指令集和符号表以及若干其他辅助信息。

2、class文件结构

概述:

  • class文件是一组以八位字节为基础的二进制流,各个数据项目严格按照顺序紧凑的排列在class文件中。
  • class文件格式采用一种类似于C语言结构的伪数据结构来存储数据,这种伪数据结构包含两种数据类型无符号和表。
  • 无符号数属于基本数据类型,从u1、u2、u4、u8来分别表示一个字节、两个字节、四个字节、八个字节的无符号数。无符号数可以用来描述数字、索引、引用7数量值或者按照utf-8编码构成字符串值。
  • 表是由多个无符号或者其他表作为数据项构成的复合数据类型。所有的表都习惯性地以“_info”结尾。表示描述有层次关系的复合结构的数据,整个class文件本质就是一张表。

魔数:

  • 每个class文件的头四位为魔数,作用是确定这个文件是否是一个能被虚拟机引用接受的class文件,class文件的魔数值是0xCAFEBABE。

class文件的版本:

  • 紧接着魔数的四个字节存储的是class文件的版本号,第5和第6个字节是次版本号,第7和第8个是主版本号。

常量池:

  • 紧接着主版本号的是常量池入口,常量池可以理解为class文件中的资源仓库,它是class文件结构中与其他项目关联的最多的数据类型,也是占有class文件空间最大的数据项目之一,同时它是class文件第一个出现的表类型数据。
  • 由于常量池的常量数值不固定,所以在常量池的入口需要放置一项u2类型的数据代表常量池容量计数值,计数是从一开始的。
  • 常量池主要存放两大常量,字面量和符号引用,字面量比较接近于Java语言层面的常量概念,如文字字符串,声明为final的常量池等。
  • 符号引用包括类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。
  • 常量池中每一项常量都是一个表,根据标志位来确定项目类型。
    コードをコピー
    package com.ecut.clazz;
    
    public class TestClass {
        private int m; public int inc() { return m + 1; } }
    コードをコピー

    字节码文件对应的16进制如下图:

    常量容量计数值为22则代表有21个常量,第一个常量的标志位是0x0A,根据下表可知对应的项目类型是CONSTANT_Methodref_info,此类型代表类中方法的符号引用,这个项目类型对应的结构有两个index,第一个index为0x0004为即十进制4,第二个index为0x0012即十进制18。第二个常量是以此类推,这个项目类型为CONSTANT_Fieldref_info,第一个index为0x0003即十进制3,第二个index为0x0013即十进制19。
  • 剩下的常量可以借助class文件字节码的工具javap来输出TestClass.class文件字节码内容。采用javap -verbose TestClass.class命令。

访问标志:

  • 在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于标识一些类或者接口层次的访问信息,包括是类还是接口,是否为public类型,是否为abstract。具体的标志位以及标志的含义如下表:
  • access_flag中一共有16个标志位可以使用,当前只定义了其中8个,没有使用到的标志位要求一律为0。
  • TestClass被public修饰并且使用了jdk1.2之后的编译器进行了编译,因此它的access_flag标志值应该为100001=0x21。

类索引、父类索引、接口索引:

  • 类索引用于确定表达这个类的全限定名,父类索引用于确定这个类的父类的全限定名,接口索引集合就用于描述这个类实现了那些接口,这些接口按implements语句从左到右排列在接口缩影集合中。

字段表集合:

  • 用于描述接口或类中声明的变量。字段包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。
  • 字段集合不会列出从超类中或者父类接口中继承而来的字段,但有可能列出原本没有的字段,如在内部类中为了保持外部类的访问性会加入外部类的实例字段。
  • 字段包含信息:字段的作用域(public、private、protected),是实例变量还是类变量(static修饰符),可变性(final)、并发性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符),字段数据类型(基本类型、对象、数组),字段名称。
  • 字段修饰符放在access_flag项目中,它包含两项name_index和descript_index,他们都是对常量池的引用,分别代表字段的简单名称和字段的描述符。
  • 字段表结构如下图所示:
  • 结合之前的常量池中的常量可知类索引为 com/ecut/clazz/TestClass,父类索引为 java/lang/Object。TestClass包含一个被private修饰的int 类型的m。                                                 

方法表集合:

  • 方法表集合包括访问标志、名称索引、描述符索引、属性集合。如果父类方法在子类方法中没有被重写,方法表集合中不会有来自父类的方法信息。
  • 方法表结构如下图:

属性表集合:

  • 在class文件中,字段表方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
  • 属性集合的结构如下图:
  • 结合常量池对文件进行分析可得,方法容量为2包含了两个方法,第一个方法时public修饰void init,该方法包含了一个属性code,code操作数栈最大值为1.第二个方法时public修饰的int inc()该方法也包含了一个属性code。

  • Exception属性列举出方法中可能抛出的异常,也就是方法描述时在throws关键字后面列出的异常。
  • LineNumberTable属性用于描述Java源码行号和字节码行号之间的对应关系。
  • LocalVariableTable属性用来描述栈帧中局部变量与Java源码中定义的变量之间的关系。
  • SourceFile属性用来记录生成这个class源码文件名称。

3、字节码指令

概述:

  • Java虚拟机的指令由一个字节长度的,代表着某中特定操作含义的数字(称为操作码)以及跟随其后的零至多个代表此操作所需参数(称为操作数)而构成。
  • Java虚拟机采用面向操作数栈的架构所以大多数指令都不包含操作数只有一个操作码。
  • 在Java虚拟机的指令集中大多数指令都包含了其操作锁对应的数据类型信息。

加载和存储指令:

  • 加载存储指令用来将数据在栈帧中的局部变量和操作数栈之间来回传输。
  • 将一个局部变量加载到操作数栈iload。
  • 将一个数值从操作数栈中存储到局部变量表istore。
  • 将一个常量加载到操作数栈bipush。
  • 扩充局部变量表的访问索引的指令wide。

运算或算法指令:

  • 运算或算法指令用于对两个操作数栈上的值进行某中特定的运算,并把结果重新存入操作数栈的栈顶。

类型转换指令:

 

  • 类型转换指令可以将两种不同的数值类型进行相互转换,这种转换操作一般用于实现用户代码中的显示类型转换操作。
  • int——》long——》float——》double。

对象创建和访问指令:

  • 虽然类实例和数组都是对象但是使用不同的指令来完成创建。
  • 创建类实例的指令 new。
  • 创建数组的指令 newarray。
  • 访问类字段和实例字段 putfield 、getfield、getstatic。

操作数栈管理指令:

  • 将操作数栈的栈顶一个元素出栈pop。
  • 上の2つのスタック値は、スワップを交換します。

コントロール転送命令:

  • 条件付きまたは無条件の値は、PCレジスタを変更しました。
  • 条件分岐ifea、IFLT。
  • 複合条件分岐lookupswich。
  • 無条件後藤。

メソッドの呼び出しと復帰命令:

  • リターン命令の戻り型に係るメソッド呼び出し命令に関係なくメソッドの戻り値は区別されます。
  • 例INVOKEVIRTUALオブジェクトのメソッドを呼び出します。
  • invokestaticは、クラスのメソッドを呼び出すために使用されます。

例外処理命令:

  • Javaプログラムで投げ操作が完了するまでにathrow命令を持って示しています。
  • 異常が他の命令実行の数で検出されたときに自動的に例外がスローされるため、Java仮想仕様も用意されています。

同期命令:

  • Java仮想マシンは、命令レベルのシーケンスの期間に内部同期方法及び同期方法をサポートすることができ、両方の構造物を支持体にチューブ(モニタ)を使用して同期されます。

推奨のブログへのリンク:

https://blog.csdn.net/u010349169/column/info/jvm-principle

再現目立つ場所に、ソースを明記してください

https://www.cnblogs.com/AmyZheng/p/10537013.html

おすすめ

転載: www.cnblogs.com/manmanchanglu/p/11621822.html