java虚拟机原理簡述,《Java 虚拟机原理》7.3 精选 —— GC 基础篇

1.GC 是什么?GC 的作用是什么?

背景:

程序员在内存处理方面出现问题,例如忘记、错误回收内存,导致程序或系统的不稳定甚至崩溃。

GC (GabageCollection)是指垃圾回收。Java 提供 GC 功能,可以自动监控内存区域是否超过作用域,实现自动回收内存的目的。

说明:

Java 语言没有提供显式方法来分配和释放内存。

2.简述 Java 垃圾回收机制及其优点

Java 垃圾回收机制实现了自动回收内存的功能。JVM 有垃圾回收线程(守护线程),其优先级较低,在正常情况下是不会执行的。当 JVM 空闲、heap 内存、Metaspace 内存不足时,JVM 会执行垃圾回收线程。

Java 垃圾回收的步骤:

① 判断对象 A 是否存活。采用引用计数法或可达性分析,例如后者从 GC Roots 开始搜索,当没有任何的 GC Roots 与对象 A 相连时,则对象 A 是不可达对象,可以判定为可回收对象。(GC Roots 是堆外指向堆内的引用)

② 触发 GC 的时机。Minor GC 发生在 Heap 新生代,例如 Eden、FromSuv、ToSuv 满了都会触发 Minor GC。Full GC 发生在 Heap 老年代和 Metaspace,例如调用System.gc、Heap 老年代空间不足、Metaspace 空间不足等。

③ 进行 GC。GC 算法是内存回收的理论方法,GC 垃圾收集器则是具体实现。GC 算法有标记清除法、复制算法、标记压缩算法。

JVM 是采用分代垃圾回收机制,本文通过某对象的完整 GC 过程,理解 Java 垃圾回收机制,可参考《Java 虚拟机原理》5.1 GC垃圾收集及案例分析 — GC案例分析

Java 垃圾回收机制的优点

① 不需要考虑内存管理;

② 有效地防止内存泄漏;

③ 有效地利用内存;

④ Java 中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。

Java 垃圾回收机制的缺点

① 垃圾回收机制的目标是在 JVM Heap 和 Metaspace 内存中,回收无用对象的内存空间,因此无法回收数据库连接、Socket、I/O 等物理资源;

② 垃圾回收机制具有不可预知性,通过 System.gc()、对象引用设置为 null 等手段促进垃圾回收,但不能精确控制垃圾回收机制的运行;

③ 垃圾回收机制的潜在缺点是它的开销会影响性能;

3.如何判断一个对象是否存活?

(1)判断对象存活的方法

引用计数法

每个对象都有一个引用的计数值,当该对象新增一个引用时,该计数值加 1;反之,当该对象减少一个引用时,该计数值减 1。如果计数值为 0,则回收该对象。引用计数法的缺点是不能处理对象的相互循环引用

35adf44e60e9

image.png

可达性分析

从 GC Roots 开始搜索,当没有任何的 GC Roots 与对象 A 相连时,则对象 A 是不可达对象,可以判定为可回收对象。(GC Roots 是堆外指向堆内的引用)

35adf44e60e9

image.png

GC Roots:

由堆外指向堆内的引用,一般而言,GC Roots 包括(但不限于)下列几种,Java 方法栈桢中的局部变量、已加载类的静态变量、JNI handles、已启动且未停止的 Java 线程等。因此,GC Roots 是 GC 对象的引用。

(2)强引用、软引用、弱引用、虚引用的区别

强引用 StrongReference

例如,new 出来的对象,JVM 即使出现 OutOfMemory 错误,也不会垃圾回收该对象。

Object obj = new Object();

软引用 SoftReference

非必须引用,内存溢出之前回收。

Object obj = new Object(); //强引用

ReferenceQueue referenceQueuee = new ReferenceQueue<>(); //引用队列

// 软引用和引用队列联合使用,如果软引用所引用的对象被垃圾回收器回收,JVM就会把这个软引用加入到引用队列中

SoftReference softReference = new SoftReference(str, referenceQueuee);

注意:一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,同时维护系统的运行安全,防止 OutOfMemory 等问题。

弱引用 WeakReference

第二次垃圾回收时回收。

@Test

public void testWeakReference() throws InterruptedException {

ReferenceQueue referenceQueuee = new ReferenceQueue<>();

Object weakObject = new Object();

//弱引用

WeakReference weakReference = new WeakReference(weakObject, referenceQueuee);

System.out.println("WeakReference:" + weakReference.get());

System.out.println("referenceQueuee:" + referenceQueuee.poll());

weakObject = null;

System.gc();

Thread.sleep(2000);

System.out.println("WeakReference:" + weakReference.get());

System.out.println("referenceQueuee:" + referenceQueuee.poll());

}

// 测试结果

WeakReference:java.lang.Object@694f9431

referenceQueuee:null

WeakReference:null

referenceQueuee:java.lang.ref.WeakReference@f2a0b8e

虚引用 PhantomReference

虚引用是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

虚引用主要用来跟踪对象被垃圾回收的活动。

4.简述 Java 中的内存泄漏

内存泄露是指一个不再被程序使用的对象或变量一直占据内存。

在 Java 垃圾回收机制中,如果对象不再被引用(GC Roots 的可达性分析),则垃圾收集器会从内存中清除该对象。

(1)长生命周期的对象持有短生命周期对象的引用

由于短生命周期对象已经不再需要,而长生命周期对象持有它的引用而导致不能被回收。通俗地说,程序创建了一个对象,之后一直不再该对象,但这个对象却一直被引用,导致无法被垃圾收集器回收。

① static 字段引起的内存泄露

在 Java 中,静态字段与整个程序的生命周期一致。如果静态对象是不断创建、增大,可能导致 OutOfMemory。

静态集合类:HashMap、LinkedList 等,容器内的对象在程序结束前都不能被回收。

单例模式:如果单例对象 A 持有外部对象 B 的引用,那么对象 B 将不能被 JVM 正常回收,导致内存泄露。

② 引用了外部类的内部类

一个外部类实例对象 B 的方法返回了一个内部类 B.Inner 的实例对象。如果 B.Inner 被长期引用,即使对象 B 不再被使用。因为 B.Inner 持有对象 B 的引用,所以对象 B 将不会被垃圾回收,导致内存泄露。解决方法:采用静态内部类。

为什么非静态内部类持有外部类引用,静态内部类不持有外部引用?

static 调用 static,非 static 调用非 static。即 static --> 针对 class, 非static -> 针对 对象。

(2)长生命周期的对象本身不释放

数据库连接、网络连接和 IO 连接等

(3)改变哈希值

不正确地重写 equals() 和 hashCode(),在 HashMap、HashSet 等种集合中,常常用到 equal() 和 hashCode() 来比较对象,如果重写不合理,将会成为潜在的内存泄露问题。

排除内存泄露的样例

public class Stack {

private Object[] elements;

private int size = 0;

private static final int DEFAULT_INITIAL_CAPACITY = 16;

public Stack() {

elements = new Object[DEFAULT_INITIAL_CAPACITY];

}

public void push(Object e) {

ensureCapacity();

elements[size++] = e;

}

public Object pop() {

if (size == 0)

throw new EmptyStackException();

return elements[--size];

}

private void ensureCapacity() {

if (elements.length == size)

elements = Arrays.copyOf(elements, 2 * size + 1);

}

}

解答:当进行大量的 pop 操作时,引用未置空使得 GC 是不会回收数组的对象。

35adf44e60e9

image.png

解决方法:

public Object pop() {

if (size == 0)

throw new EmptyStackException();

Object result = elements[--size];

elements[size] = null;

return result;

}

35adf44e60e9

image.png

5.简述 JVM 的一次完整 GC 流程

(1)Java 对象的创建及初始化

35adf44e60e9

image.png

① 类加载检查。JVM 遇到 new 指令,首先去检查该类是否在常量池中有符合引用,并且检查该类是否被加载、解析和初始化。如果没有,则执行类加载流程。

② 内存分配。在类加载检查后,可知对象所需要的内存大小。JVM 采用“指针碰撞”或者“空闲列表”,为对象进行内存分配。

35adf44e60e9

image.png

③ 初始化零值。JVM 为分配到的内存空间都初始化为零值(不包括对象头),保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,例如,Object 对象的零值为 null,int 的零值为 0 等。

④ 设置对象头。JVM 为对象设置对象头,包括该对象是哪个类的实例、对象的哈希码、对象的 GC 分代年龄等。

⑤ 执行 init 方法。init 方法即程序中对象的构造函数显示的内容。

(2)GC 过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值