【Java引用规范】强软引用

简介: 本文详细介绍了Java中引用的概念和作用,包括强引用、软引用、弱引用和虚引用,并探讨了不同引用类型在内存管理和垃圾回收中的特性与用途。强引用是最常见的引用类型,对象只要被引用就不会被垃圾回收;软引用适用于内存敏感的缓存场景,在内存不足时会被回收;弱引用在更早的垃圾回收阶段被清除;虚引用主要用于对象的finalize过程。文章通过示例代码和内存分析工具展示了软引用的具体应用和回收机制。

概念和作用

引用是Java中对对象进行操作的主要方式,通过引用,可以在程序中创建、访问和操作对象。

java

代码解读

复制代码

Object obj = new Object();

这里,obj就是是一个引用,它指向一个刚创建的Object对象。

在Java中,有着几种不同的引用类型:

  1. 强引用
  2. 软引用
  3. 弱引用
  4. 虚引用

每种引用类型在内存管理和垃圾回收方面有不同的特性和用途。

引用在Java内存管理中的重要性

在各个编程语言以及其运行框架中,内存管理是及其重要的一个功能。内存管理就会涉及到一个场景,我们怎么能确定占用这个地址的内存是能被框架回收的呢?

引用此时就发挥了重要作用,在Java中,内存管理并非交由开发者管理,而是由JVM来进行系统性的管理的。虚拟机使用可达性算法来分析对象是否还在被引用。引用则是判断该对象能不能到达的一条路径,没有引用不可达到,则能回收。

简单示例:

java

代码解读

复制代码

public class Main {
	public static void main(String[] args) {
    	Map<String, Object> map = new HashMap<>();
        map = new TreeMap<>();
    }
}

在第3行代码中,我们实例化了HashMap对象,那么现在在堆中,就会有一块内存是该对象占用的,然后map引用了这个对象。

其次在第4行代码中,我们重新赋值了map。此时,刚刚实例化的HashMap对象就没有被任何变量以及对象引用,在下一次的垃圾回收中,HashMap对象就会被回收掉。

强引用(Strong Reference)

定义和特点

在Java中,通常来说只要一个对象被变量或者对象引用的话,那么两者之前的引用关系就被称为强引用。

java

代码解读

复制代码

public class Main {

    private static final Object OBJECT = new Object();
    
    public static void main(String[] args) {
        Object obj = new Object();
    }
}

在上面的代码中,OBJECTobj都存在强引用关系。

内存管理和垃圾回收行为

要是我们没有显式的声明一个对象为null的话,只要程序还在运行且该对象能被其他对象所使用。那么它就无法被垃圾回收,直到程序退出。

当我们在一个方法内创建一个对象时,只要该对象不会被其他变量引用时,在方法执行完后。可达性分析算法就会认为该对象能被回收,哪怕之前该对象存在强引用。

软引用(Soft Reference)

定义和用途

SoftReference类的注释中,提到了该类最主要的作用

Soft references are most often used to implement memory-sensitive caches.

软引用最常用于实现内存敏感缓存

另外,注释中还有着一句话:

All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError.

JVM抛出OOM之前,会将所有软引用的引用对象给清除。

这句话也侧面说明了它的用途:缓存

如何创建和使用软引用

java

代码解读

复制代码

public class Main {

    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        SoftReference<Object> softReference = new SoftReference<>(object);
    }
}

创建一个SoftReference对象,将需要引用的对象实例化时传递即可。

软引用在内存不足时的回收机制

示例程序

启动JVM参数:

-Xms512m -Xmx512m

示例代码:

java

代码解读

复制代码

package com.zsk;

import java.lang.ref.SoftReference;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;


public class Main {

    public static void main(String[] args) throws InterruptedException {
        int[] array = new int[(int) 1e7];
        List<SoftReference<int[]>> data = new ArrayList<>();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        while (true) {
            int[] arr = new int[(int) 1e7];
            Thread.sleep(1000L);
            SoftReference<int[]> softReference = new SoftReference<>(arr);
            System.out.println("Time: " + simpleDateFormat.format(new Date()));
            data.add(softReference);
        }
    }

}

上面的代码作用是每隔一秒,创建一个大数组对象以及创建一个软引用对象引用大数组对象,最后将软引用对象放在列表中。

内存变动

当程序启动后,我们就能得到下面这个有规律的图。

由IntelliJ IDEA生成

查看路径:Profiler-> 选择对应的进程 -> CPU和内存实时图表

每次堆内存在即将使用完时,JVM将进行垃圾回收,此时软引用所引用的对象就会被回收掉了。

我们也能计算出每次创建大数组对象需要的内存空间:

10000000(size)* 4(int占用字节)= 40000000byte ≈ 38.15MB

该数量与上方内存增长大小与回收后的空间也是一致的。

内存分析

我们也可以MAT内存分析工具来进一步佐证。

首先,我们需要对代码进行一些处理,在垃圾回收前以及垃圾回收后的内存快照(hprof文件)给保存下来

改动后的代码:

java

代码解读

复制代码

public class Main {

    public static void main(String[] args) throws InterruptedException {
        int[] array = new int[(int) 1e7];
        List<SoftReference<int[]>> data = new ArrayList<>();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        int i = 0;
        while (true) {
            if (i++ == 10) {
                Thread.sleep(10000L);
            }
            if (i > 10) {
                Thread.sleep(10000L);
            } else {
                Thread.sleep(1000L);
            }
            int[] arr = new int[(int) 1e7];
            SoftReference<int[]> softReference = new SoftReference<>(arr);
            System.out.println("Time: " + simpleDateFormat.format(new Date()));
            data.add(softReference);
        }
    }

}

垃圾回收变化以及回收机制

垃圾回收前的内存占用情况:

垃圾回收后的内存占用情况:

可以清楚的看到了ArrayList中的软引用的引用对象(大数组)都被回收掉了,也验证了该类上的注释:在抛出OOM之前,也就是堆内存使用完之前,将所有的弱引用的引用对象给回收。

题外话

看到对比图,可能有个疑问,为什么ArrayList中弱引用对象的地址变动了呢?

在JDK进行垃圾回收时,根据不同垃圾回收器使用的垃圾回收算法,会进行内存空间的整理。

我们可以在运行前,添加JVM参数,就能打印出使用的垃圾回收器。

增加JVM启动参数:-Xloggc:.\gc.log

就能在gc.log这个文件中,看到所使用的垃圾回收器类型以及一些启动信息

latex

代码解读

复制代码

OpenJDK 64-Bit Server VM (25.392-b08) for windows-amd64 JRE (1.8.0_392-b08), built on Oct 16 2023 22:02:46 by "Administrator" with MS VC++ 15.9 (VS2017)
Memory: 4k page, physical 16726872k(8454788k free), swap 17775448k(3601532k free)
CommandLine flags: -XX:InitialHeapSize=536870912 -XX:MaxHeapSize=536870912 -XX:+PrintGC 
-XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops 
-XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 

由于示例代码在运行前没有指定垃圾回收器,就是默认使用UseParallelGC并行垃圾回收器。

并行垃圾回收器使用的是标记-复制算法。

该算法会进行三步:

  1. 标记:标记未被引用对象(不可达算法),其中只被软引用引用的对象也会被标记出来。
  2. 复制ParallelGC将存活的对象从Eden区和from区复制到to区,内存地址变动也是因为这个复制所作的操作。
  3. 清理ParallelGC会清理掉年轻代中不再使用的对象。


转载来源:https://juejin.cn/post/7323251349302067263

相关文章
|
27天前
|
安全 IDE Java
Java常见规范及易忘点
遵循Java编程规范和注意易忘点是提高代码质量和可维护性的关键。通过规范的命名、格式、注释和合理的代码组织,可以让代码更加清晰和易于维护。同时,注意空指针检查、线程安全、集合框架和字符串操作等常见易忘点,可以减少程序错误,提高运行效率。结合单一职责原则、面向接口编程和合理的异常处理,能够编写出高质量的Java代码。希望本文能够帮助Java开发者提升编码水平,写出更高效、更可靠的代码。
24 2
|
2月前
|
Java 编译器 Android开发
java作业的提交规范与要求
java作业的提交规范与要求
29 0
|
3月前
|
Java API 开发者
Java 注释规范
Java中的注释规范包括单行注释(`//`)、多行注释(`/* ... */`)和文档注释(`/** ... */`)。单行注释适用于简短说明,多行注释用于较长描述,文档注释则专为自动生成API文档设计。注释应清晰明了、及时更新,避免冗余,并详细说明参数和返回值。遵循这些规范有助于提高代码的可读性和可维护性。
223 5
|
4月前
|
Java
Java应用结构规范问题之在UnitConvertUtils工具类将千米转换为米的问题如何解决
Java应用结构规范问题之在UnitConvertUtils工具类将千米转换为米的问题如何解决
|
4月前
|
Java 应用服务中间件 HSF
Java应用结构规范问题之配置Logback以仅记录错误级别的日志到一个滚动文件中的问题如何解决
Java应用结构规范问题之配置Logback以仅记录错误级别的日志到一个滚动文件中的问题如何解决
|
4月前
|
Java 应用服务中间件 HSF
Java应用结构规范问题之配置Logback以在控制台输出日志的问题如何解决
Java应用结构规范问题之配置Logback以在控制台输出日志的问题如何解决
|
4月前
|
Java 开发者
Java 编程风格与规范:跟上时代热点,打造高质量代码,为开发者梦想保驾护航
【8月更文挑战第30天】本文强调了Java编程中代码质量和可维护性的重要性,详细介绍了命名规范、代码格式和注释的最佳实践,如使用描述性的命名、适当的缩进及空行,以及关键代码部分的注释说明,同时还提供了避免魔法值和减少代码重复的建议与示例,帮助提升团队协作效率和项目长期发展。
83 2
|
4月前
|
Java
编写规范JAVA代码
本文档制定了Java编程规范,旨在确保系统源程序的可读性和可维护性,适用于所有Java开发、测试及维护过程。规范包括命名规则(如Package、Class及其成员等)与样式规定,强调统一风格以提高协作效率,并列举了具体示例与注意事项,如避免单字符变量名及使用有意义的反义词组命名等。
74 1
|
4月前
|
Java 应用服务中间件 HSF
Java应用结构规范问题之AllLoggers接口获取异常日志的Logger实例的问题如何解决
Java应用结构规范问题之AllLoggers接口获取异常日志的Logger实例的问题如何解决
|
4月前
|
Java 应用服务中间件 HSF
Java应用结构规范问题之dal层中的mapper数据源类型进行组织的问题如何解决
Java应用结构规范问题之dal层中的mapper数据源类型进行组织的问题如何解决
下一篇
DataWorks