@@ -66,7 +66,7 @@ java -Xss512M HackTheJava
66
66
67
67
所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。
68
68
69
- 现代的垃圾收集器基本都是采用分代收集算法,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法, 可以将堆分成两块:
69
+ 现代的垃圾收集器基本都是采用分代收集算法,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法。 可以将堆分成两块:
70
70
71
71
- 新生代(Young Generation)
72
72
- 老年代(Old Generation)
@@ -87,21 +87,19 @@ java -Xms1M -Xmx2M HackTheJava
87
87
88
88
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。
89
89
90
- HotSpot 虚拟机把它当成永久代来进行垃圾回收。但是很难确定永久代的大小 ,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
90
+ HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永久代的大小 ,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
91
91
92
92
## 运行时常量池
93
93
94
94
运行时常量池是方法区的一部分。
95
95
96
- Class 文件中的常量池(编译器生成的各种字面量和符号引用 )会在类加载后被放入这个区域。
96
+ Class 文件中的常量池(编译器生成的字面量和符号引用 )会在类加载后被放入这个区域。
97
97
98
98
除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。
99
99
100
100
## 直接内存
101
101
102
- 在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存(Native 堆),然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。
103
-
104
- 这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
102
+ 在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。
105
103
106
104
# 二、垃圾收集
107
105
@@ -113,11 +111,9 @@ Class 文件中的常量池(编译器生成的各种字面量和符号引用
113
111
114
112
### 1. 引用计数算法
115
113
116
- 给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。
117
-
118
- 两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。
114
+ 为对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。
119
115
120
- 正因为循环引用的存在,因此 Java 虚拟机不使用引用计数算法。
116
+ 两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。 正因为循环引用的存在,因此 Java 虚拟机不使用引用计数算法。
121
117
122
118
``` java
123
119
public class ReferenceCountingGC {
@@ -152,19 +148,17 @@ Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC
152
148
153
149
主要是对常量池的回收和对类的卸载。
154
150
155
- 在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGi 这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载功能,以保证不会出现内存溢出 。
151
+ 为了避免内存溢出, 在大量使用反射、动态代理的场景都需要虚拟机具备类卸载功能 。
156
152
157
- 类的卸载条件很多,需要满足以下三个条件,并且满足了也不一定会被卸载 :
153
+ 类的卸载条件很多,需要满足以下三个条件,但是满足了也不一定会被卸载 :
158
154
159
- - 该类所有的实例都已经被回收,也就是堆中不存在该类的任何实例 。
155
+ - 该类所有的实例都已经被回收,此时堆中不存在该类的任何实例 。
160
156
- 加载该类的 ClassLoader 已经被回收。
161
157
- 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。
162
158
163
- 可以通过 -Xnoclassgc 参数来控制是否对类进行卸载。
164
-
165
159
### 4. finalize()
166
160
167
- finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作 。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂 ,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
161
+ finalize() 类似 C++ 的析构函数,用于关闭外部资源 。但是 try-finally 等方式可以做的更好,并且该方法运行代价很高 ,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
168
162
169
163
当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会调用 finalize() 方法。
170
164
@@ -210,9 +204,9 @@ obj = null;
210
204
211
205
### 4. 虚引用
212
206
213
- 又称为幽灵引用或者幻影引用。 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象 。
207
+ 又称为幽灵引用或者幻影引用, 一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用得到一个对象 。
214
208
215
- 为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知 。
209
+ 为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到一个系统通知 。
216
210
217
211
使用 PhantomReference 来实现虚引用。
218
212
@@ -249,9 +243,9 @@ obj = null;
249
243
250
244
主要不足是只使用了内存的一半。
251
245
252
- 现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将新生代划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和使用过的那一块 Survivor。
246
+ 现在的商业虚拟机都采用这种收集算法回收新生代,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象全部复制到另一块 Survivor 空间上,最后清理 Eden 和使用过的那一块 Survivor。
253
247
254
- HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保 ,也就是借用老年代的空间存储放不下的对象。
248
+ HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行空间分配担保 ,也就是借用老年代的空间存储放不下的对象。
255
249
256
250
### 4. 分代收集
257
251
@@ -281,23 +275,23 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
281
275
282
276
它的优点是简单高效,对于单个 CPU 环境来说,由于没有线程交互的开销,因此拥有最高的单线程收集效率。
283
277
284
- 它是 Client 模式下的默认新生代收集器,因为在该应用场景下,分配给虚拟机管理的内存一般来说不会很大 。Serial 收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。
278
+ 它是 Client 模式下的默认新生代收集器,因为在该应用场景下内存一般来说不会很大 。Serial 收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。
285
279
286
280
### 2. ParNew 收集器
287
281
288
282
<div align =" center " > <img src =" pics/81538cd5-1bcf-4e31-86e5-e198df1e013b.jpg " width =" " /> </div ><br >
289
283
290
284
它是 Serial 收集器的多线程版本。
291
285
292
- 是 Server 模式下的虚拟机首选新生代收集器 ,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作。
286
+ 是 Server 模式下首选的新生代收集器 ,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作。
293
287
294
288
默认开启的线程数量与 CPU 数量相同,可以使用 -XX: ParallelGCThreads 参数来设置线程数。
295
289
296
290
### 3. Parallel Scavenge 收集器
297
291
298
292
与 ParNew 一样是多线程收集器。
299
293
300
- 其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间 ,而它的目标是达到一个可控制的吞吐量,它被称为 “吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户代码的时间占总时间的比值 。
294
+ 其它收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间 ,而它的目标是达到一个可控制的吞吐量,因此它被称为 “吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户程序的时间占总时间的比值 。
301
295
302
296
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。
303
297
0 commit comments