1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-08-14 05:21:42 +08:00

JVM->类加载过程中的准备环节勘误

This commit is contained in:
guide 2021-04-25 14:10:25 +08:00
parent 919d094c19
commit 63d0142e17
3 changed files with 45 additions and 42 deletions

View File

@ -131,7 +131,7 @@ Java 方法有两种返回方式:
Java 虚拟机所管理的内存中最大的一块Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。** Java 虚拟机所管理的内存中最大的一块Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。**
**Java世界中“几乎”所有的对象都在堆中分配但是随着JIT编译期的发展与逃逸分析技术逐渐成熟栈上分配、标量替换优化技术将会导致一些微妙的变化所有的对象都分配到堆上也渐渐变得不那么“绝对”了。从jdk 1.7开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。** Java世界中“几乎”所有的对象都在堆中分配但是随着JIT编译期的发展与逃逸分析技术逐渐成熟栈上分配、标量替换优化技术将会导致一些微妙的变化所有的对象都分配到堆上也渐渐变得不那么“绝对”了。从 JDK 1.7开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。
Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆Garbage Collected Heap**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为新生代和老年代再细致一点有Eden 空间、From Survivor、To Survivor 空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。** Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆Garbage Collected Heap**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为新生代和老年代再细致一点有Eden 空间、From Survivor、To Survivor 空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。**
@ -214,6 +214,10 @@ JDK 1.8 的时候方法区HotSpot 的永久代被彻底移除了JDK1
#### 2.5.3 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢? #### 2.5.3 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
下图来自《深入理解 Java 虚拟机》第 3 版 2.2.5
![](https://img-blog.csdnimg.cn/20210425134508117.png)
1. 整个永久代有一个 JVM 本身设置的固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。 1. 整个永久代有一个 JVM 本身设置的固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。
>当元空间溢出时会得到如下错误: `java.lang.OutOfMemoryError: MetaSpace` >当元空间溢出时会得到如下错误: `java.lang.OutOfMemoryError: MetaSpace`

View File

@ -126,8 +126,6 @@ JVM具有四种类型的*GC*实现:
-Xloggc:/path/to/gc.log -Xloggc:/path/to/gc.log
``` ```
## 推荐阅读 ## 推荐阅读
- [CMS GC 默认新生代是多大?](https://www.jianshu.com/p/832fc4d4cb53) - [CMS GC 默认新生代是多大?](https://www.jianshu.com/p/832fc4d4cb53)

View File

@ -16,7 +16,6 @@
一个类的完整生命周期如下: 一个类的完整生命周期如下:
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/类加载过程-完善.png) ![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/类加载过程-完善.png)
## 类加载过程 ## 类加载过程
@ -33,9 +32,9 @@ Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚
1. 通过全类名获取定义此类的二进制字节流 1. 通过全类名获取定义此类的二进制字节流
2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构 2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口 3. 在内存中生成一个代表该类的 `Class` 对象,作为方法区这些数据的访问入口
虚拟机规范上面这3点并不具体因此是非常灵活的。比如"通过全类名获取定义此类的二进制字节流" 并没有指明具体从哪里获取、怎样获取。比如:比较常见的就是从 ZIP 包中读取日后出现的JAR、EAR、WAR格式的基础、其他文件生成典型应用就是JSP等等。 虚拟机规范上面这 3 点并不具体,因此是非常灵活的。比如:"通过全类名获取定义此类的二进制字节流" 并没有指明具体从哪里获取、怎样获取。比如:比较常见的就是从 `ZIP` 包中读取(日后出现的 `JAR``EAR``WAR` 格式的基础)、其他文件生成(典型应用就是 `JSP`)等等。
**一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 `loadClass()` 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。** **一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 `loadClass()` 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。**
@ -51,10 +50,13 @@ Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚
**准备阶段是正式为类变量分配内存并设置类变量初始值的阶段**,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意: **准备阶段是正式为类变量分配内存并设置类变量初始值的阶段**,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
1. 这时候进行内存分配的仅包括类变量static而不包括实例变量实例变量会在对象实例化时随着对象一块分配在 Java 堆中。 1. 这时候进行内存分配的仅包括类变量( Class Variables ,即静态变量,被 `static` 关键字修饰的变量,只与类相关,因此被称为类变量),而不包括实例变量。实例变量会在对象实例化时随着对象一块分配在 Java 堆中。
2. 这里所设置的初始值"通常情况"下是数据类型默认的零值如0、0L、null、false等比如我们定义了`public static int value=111` ,那么 value 变量在准备阶段的初始值就是 0 而不是111初始化阶段才会赋值。特殊情况比如给 value 变量加上了 final 关键字`public static final int value=111` ,那么准备阶段 value 的值就被赋值为 111。 2. 从概念上讲,类变量所使用的内存都应当在 **方法区** 中进行分配。不过有一点需要注意的是JDK 7 之前HotSpot 使用永久代来实现方法区的时候,实现是完全符合这种逻辑概念的。 而在 JDK 7 及之后HotSpot 已经把原本放在永久代的字符串常量池、静态变量等移动到堆中,这个时候类变量则会随着 Class 对象一起存放在 Java 堆中。相关阅读:[《深入理解Java虚拟机第3版》勘误#75](https://github.com/fenixsoft/jvm_book/issues/75)
3. 这里所设置的初始值"通常情况"下是数据类型默认的零值(如 0、0L、null、false 等),比如我们定义了`public static int value=111` ,那么 value 变量在准备阶段的初始值就是 0 而不是 111初始化阶段才会赋值。特殊情况比如给 value 变量加上了 final 关键字`public static final int value=111` ,那么准备阶段 value 的值就被赋值为 111。
**基本数据类型的零值** (图片来自《深入理解 Java 虚拟机》第 3 版 7.33 )
**基本数据类型的零值:**
![基本数据类型的零值](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/基本数据类型的零值.png) ![基本数据类型的零值](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/基本数据类型的零值.png)
@ -76,7 +78,7 @@ Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚
对于初始化阶段,虚拟机严格规范了有且只有 5 种情况下,必须对类进行初始化(只有主动去使用类才会初始化类) 对于初始化阶段,虚拟机严格规范了有且只有 5 种情况下,必须对类进行初始化(只有主动去使用类才会初始化类)
1. 当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。 1. 当遇到 `new``getstatic``putstatic` `invokestatic` 4 条直接码指令时,比如 `new` 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
- 当 jvm 执行 new 指令时会初始化类。即当程序创建一个类的实例对象。 - 当 jvm 执行 new 指令时会初始化类。即当程序创建一个类的实例对象。
- 当 jvm 执行 getstatic 指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。 - 当 jvm 执行 getstatic 指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。
- 当 jvm 执行 putstatic 指令时会初始化类。即程序给类的静态变量赋值。 - 当 jvm 执行 putstatic 指令时会初始化类。即程序给类的静态变量赋值。
@ -102,7 +104,7 @@ Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚
所以,在 JVM 生命周期类,由 jvm 自带的类加载器加载的类是不会被卸载的。但是由我们自定义的类加载器加载的类是可能被卸载的。 所以,在 JVM 生命周期类,由 jvm 自带的类加载器加载的类是不会被卸载的。但是由我们自定义的类加载器加载的类是可能被卸载的。
只要想通一点就好了jdk自带的BootstrapClassLoader, ExtClassLoader, AppClassLoader负责加载jdk提供的类所以它们(类加载器的实例)肯定不会被回收。而我们自定义的类加载器的实例是可以被回收的,所以使用我们自定义加载器加载的类是可以被卸载掉的。 只要想通一点就好了jdk 自带的 `BootstrapClassLoader`, `ExtClassLoader`, `AppClassLoader` 负责加载 jdk 提供的类,所以它们(类加载器的实例)肯定不会被回收。而我们自定义的类加载器的实例是可以被回收的,所以使用我们自定义加载器加载的类是可以被卸载掉的。
**参考** **参考**
@ -119,4 +121,3 @@ Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚
**Java 工程师必备学习资源:** 一些 Java 工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。 **Java 工程师必备学习资源:** 一些 Java 工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。
![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png) ![我的公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png)