「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」。
1. System.gc()
System.gc()内部调用了 Runtime.getRuntiom
public static void gc() {
Runtime.getRuntime().gc();
}
在往深一层则是本地方法了
public native void gc();
\
System.gc()会执行FullGC,对新生代和老年代进行回收
注意: 此时垃圾回收线程可能并不会立即执行
证明:
package com.zy.study14;
/**
* @Author: Zy
* @Date: 2021/12/29 11:13
* 测试system.gc()方法
*/
public class SystemGcTest {
public static void main(String[] args) {
new SystemGcTest();
// 告知垃圾回收器执行垃圾回收,当并不一定立刻执行
// 证明如下: 如果执行了就会执行finalize方法,否则程序结束,垃圾回收线程也不会执行
System.gc();
// 立刻执行对象的finalize方法
//System.runFinalization();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("执行了finalize方法");
}
}
\
System.gc对对象的回收情况:
package com.zy.study14;
/**
* @Author: Zy
* @Date: 2021/12/29 11:30
* 测试垃圾回收对象
* -XX:+PrintGCDetails 打印gc细节
*/
public class SystemGcObjectTest {
public void test1(){
// 栈中有引用不会回收
byte[] buffer = new byte[10*1024*1024];
System.gc();
}
public void test2(){
// 将引用置空,此时可以回收
byte[] buffer = new byte[10*1024*1024];
buffer = null;
System.gc();
}
public void test3(){
// 代码块中引用并没有被覆盖,局部变量表中深度为2,第一个变量为this,第二个仍然为buffer,有该引用,所以不会被回收
{
byte[] buffer = new byte[10*1024*1024];
}
System.gc();
}
public void test4(){
// 代码块中buffer引用被被覆盖,局部变量表中深度为2,第一个变量为this,第二个buffer被覆盖为value,引用置空,所以被回收
{
byte[] buffer = new byte[10*1024*1024];
}
int value = 10;
System.gc();
}
public void test5(){
// 引用置空 会被回收
test1();
System.gc();
}
public static void main(String[] args) {
SystemGcObjectTest systemGcObjectTest = new SystemGcObjectTest();
systemGcObjectTest.test1();
}
}
通过对打印的gc信息可以观察到是否符合该方法中描述的回收情况,同时可以结合jclasslib查看局部变量表引用信息.
方法1,2,5都是比较简单的
比较复杂的就是3和4
针对3来说:
局部变量表的深度为2,但是变量表里只显示this对象,索引为1的仍然是代码块中的buffer引用.所以不会回收.
2. 内存溢出和内存泄漏问题
\
2.1 内存溢出
内存空间不足,且垃圾回收器也无法提供更多的内存,就会出现内存溢出情况
内存溢出多出现于堆空间,很少出现于虚拟机栈,但是栈也会出现Strack Overflow
正常情况下,各年龄代的垃圾回收正常回收对象已经足够,当内存空间实在不足时,触发一次FullGC,回收大量内存,保证程序正常,但是当内存占用的速度非常快的时候,超过垃圾回收的速度的时候就会出现OOM
2.1.1 内存空间不足
内存空间不足可以分为两种情况
- 堆内存初始化的内存较小 -Xms -Xmx分配 / 存在内存泄漏问题
- 代码中出现了大对象一直被引用的情况
-
- 例如方法区中的回收,当出现了大量动态类型生成的场合时,就可能导致永久代(jdk7)/元空间(jdk8) OOM
2.1.2 垃圾回收器无法提供更多的内存
当内存空间不足时,会先执行一次垃圾回收,清理空间
但是也并不是每次都会触发垃圾回收,特殊情况: 当要分配的对象大小超过了jvm堆内存的最大空间时,就会直接OOM,不再垃圾回收.
2.2 内存泄漏
内存泄漏的内存指的是jvm中的虚拟内存,并非物理内存.
2.2.1解释
- 对象已经不再使用,但是无法回收
- 对象的生命周期过长,导致无法回收
2.2.2后果
内存泄漏一旦发生,随着时间的推移,jvm可用内存会越来越少,最终可能导致jvm没有足够的内存从而程序崩溃的结果
\
3. STW(Stop The World)问题
stw指的是gc过程中,程序的所有线程全部暂停,等待gc完成的情况.
stw是不可避免的,在jvm垃圾回收的可达性算法中,我们必须要先分析所有可达对象,而jvm的GCRoots对象是在不停的变化中的,为了前后一致性,必须要停止其他所有线程,让GC线程进行操作.
STW是所有垃圾回收器都存在的事件,包括目前jvm使用G1以及未来的ZGC都无法避免,只能不断优化垃圾回收时间, 减少停顿时间,提高用户体验.
开发中尽量少用System.gc() 这个是触发FullGC的,FullGC就会进行STW
4. 垃圾回收中的并行和并发
4.1 并行与并发的基本概念
并行: 同一个时间点,多个cpu同时处理
并发: 一个时间段内,一个/多个cpu同时处理
并行只存在于多个cpu或单个cpu多核心的情况下.
并行是不会抢占资源的,因为多核分别处理,不会互相干涉
并发是会抢占资源的,同一个处理器下同一个时间点只能执行一个线程.
4.2 垃圾回收中的并行和并发
垃圾回收也是通过线程来执行的,垃圾回收的线程也分为并行和并发
4.2.1 并行和串行
并行和串行针对的是垃圾回收线程之前可能是并行/串行
只有一个垃圾线程执行时,我们称之为串行
多个垃圾线程同时执行时,我们称之为并行
常用的垃圾回收线程有: ParNew/Parallel Scavenge/Parallel Old
4.2.2 并发
并发指的是垃圾回收线程和程序线程之间是并发的
垃圾回收线程和程序线程可能是并行的,也可能是并发的,但是不可能一直并行,因为存在不可避免的STW事件,最多交替执行
当垃圾回收线程和程序线程运行于不同的cpu上时,就是并行的,但这个过程并不能一直持续下去,当发生STW时,必然要停止程序线程,让垃圾回收线程运行.因此只能说在垃圾回收的一段时间内,垃圾回收线程和程序线程之间是并发的
5. 安全点和安全区域
\
5.1 安全点(SafePoint)
程序运行时,并不是在任意位置都可以停下来进行GC,只能在特定的位置才能停下来进行GC,这些特定的位置就是安全点
安全点的设置要恰到好处,太多会出现频繁gc,影响程序的性能,太少可能导致stw的时间过长甚至长时间无法gc会导致oom
安全点的设置位置也尽量选择执行时间较长的位置,比如方法调用,循环等位置,如果选择了执行时间较短的位置作为安全点,可能存在性能问题.
5.1.1 安全点的使用
当发生gc的时候,线程进入安全点的方式:
- 抢先式中断: 当gc发生时,就将所有线程都暂停,然后检查所有线程,是否有未到安全点的线程,如果有,恢复该线程,直到该线程到达安全点为止. (较为复杂,目前没有jvm再采用此种方式)
- 主动中断: 设置一个是否中断标志,每个线程运行到安全点的时候查询此标志,是否为true,为true则进行自己中断且挂起,当gc时,设置该中断标志即可.
\
\
5.2 安全区域(SafeRegion)
安全区域可以看作是安全点的扩充,将一块代码内,对象引用关系不会变化的区域称之为安全区域,在安全区域内,gc是不受影响的.
当线程发生时,如果某个程序线程处理seelp状态时,是无法相应gc线程,将线程的代码执行到具体的安全点再进行gc的,这个时候就需要安全区域来保证休眠线程不会影响到gc.
执行过程:
- 当线程进入到安全区域的时候,线程会进行标识,标识本线程已经进入安全区域了,这个时候如果发生gc,那么jvm就会忽略进入安全区域的线程(安全区域内对象关系不再变化)
- 当线程要离开安全区域的时候,需要检查gc是否完成,如果完成,那么就直接往下执行,如果没有完成,那么需要停止,等待gc完成后方可离开安全区域,这样是为了保证gc前后的一致性.
6. java中的引用(Reference)
引用是java中堆和栈的桥梁,想要访问堆中的对象,就必须通过引用来访问(8个基本数据类型除外)
在垃圾回收中,如果一个对象仍然被GcRoots引用,那么就不会被回收(强引用),这也不是绝对的,主要是根据引用类型来决定的
在jvm中也有对于的抽象类 Reference
package java.lang.ref;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.access.JavaLangRefAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.ref.Cleaner;
/**
* Abstract base class for reference objects. This class defines the
* operations common to all reference objects. Because reference objects are
* implemented in close cooperation with the garbage collector, this class may
* not be subclassed directly.
*
* @author Mark Reinhold
* @since 1.2
*/
public abstract class Reference<T> {
...
}
并且软/弱/虚引用分别都有对于的实现类
6.1 引用分类
引用类型主要分为四类,四种引用在垃圾回收时表现不同
- 强引用 不回收
- 软引用 内存不足时回收
- 弱引用 发现即回收
- 虚引用 对象跟踪回收
由强到虚,回收级别递增.
6.1.1 强引用(StrongReference)
开发过程中的用的基本都是强引用
String str = new String("hello world");
这种最常见的创建对象的方式就是强引用.
这种引用jvm是不会进行回收的,只有当引用被置为null的时候,jvm才会进行回收.
例子证明:
public class Test1 {
public static void main(String[] args) {
String str = new String("hello world");
// 垃圾回收
System.gc();
// 线程休眠3s,等待gc完成
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 如果能打印,就说明没被回收
System.out.print(str);
}
}
一般情况下,出现内存泄漏的问题都是因为强引用.
6.1.2 软引用(SoftReference)
软引用是用来做一些非必要但是还有用的对象. 例如缓存
当内存不足时,软引用会被回收.
具体的逻辑为:
- 内存空间不足,进行垃圾回收,回收不可达对象
- 不可达对象回收后,内存空间依然不足,进行软引用的回收
- 如果软引用回收后,内存空间依然不足,报错OOM,如果内存空间足够,则不报OOM
软引用在jdk中有对应的实现类
package java.lang.ref;
/**
* Soft reference objects, which are cleared at the discretion of the garbage
* collector in response to memory demand. Soft references are most often used
* to implement memory-sensitive caches.
*
* <p> Suppose that the garbage collector determines at a certain point in time
* that an object is <a href="package-summary.html#reachability">softly
* reachable</a>. At that time it may choose to clear atomically all soft
* references to that object and all soft references to any other
* softly-reachable objects from which that object is reachable through a chain
* of strong references. At the same time or at some later time it will
* enqueue those newly-cleared soft references that are registered with
* reference queues.
*
* <p> All soft references to softly-reachable objects are guaranteed to have
* been cleared before the virtual machine throws an
* {@code OutOfMemoryError}. Otherwise no constraints are placed upon the
* time at which a soft reference will be cleared or the order in which a set
* of such references to different objects will be cleared. Virtual machine
* implementations are, however, encouraged to bias against clearing
* recently-created or recently-used soft references.
*
* <p> Direct instances of this class may be used to implement simple caches;
* this class or derived subclasses may also be used in larger data structures
* to implement more sophisticated caches. As long as the referent of a soft
* reference is strongly reachable, that is, is actually in use, the soft
* reference will not be cleared. Thus a sophisticated cache can, for example,
* prevent its most recently used entries from being discarded by keeping
* strong referents to those entries, leaving the remaining entries to be
* discarded at the discretion of the garbage collector.
*
* @author Mark Reinhold
* @since 1.2
*/
public class SoftReference<T> extends Reference<T> {
...
}
我们用一个例子证明软引用在内存不足时会被回收:
import java.lang.ref.SoftReference;
public class SoftReferenceTest {
public static void main(String[] args) {
// 创建一个软引用 hello_world 关联了一个强引用对象String,当然这个对象创建完就不可达了,会被回收掉,
// 此时我们还能否从软引用中获取该对象?
SoftReference hello_world = new SoftReference(new String("hello world"));
// 确定可以通过软引用获取String
System.out.println(hello_world.get().toString());
// 接下来设置堆内存大小为 -Xms10M -Xmx10M -XX:+PrintGCDetails
// 并触发垃圾回收
// 进行异常捕获,最后输出软引用
try {
byte[] bytes = new byte[1024 * 1024 * 10];
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("垃圾回收后");
System.out.println(hello_world.get());
}
}
}
可以看到结果
垃圾回收前后:
此时可以证明: 当内存不足时,会将软引用回收.
注意: 软引用回收指的是,只被软引用关联的对象,如果一个对象既有弱引用,又有强引用,那么是不会被回收的.
6.1.3 弱引用(WeakReference)
弱引用的回收比软引用要快,每次gc的时候都会回收,当然这里的回收也指的是只有弱引用的对象.
这意味着弱引用的生命周期只有一次垃圾回收的长度.
弱引用也有对应的实现类:
package java.lang.ref;
/**
* Weak reference objects, which do not prevent their referents from being
* made finalizable, finalized, and then reclaimed. Weak references are most
* often used to implement canonicalizing mappings.
*
* <p> Suppose that the garbage collector determines at a certain point in time
* that an object is <a href="package-summary.html#reachability">weakly
* reachable</a>. At that time it will atomically clear all weak references to
* that object and all weak references to any other weakly-reachable objects
* from which that object is reachable through a chain of strong and soft
* references. At the same time it will declare all of the formerly
* weakly-reachable objects to be finalizable. At the same time or at some
* later time it will enqueue those newly-cleared weak references that are
* registered with reference queues.
*
* @author Mark Reinhold
* @since 1.2
*/
public class WeakReference<T> extends Reference<T> {
...
}
弱引用的回收证明例子:
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;
public class WeakReferenceTest {
public static void main(String[] args) {
WeakReference weakReference =
new WeakReference<>(new String("hello world"));
System.out.println(weakReference.get());
System.gc();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("垃圾回收后");
System.out.println(weakReference.get());
}
}
结果证明:
\
6.1.4 虚引用(PhantomReference)
虚引用相比于软/弱引用来说回收的级别更高,也无法根据虚引用来获取对应的对象
对对象来说,有虚引用和没有虚引用是一样的,对对象的生命周期没有任何影响.
虚引用唯一的作用就是来作为对象回收的跟踪,当对象被回收的时候可以通知程序该对象被回收了,所以虚引用的创建必须要指定一个虚引用队列.
\
虚引用也有对应的实现类
package java.lang.ref;
import jdk.internal.vm.annotation.IntrinsicCandidate;
/**
* Phantom reference objects, which are enqueued after the collector
* determines that their referents may otherwise be reclaimed. Phantom
* references are most often used to schedule post-mortem cleanup actions.
*
* <p> Suppose the garbage collector determines at a certain point in time
* that an object is <a href="package-summary.html#reachability">
* phantom reachable</a>. At that time it will atomically clear
* all phantom references to that object and all phantom references to
* any other phantom-reachable objects from which that object is reachable.
* At the same time or at some later time it will enqueue those newly-cleared
* phantom references that are registered with reference queues.
*
* <p> In order to ensure that a reclaimable object remains so, the referent of
* a phantom reference may not be retrieved: The {@code get} method of a
* phantom reference always returns {@code null}.
* The {@link #refersTo(Object) refersTo} method can be used to test
* whether some object is the referent of a phantom reference.
*
* @author Mark Reinhold
* @since 1.2
*/
public class PhantomReference<T> extends Reference<T> {
...
}
\
可以通过代码验证虚引用的对象回收通知功能:
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
public class PhantomReferenceTest {
static ReferenceQueue referenceQueue = null;
/**
* 守护线程,监听queue队列,当有虚引用被回收时,就可以看到输出哪个对象被回收了
*/
public static class CheckGcThread extends Thread {
@Override
public void run() {
if (referenceQueue != null) {
Reference remove = null;
try {
remove = referenceQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (remove != null) {
System.out.println(remove+"对象被回收了");
}
}
}
}
public static void main(String[] args) {
// 启动队列监听线程 设置为守护线程,当主线程结束的时候随之结束
CheckGcThread checkGcThread = new CheckGcThread();
checkGcThread.setDaemon(true);
checkGcThread.start();
// 虚引用的创建必须要传入一个队列
referenceQueue = new ReferenceQueue();
PhantomReference hello_world = new PhantomReference(new String("hello" +
" world"), referenceQueue);
// 无法通过虚引用获取对象的值
System.out.println(hello_world.get());
// 垃圾回收
System.gc();
}
}
可以看到结果:
6.2 扩展
终结器引用: 用于调用对象的finalize方法,也是借助于队列的方式,一般情况用不到
对应实现类:
package java.lang.ref;
/**
* Final references, used to implement finalization
*/
class FinalReference<T> extends Reference<T> {
...
}