【实战JVM】-基础篇-04-自动垃圾回收
- 自动垃圾回收
- 1 多语言内存管理
- 1.1 C/C++的内存管理
- 1.2 Java的内存管理
- 1.3 自动与手动对比
- 1.4 应用场景
- 2 方法区的回收
- 2.1 回收条件
- 3 堆回收
- 3.1 判断是否被引用
- 3.1.1 引用计数法
- 3.1.2 可达性分析算法
- 3.1.2.1 GC Root
- 3.1.2.2 监视GC Root
- 3.1.2.3 总结
- 3.2 五种对象引用
- 3.2.1 软引用
- 3.2.1.1 软引用使用
- 3.2.1.2 软引用回收
- 3.2.1.3 软引用使用场景-缓存
- 3.2.2 弱引用
- 3.2.4 虚引用和终结器引用
- 3.3 垃圾回收算法
- 3.3.1 垃圾回收算法标准
- 3.3.2 标记清除算法
- 3.3.3 复制算法
- 3.3.4 标记整理算法
- 3.3.5 分代GC
- 3.3.5.1 分代垃圾回收算法
- 3.4 垃圾回收器
- 3.4.1 Serial-Serial Old
- 3.4.2 ParNew-CMS
- 3.4.3 Parallel Scavenge-Parallel Old
- 3.4.4 G1
- 3.4.4.1 内存结构
- 3.4.4.2 回收策略
- 3.4.4.3 使用G1
- 3.4.5 选择组合策略
自动垃圾回收
1 多语言内存管理
1.1 C/C++的内存管理
1.2 Java的内存管理
1.3 自动与手动对比
1.4 应用场景
2 方法区的回收
2.1 回收条件
3 堆回收
3.1 判断是否被引用
3.1.1 引用计数法
C++智能指针就采用引用计数法,但是存在缺点:
- 每次引用和取消引用都需要维护计数器,对系统性能会有一定的影响。
- 存在循环引用问题,所谓循环引用就是当A引用B,B同时引用A时会出现对象无法回收的问题。
查看垃圾回收信息,采用-verbose:gc
参数,但是java并没有采用引用计数法。使用的是可达性分析算法。
3.1.2 可达性分析算法
注:GC Root对象不可被回收
3.1.2.1 GC Root
-
线程Thread对象
包括主线程,子线程等等。
-
java.lang.Class对象
系统类加载器加载的
java.lang.*
中的Class对象,其中包括sun.misc.Launcher类,再由sun.misc.Launcher类加载扩展类和应用程序类加载器。
-
监视器对象
3.1.2.2 监视GC Root
public class ReferenceCounting {
private static A a2=null;
public static void main(String[] args) throws IOException {
A a1=new A();
B b1=new B();
a1.b=b1;
b1.a=a1;
a2=a1;
System.in.read();
}
}
class B {
public A a;
}
class A {
public B b;
}
启动arthas
java -Dfile.encoding=UTF-8 -jar arthas-boot.jar
进入ReferenceCounting,对内存进行快照
heapdump "D:\File\StudyJavaFile\JavaStudy\JVM\low\day03\resource\test2.hprof"
先把环境变量的jdk设为17再启动memoryanalyzer
打开刚刚的内存快照,canel取消引导
选择GC Root
可以看到成员变量实际在堆中存放的是直接引用
所有的局部变量和成员变量已经看过了,我们来找静态变量a2
private static A a2=null;
public static void main(String[] args) throws IOException {
A a1=new A();
B b1=new B();
a1.b=b1;
b1.a=a1;
a2=a1;
System.in.read();
}
静态变量通过应用程序加载器加载到堆中,A类应该还有个应用程序类的加载器的GCroot指向A,我们通过path to gcroot来找引用
我们可以看到有三个引用,亦可以看到应用程序加载器的父类都有哪些。
3.1.2.3 总结
3.2 五种对象引用
3.2.1 软引用
使用软引用必须创建两个对象:
- 软引用自身SoftReference对象
- 在软引用自身SoftReference对象中创建第二个对象A,才是程序真正使用的对象
3.2.1.1 软引用使用
public class SoftReferenceDemo2 {
public static void main(String[] args) throws IOException {
byte[] bytes = new byte[1024 * 1024 * 100];
SoftReference<byte[]> softReference = new SoftReference<byte[]>(bytes);
bytes = null;
System.out.println(softReference.get());
byte[] bytes2 = new byte[1024 * 1024 * 100];
System.out.println(softReference.get());
// byte[] bytes3 = new byte[1024 * 1024 * 100];
// softReference = null;
System.gc();
System.in.read();
}
}
设置最大堆内存-Xmx200m,启动,总共200M实际能用不到200M,自然第一个放进去第二个放不进去,然后软引用就被释放回收了,打印null
[B@7ba4f24f
null
设置最大堆内存-Xmx400m,都能发进去,并且指向同一个软引用空间
[B@7ba4f24f
[B@7ba4f24f
修改回200m,添加
byte[] bytes3 = new byte[1024 * 1024 * 100];
已经把软引用释放了,就不能添加了,自然报outofmemory
[B@7ba4f24f
null
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at chapter04.soft.SoftReferenceDemo2.main(SoftReferenceDemo2.java:20)
3.2.1.2 软引用回收
public class SoftReferenceDemo3 {
public static void main(String[] args) throws IOException {
ArrayList<SoftReference> softReferences = new ArrayList<>();
ReferenceQueue<byte[]> queues = new ReferenceQueue<byte[]>();
for (int i = 0; i < 10; i++) {
byte[] bytes = new byte[1024 * 1024 * 100];
SoftReference studentRef = new SoftReference<byte[]>(bytes,queues);
softReferences.add(studentRef);
}
SoftReference<byte[]> ref = null;
int count = 0;
while ((ref = (SoftReference<byte[]>) queues.poll()) != null) {
count++;
}
System.out.println(count);
}
}
设置堆内存200M,只够存一个,后一个盒子把前一个盒子覆盖了,并且把前一个盒子放进queue中。所以最后一个存在,没有被回收,因此输出9
3.2.1.3 软引用使用场景-缓存
通过在软引用对象中添加一个属性 _key
来方便后面的清理HashMap,如果内存不足,软引用自动清除,然后再根据引用队列中queue中存在的软引用对象,再通过软引用的属性 _key
来清理HashMap完成闭环的清理缓存的操作。
public class StudentCache {
private static StudentCache cache = new StudentCache();
public static void main(String[] args) {
for (int i = 0; ; i++) {
StudentCache.getInstance().cacheStudent(new Student(i, String.valueOf(i)));
}
}
private Map<Integer, StudentRef> StudentRefs;// 用于Cache内容的存储
private ReferenceQueue<Student> q;// 垃圾Reference的队列
// 继承SoftReference,使得每一个实例都具有可识别的标识。
// 并且该标识与其在HashMap内的key相同。
private class StudentRef extends SoftReference<Student> {
private Integer _key = null;
public StudentRef(Student em, ReferenceQueue<Student> q) {
super(em, q);
_key = em.getId();
}
}
// 构建一个缓存器实例
private StudentCache() {
StudentRefs = new HashMap<Integer, StudentRef>();
q = new ReferenceQueue<Student>();
}
// 取得缓存器实例
public static StudentCache getInstance() {
return cache;
}
// 以软引用的方式对一个Student对象的实例进行引用并保存该引用
private void cacheStudent(Student em) {
cleanCache();// 清除垃圾引用
StudentRef ref = new StudentRef(em, q);
StudentRefs.put(em.getId(), ref);
System.out.println(StudentRefs.size());
}
// 依据所指定的ID号,重新获取相应Student对象的实例
public Student getStudent(Integer id) {
Student em = null;
// 缓存中是否有该Student实例的软引用,如果有,从软引用中取得。
if (StudentRefs.containsKey(id)) {
StudentRef ref = StudentRefs.get(id);
em = ref.get();
}
// 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,
// 并保存对这个新建实例的软引用
if (em == null) {
em = new Student(id, String.valueOf(id));
System.out.println("Retrieve From StudentInfoCenter. ID=" + id);
this.cacheStudent(em);
}
return em;
}
// 清除那些所软引用的Student对象已经被回收的StudentRef对象
private void cleanCache() {
StudentRef ref = null;
while ((ref = (StudentRef) q.poll()) != null) {
StudentRefs.remove(ref._key);
}
}
// // 清除Cache内的全部内容
// public void clearCache() {
// cleanCache();
// StudentRefs.clear();
// //System.gc();
// //System.runFinalization();
// }
}
class Student {
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.2.2 弱引用
public class WeakReferenceDemo2 {
public static void main(String[] args) throws IOException {
byte[] bytes = new byte[1024 * 1024 * 100];
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes);
bytes = null;
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());
}
}
打印,不管有没有数据,全部回收
[B@7ba4f24f
null
3.2.4 虚引用和终结器引用
3.3 垃圾回收算法
释放不再存活对象的内存,使得程序能再次利用这部分空间。
3.3.1 垃圾回收算法标准
- 最大吞吐量
- 最大暂停时间
- 堆使用效率
3.3.2 标记清除算法
- 优点:实现简单,只需要在一阶段给每个对象维护一个标志位,第二阶段删除对象即可。
- 缺点:碎片化问题(外部碎片),分配速度慢(扫描空闲链表,速度慢)
3.3.3 复制算法
3.3.4 标记整理算法
3.3.5 分代GC
在jdk8中,添加-XX:+UserSerialGC
参数使用分代回收的垃圾回收器,运行程序
在arthas中使用memory
命令查看内存,显示三个区域内存情况。
public class GcDemo0 {
public static void main(String[] args) throws IOException {
List<Object> list = new ArrayList<>();
int count = 0;
while (true){
System.in.read();
System.out.println(++count);
//每次添加1m的数据
list.add(new byte[1024 * 1024 * 1]);
}
}
}
添加参数 -XX:+UseSerialGC -Xms60m -Xmn20m -Xmx60m -XX:SurvivorRatio=3 -XX:+PrintGCDetails
应当是总共60M,老年代40M,新生代20M,新生代中,伊甸园12M,S0,S1各4M,满足SurvivorRatio=3
的3:1:1
3.3.5.1 分代垃圾回收算法
在复制算法中,S0和S1会发生互换,第一次MinorGC全放到To中,然后To和From交换名字。
FullGC对新生代和老年代一起回收
3.4 垃圾回收器
3.4.1 Serial-Serial Old
新生代采用复制算法,老年代采用标记整理算法
3.4.2 ParNew-CMS
3.4.3 Parallel Scavenge-Parallel Old
3.4.4 G1
3.4.4.1 内存结构
3.4.4.2 回收策略
年轻代选择的复制算法