Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 55 additions & 43 deletions docs/java/jvm/类文件结构.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。

<!-- TOC -->
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->

<!-- code_chunk_output -->

- [类文件结构](#类文件结构)
- [一 概述](#一-概述)
- [二 Class 文件结构总结](#二-class-文件结构总结)
- [2.1 魔数](#21-魔数)
- [2.2 Class 文件版本](#22-class-文件版本)
- [2.3 常量池](#23-常量池)
- [2.4 访问标志](#24-访问标志)
- [2.5 当前类索引,父类索引与接口索引集合](#25-当前类索引父类索引与接口索引集合)
- [2.6 字段表集合](#26-字段表集合)
- [2.7 方法表集合](#27-方法表集合)
- [2.8 属性表集合](#28-属性表集合)
- [参考](#参考)

<!-- /TOC -->
- [一 概述](#一-概述)
- [二 Class 文件结构总结](#二-class-文件结构总结)
- [2.1 魔数(Magic Number)](#21-魔数magic-number)
- [2.2 Class 文件版本号(Minor&Major Version)](#22-class-文件版本号minormajor-version)
- [2.3 常量池(Constant Pool)](#23-常量池constant-pool)
- [2.4 访问标志(Access Flags)](#24-访问标志access-flags)
- [2.5 当前类(This Class)、父类(Super Class)、接口(Interfaces)索引集合](#25-当前类this-class-父类super-class-接口interfaces索引集合)
- [2.6 字段表集合(Fields)](#26-字段表集合fields)
- [2.7 方法表集合(Methods)](#27-方法表集合methods)
- [2.8 属性表集合(Attributes)](#28-属性表集合attributes)
- [参考](#参考)

<!-- /code_chunk_output -->


# 类文件结构

Expand All @@ -27,11 +29,13 @@ Clojure(Lisp 语言的一种方言)、Groovy、Scala 等语言都是运行

![java虚拟机](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/bg/desktop类文件结构概览.png)

**可以说`.class`文件是不同的语言在 Java 虚拟机之间的重要桥梁,同时也是支持 Java 跨平台很重要的一个原因。**
可以说`.class`文件是不同的语言在 Java 虚拟机之间的重要桥梁,同时也是支持 Java 跨平台很重要的一个原因。

## 二 Class 文件结构总结

根据 Java 虚拟机规范,类文件由单个 ClassFile 结构组成:
根据 Java 虚拟机规范,Class 文件通过 `ClassFile` 定义,有点类似 C 语言的结构体。

`ClassFile` 的结构如下:

```java
ClassFile {
Expand All @@ -54,53 +58,61 @@ ClassFile {
}
```

下面详细介绍一下 Class 文件结构涉及到的一些组件
通过分析 `ClassFile` 的内容,我们便可以知道 class 文件的组成

**Class文件字节码结构组织示意图** (之前在网上保存的,非常不错,原出处不明):
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/16d5ec47609818fc.jpeg)

![类文件字节码结构组织示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/类文件字节码结构组织示意图.png)
下面这张图是通过 IDEA 插件 `jclasslib` 查看的,你可以更直观看到 Class 文件结构。

### 2.1 魔数
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/image-20210401170711475.png)

使用 `jclasslib` 不光可以直观地查看某个类对应的字节码文件,还可以查看类的基本信息、常量池、接口、属性、函数等信息。

下面详细介绍一下 Class 文件结构涉及到的一些组件。

### 2.1 魔数(Magic Number)

```java
u4 magic; //Class 文件的标志
```

每个 Class 文件的头四个字节称为魔数(Magic Number),它的唯一作用是**确定这个文件是否为一个能被虚拟机接收的 Class 文件**。
每个 Class 文件的头 4 个字节称为魔数(Magic Number),它的唯一作用是**确定这个文件是否为一个能被虚拟机接收的 Class 文件**。

程序设计者很多时候都喜欢用一些特殊的数字表示固定的文件类型或者其它特殊的含义。

### 2.2 Class 文件版本
### 2.2 Class 文件版本号(Minor&Major Version)

```java
u2 minor_version;//Class 的小版本号
u2 major_version;//Class 的大版本号
```

紧接着魔数的四个字节存储的是 Class 文件的版本号:第五和第六是**次版本号**,第七和第八是**主版本号**。
紧接着魔数的四个字节存储的是 Class 文件的版本号:第 5 和第 6 位是**次版本号**,第 7 和第 8 位是**主版本号**。

每当 Java 发布大版本(比如 Java 8,Java9)的时候,主版本号都会加 1。你可以使用 `javap -v` 命令来快速查看 Class 文件的版本号信息。

高版本的 Java 虚拟机可以执行低版本编译器生成的 Class 文件,但是低版本的 Java 虚拟机不能执行高版本编译器生成的 Class 文件。所以,我们在实际开发的时候要确保开发的的 JDK 版本和生产环境的 JDK 版本保持一致。

### 2.3 常量池
### 2.3 常量池(Constant Pool)

```java
u2 constant_pool_count;//常量池的数量
cp_info constant_pool[constant_pool_count-1];//常量池
```

紧接着主次版本号之后的是常量池,常量池的数量是 constant_pool_count-1(**常量池计数器是从1开始计数的,将第0项常量空出来是有特殊考虑的,索引值为0代表“不引用任何一个常量池项”**)。
紧接着主次版本号之后的是常量池,常量池的数量是 `constant_pool_count-1`(**常量池计数器是从 1 开始计数的,将第 0 项常量空出来是有特殊考虑的,索引值为 0 代表“不引用任何一个常量池项”**)。

常量池主要存放两大常量:字面量和符号引用。字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。而符号引用则属于编译原理方面的概念。包括下面三类常量:
常量池主要存放两大常量:字面量和符号引用。字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。而符号引用则属于编译原理方面的概念。包括下面三类常量:

- 类和接口的全限定名
- 字段的名称和描述符
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符

常量池中每一项常量都是一个表,这14种表有一个共同的特点:**开始的第一位是一个 u1 类型的标志位 -tag 来标识常量的类型,代表当前这个常量属于哪种常量类型.**
常量池中每一项常量都是一个表,这 14 种表有一个共同的特点:**开始的第一位是一个 u1 类型的标志位 -tag 来标识常量的类型,代表当前这个常量属于哪种常量类型.**

| 类型 | 标志(tag) | 描述 |
| :------------------------------: | :---------: | :--------------------: |
| CONSTANT_utf8_info | 1 | UTF-8编码的字符串 |
| CONSTANT_utf8_info | 1 | UTF-8 编码的字符串 |
| CONSTANT_Integer_info | 3 | 整形字面量 |
| CONSTANT_Float_info | 4 | 浮点型字面量 |
| CONSTANT_Long_info | 5 | 长整型字面量 |
Expand All @@ -115,11 +127,11 @@ ClassFile {
| CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
| CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |

`.class` 文件可以通过`javap -v class类名` 指令来看一下其常量池中的信息(`javap -v class类名-> temp.txt` :将结果输出到 temp.txt 文件)。
`.class` 文件可以通过`javap -v class类名` 指令来看一下其常量池中的信息(`javap -v class类名-> temp.txt` :将结果输出到 temp.txt 文件)。

### 2.4 访问标志
### 2.4 访问标志(Access Flags)

在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口,是否为 public 或者 abstract 类型,如果是类的话是否声明为 final 等等。
在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口,是否为 `public` 或者 `abstract` 类型,如果是类的话是否声明为 `final` 等等。

类访问和属性修饰符:

Expand All @@ -138,7 +150,7 @@ public class Employee {

![查看类的访问标志](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/查看类的访问标志.png)

### 2.5 当前类索引,父类索引与接口索引集合
### 2.5 当前类(This Class)、父类(Super Class)、接口(Interfaces)索引集合

```java
u2 this_class;//当前类
Expand All @@ -147,11 +159,11 @@ public class Employee {
u2 interfaces[interfaces_count];//一个类可以实现多个接口
```

**类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 `java.lang.Object` 之外,所有的 java 类都有父类,因此除了 `java.lang.Object` 外,所有 Java 类的父类索引都不为 0。**
类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 `java.lang.Object` 之外,所有的 java 类都有父类,因此除了 `java.lang.Object` 外,所有 Java 类的父类索引都不为 0。

**接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按 `implements` (如果这个类本身是接口的话则是`extends`) 后的接口顺序从左到右排列在接口索引集合中。**
接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按 `implements` (如果这个类本身是接口的话则是`extends`) 后的接口顺序从左到右排列在接口索引集合中。

### 2.6 字段表集合
### 2.6 字段表集合(Fields)

```java
u2 fields_count;//Class 文件的字段的个数
Expand All @@ -164,7 +176,7 @@ public class Employee {

![字段表的结构 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/字段表的结构.png)

- **access_flags:** 字段的作用域(`public` ,`private`,`protected`修饰符),是实例变量还是类变量(`static`修饰符),可否被序列化(transient 修饰符),可变性(final),可见性(volatile 修饰符,是否强制从主内存读写)。
- **access_flags:** 字段的作用域(`public` ,`private`,`protected`修饰符),是实例变量还是类变量(`static`修饰符),可否被序列化(transient 修饰符),可变性(final),可见性(volatile 修饰符,是否强制从主内存读写)。
- **name_index:** 对常量池的引用,表示的字段的名称;
- **descriptor_index:** 对常量池的引用,表示字段和方法的描述符;
- **attributes_count:** 一个字段还会拥有一些额外的属性,attributes_count 存放属性的个数;
Expand All @@ -176,7 +188,7 @@ public class Employee {

![字段的 access_flag 的取值](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/JVM/image-20201031084342859.png)

### 2.7 方法表集合
### 2.7 方法表集合(Methods)

```java
u2 methods_count;//Class 文件的方法的数量
Expand All @@ -185,7 +197,7 @@ public class Employee {

methods_count 表示方法的数量,而 method_info 表示方法表。

Class 文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。
Class 文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。

**method_info(方法表的) 结构:**

Expand All @@ -197,7 +209,7 @@ Class 文件存储格式中对方法的描述与对字段的描述几乎采用

注意:因为`volatile`修饰符和`transient`修饰符不可以修饰方法,所以方法表的访问标志中没有这两个对应的标志,但是增加了`synchronized`、`native`、`abstract`等关键字修饰方法,所以也就多了这些关键字对应的标志。

### 2.8 属性表集合
### 2.8 属性表集合(Attributes)

```java
u2 attributes_count;//此类的属性表中的属性数
Expand All @@ -211,4 +223,4 @@ Class 文件存储格式中对方法的描述与对字段的描述几乎采用
- <https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html>
- <https://coolshell.cn/articles/9229.html>
- <https://blog.csdn.net/luanlouis/article/details/39960815>
- 《实战 Java 虚拟机》
- 《实战 Java 虚拟机》
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ public class SynchronizedDemo {

> 在 Java 虚拟机(HotSpot)中,Monitor 是基于 C++实现的,由[ObjectMonitor](https://github.com/openjdk-mirror/jdk7u-hotspot/blob/50bdefc3afe944ca74c3093e7448d6b889cd20d1/src/share/vm/runtime/objectMonitor.cpp)实现的。每个对象中都内置了一个 `ObjectMonitor`对象。
>
> 另外,**`wait/notify`等方法也依赖于`monitor`对象,这就是为什么只有在同步的块或者方法中才能调用`wait/notify`等方法,否则会抛出`java.lang.IllegalMonitorStateException`的异常的原因。**
> 另外,`wait/notify`等方法也依赖于`monitor`对象,这就是为什么只有在同步的块或者方法中才能调用`wait/notify`等方法,否则会抛出`java.lang.IllegalMonitorStateException`的异常的原因。

在执行`monitorenter`时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。

Expand Down
2 changes: 1 addition & 1 deletion docs/system-design/distributed-system/rpc/Dubbo.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ public abstract class AbstractLoadBalance implements LoadBalance {

`AbstractLoadBalance` 的实现类有下面这些:

![](/Users/guide/Library/Application Support/typora-user-images/image-20210326105257812.png)
![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/image-20210326105257812.png)

官方文档对负载均衡这部分的介绍非常详细,推荐小伙伴们看看,地址:[https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance](https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance ) 。

Expand Down