1. 对象重用

1.1. 原因

1.1.1. 许多对象的初始化成本很高,权衡了增加的GC时间之后,还是重用对象的效率更高

1.2. 只适用于初始化成本很高且数量较少的一组对象

1.2.1. 被重用的对象会在堆中停留很长时间。如果堆中有大量对象,创建新对象的空间就更少了,因此GC操作会更频繁。

1.3. 方式

1.3.1. 对象池

1.3.1.1. 线程池化可以节省创建线程的时间

1.3.1.2. 在池中有少量对象并不会对GC效率产生太大影响,但堆中充满池化对象时会大大减慢GC的速度

1.3.2. 线程局部变量

1.3.2.1. 随机数生成器作为线程局部变量,可以节省使用随机数种子创建生成器所需的时间

1.4. 特点

1.4.1. 初始化对象需要很长的时间

1.4.2. 共享的对象数量往往很少

1.4.2.1. 它们对GC操作的影响降到了最低,因为它们不足以减慢GC周期

1.5. 应用场景

1.5.1. 线程池

1.5.1.1. 线程初始化的成本很高

1.5.2. JDBC连接池

1.5.2.1. 数据库连接初始化的成本很高

1.5.3. 大数组

1.5.3.1. 每个元素都必须初始化为基于0的默认值(null、0或false,视情况而定)

1.5.4. 原生NIO缓冲区

1.5.4.1. 无论缓冲区有多大,分配一个直接的java.nio.Buffer(调用allocateDirect()方法返回的缓冲区)都是成本很高的操作

1.5.4.2. 最好是创建一个大的缓冲区,然后通过按需切片的方式来管理缓冲区,以便在将来的操作中返回它们重新使用

1.5.5. 与安全相关的类

1.5.5.1. MessageDigest、Signature以及其他安全算法的实例

1.5.6. 字符串编码器和解码器对象

1.5.7. StringBuilder帮助类

1.5.8. 随机数生成器

1.5.8.1. Random类和(特别是)SecureRandom类的实例

1.5.9. 从DNS查询中获得的名字

1.5.9.1. 网络查询的成本很高

1.5.10. ZIP编码器和解码器

1.5.10.1. 初始化成本并不是特别高,但是它们的释放成本很高

1.5.10.2. 依赖对象终结(finalization)来确保它们使用的原生内存也被释放了

2. 对象池

2.1. 大小可能很难设置正确

2.2. 不能直接让对象退出作用域,程序员必须将对象返还到池中

2.3. 持有大量对象会降低GC的效率(有时会大幅降低)

2.4. 必然需要同步,如果对象被频繁地删除和替换,对象池可能会存在大量竞争

2.5. 普通类的大型对象池带来的性能问题肯定会比解决的问题还多

2.6. 限流对池性能的影响是有利的,池允许对稀缺资源的访问进行限流

3. 线程局部变量

3.1. 在线程内总是可用的,不需要显式地归还

3.2. 不能对资源的访问进行限流

3.2.1. 除非线程数量本身可以起到限流的作用

3.3. 不需要同步

3.3.1. 只能在单个线程内使用

3.3.2. 使用ThreadLocalRandom类性能会更好

3.4. 如果线程和可重用对象之间有一一对应的关系,那么线程局部变量更容易使用

4. 不确定引用

4.1. indefinite reference

4.2. 表示任何特殊类型的引用

4.2.1. 软引用或弱引用

4.2.2. 实际上是一个对象实例

4.3. 更多地用于缓存耗时的计算或数据库查询的结果,而不是用于重用一个简单对象

4.4. 对垃圾回收器的影响

4.4.1. 会导致应用程序使用更多的内存

4.4.2. 不确定引用被垃圾回收器回收至少需要两个GC周期,这对垃圾回收器的影响更大

4.4.2.1. 在最坏的情况下,引用队列不会被立即清理,而是需要经过很多GC周期后才会释放一切对象

4.5. -XX:+PrintReferenceGC标志

4.5.1. 看到处理引用所花费的时间

4.5.2. 默认为false

4.6. 不确定引用会消耗自身的内存,而且会长时间持有其他对象的内存,应尽量少用

5. 引用

5.1. reference

5.2. 一个引用(或对象引用)可以是任何类型的引用

5.3. 强引用、弱引用和软引用等

6. 所引对象

6.1. referent

6.2. 将另一个引用(几乎总是强引用)嵌入到不确定引用类的实例中。被封装的这个对象被称为所引对象

7. 软引用

7.1. Soft Reference

7.2. 当相关对象在未来有很大概率被重用,而你想让垃圾回收器回收最近没有用到的对象时使用

7.2.1. 软引用可以长期持有对象,它会提供一个简单的、GC友好的LRU缓存

7.3. 本质上是一个大型的、最近最少使用的(LRU)对象池

7.4. 获得良好性能的关键是要确保它被及时清理

7.5. 清理时机

7.5.1. 所引对象必须没有被其他的强引用所引用

7.5.2. 软引用是指向所引对象的唯一引用,那么当软引用最近没有被访问时,所引对象就会在下个GC周期被释放

7.6. -XX:SoftRefLRUPolicyMSPerMB=N标志

7.6.1. 默认值是1000

7.7. 长期运行的应用程序也可以考虑增加SoftRefLRUPolicyMSPerMB的值的条件

7.7.1. 堆中有大量空闲空间可用

7.7.2. 软引用的访问频率不高

7.8. 不要使用过多的软引用,它们很容易填满整个堆

7.9. 当对象的数量不太多时,软引用的效果才会好,否则,还是要考虑使用更传统的、大小有界的对象池,并且以LRU缓存形式实现

8. 弱引用

8.1. weak reference

8.2. 在相关所引对象会被几个线程同时使用时使用

8.3. 当强引用被清理时,弱引用立即被清理

8.4. 垃圾回收器在每个GC周期都会回收只有弱引用的对象

8.5. 集合类常常是内存泄漏的源头

8.5.1. WeakHashMap和WeakIdentityMap

8.5.2. 基于不确定引用的集合可能很有用,但是应该谨慎使用

9. 终结器和最终引用

9.1. 不鼓励使用终结器,应该使用新的Cleaner类

9.2. Object类的finalize()方法

9.2.1. 糟糕的方法,你应该尽量避免使用

9.2.2. 在JDK 11中被废弃了

9.3. Cleaner(清理器)对象

9.3.1. 使用新的java.lang.ref.Cleaner类代替finalize()方法会容易很多

9.3.2. 在JDK 11中使用

9.3.3. 执行清理的对象不能包含指向需要被清理的对象的引用

9.3.3.1. 因为lambda太容易引用外部类了,不鼓励开发人员使用lambda,而是要使用类

9.4. 如果你必须使用终结器,请确保将对象访问的内存保持在最低限度

9.5. 使用另一种类型的不确定引用,而不是隐式地使用Finalizer引用

9.5.1. PhantomReference类(虚引用)

9.5.1.1. JDK 11使用

9.6. 终结器队列

9.6.1. 当所引对象符合GC条件时,用来处理Finalizer引用的引用队列

9.6.2. 命令来处理终结器队列

9.6.2.1. % jcmd process_id GC.run_finalization

9.6.2.2. % jmap -finalizerinfo process_id

10. 普通对象指针

10.1. ordinary object pointer,oop

10.2. -XX:+UseCompressedOops

10.2.1. 对于4 GB到32 GB的堆,应该使用

10.2.2. 只要堆的最大大小小于32GB,标志默认启用

10.3. 使用了31 GB的堆并启用了压缩的oop的程序,通常比使用了33 GB堆的程序快

10.4. 最好使用小于32 GB的堆,或者至少比32 GB大几个GB的堆

10.5. 一旦更多的内存被添加到堆中以弥补未压缩的引用所使用的空间,GC周期的数量就会减少

10.6. 规划至少38 GB的堆是一个好的开始