Java 内存管理技巧(新手必看集合篇)

😁前言

引用类型是java中很重要的一个知识,也是很容易忽略的一个知识点。经常背八股文的同学肯定已经很熟悉,但是你们知道在什么场景用吗?在实际开发中用过吗?今天主要分享一下弱引用的使用场景。

如果还不熟悉引用类型的同学,就先看看基础知识吧!

引用类型基础知识

    1. 强引用(Strong Reference) : 强引用是最常见的引用类型,通过new关键字创建的对象都属于强引用。只要强引用存在,垃圾回收器(GC)就不会回收被引用的对象。
java 复制代码
Object obj = new Object(); // 强引用
obj = null; // 置为null后,对象可被GC回收
    1. 软引用(SoftReference) : 软引用通过SoftReference类实现。当内存充足时,软引用的对象不会被回收;但当内存不足时,GC 会回收这些对象。(缓存场景)
java 复制代码
SoftReference<String> softRef = new SoftReference<>(new String("Hello"));
  
// 获取对象
String str = softRef.get();
System.out.println(str); // 输出: Hello

// 手动触发GC(内存充足时软引用对象可能不会被回收)
System.gc();
str = softRef.get();
System.out.println(str); // 可能输出: Hello(取决于内存情况)
    1. 弱引用(WeakReference) : 弱引用通过WeakReference类实现。无论内存是否充足,只要 GC 扫描到弱引用对象,就会立即回收它。(临时关联对象,避免内存泄漏)
java 复制代码
// 创建弱引用
WeakReference<String> weakRef = new WeakReference<>(new String("Weak"));

// 获取对象
String str = weakRef.get();
System.out.println(str); // 输出: Weak

// 手动触发GC(弱引用对象会被立即回收)
System.gc();
str = weakRef.get();
System.out.println(str); // 输出: null
    1. 虚引用(PhantomReference) :虚引用通过PhantomReference类实现。它无法直接获取对象,主要用于在对象被回收时收到系统通知,通常配合引用队列使用。(实际的业务系统开发使用场景也非常少,本篇文章就不过多的介绍了)
java 复制代码
// 创建引用队列
ReferenceQueue<String> queue = new ReferenceQueue<>();
// 创建虚引用(必须关联引用队列)
PhantomReference<String> phantomRef = new PhantomReference<>(
    new String("Phantom"), queue
);

// 虚引用无法获取对象
System.out.println(phantomRef.get()); // 输出: null

// 手动触发GC
System.gc();
Thread.sleep(100); // 等待GC完成

// 检查引用队列
if (queue.poll() != null) {
    System.out.println("对象已被回收"); // 输出: 对象已被回收
}

📖在springboot 中的运用场景

springboot中软弱引用的的地方用得非常多,关键是现实类是 org.springframework.util.ConcurrentReferenceHashMap 工具类, 这是 Spring 框架自带的一个工具类,支持soft、weak 两种应用类型,默认soft引用。 springboot 中 使用软弱应用也主要是通过调用该工具类来实现的缓存。

ConcurrentReferenceHashMap 核心代码

java 复制代码
/**
 * The reference type: SOFT or WEAK.
 */
private final ReferenceType referenceType;
..................
public ConcurrentReferenceHashMap(
      int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) {

   Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative");
   Assert.isTrue(loadFactor > 0f, "Load factor must be positive");
   Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive");
   Assert.notNull(referenceType, "Reference type must not be null");
   this.loadFactor = loadFactor;
   this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL);
   int size = 1 << this.shift;
   this.referenceType = referenceType;
   int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size);
   int initialSize = 1 << calculateShift(roundedUpSegmentCapacity, MAXIMUM_SEGMENT_SIZE);
   //------------------------------------
   Segment[] segments = (Segment[]) Array.newInstance(Segment.class, size);
   int resizeThreshold = (int) (initialSize * getLoadFactor());
   for (int i = 0; i < segments.length; i++) {
      segments[i] = new Segment(initialSize, resizeThreshold);
   }
   this.segments = segments;
}

2.Segment 构造器

java 复制代码
public Segment(int initialSize, int resizeThreshold) {
    this.referenceManager = createReferenceManager();
    this.initialSize = initialSize;
    this.references = createReferenceArray(initialSize);
    this.resizeThreshold = resizeThreshold;
}

3.createReference

arduino 复制代码
public Reference<K, V> createReference(Entry<K, V> entry, int hash, @Nullable Reference<K, V> next) {
    if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) {
       return new WeakEntryReference<>(entry, hash, next, this.queue);
    }
    return new SoftEntryReference<>(entry, hash, next, this.queue);
}

springbooot 如何使用ConcurrentReferenceHashMap

springboot 中使用ConcurrentReferenceHashMap的地方非常的多

今天简单看看其中几个类吧,常见的BeanUtils.javaSpringFactoriesLoaderSpringConfigurationPropertySourcesTreadPoolTaskExcutor

1. BeanUtils 中运用

根据约定规则(Convention over Configuration),为给定的目标类型(targetType)查找对应的 PropertyEditor 编辑器。 将查找失败的类型记录到 unknownEditorTypes 集合中,避免后续重复尝试加载,提升性能。使用弱引用类型缓存数据,在内存不足发生GC时targetType不存在强引用时将会被回收。

java 复制代码
private static final Set<Class<?>> unknownEditorTypes =
       Collections.newSetFromMap(new ConcurrentReferenceHashMap<>(64));

..........................................
@Nullable
public static PropertyEditor findEditorByConvention(@Nullable Class<?> targetType) {
    if (targetType == null || targetType.isArray() || unknownEditorTypes.contains(targetType)) {
       return null;
    }

    ClassLoader cl = targetType.getClassLoader();
    if (cl == null) {
       try {
          cl = ClassLoader.getSystemClassLoader();
          if (cl == null) {
             return null;
          }
       }
       catch (Throwable ex) {
          // e.g. AccessControlException on Google App Engine
          return null;
       }
    }

    String targetTypeName = targetType.getName();
    String editorName = targetTypeName + "Editor";
    try {
       Class<?> editorClass = cl.loadClass(editorName);
       if (editorClass != null) {
          if (!PropertyEditor.class.isAssignableFrom(editorClass)) {
             unknownEditorTypes.add(targetType);
             return null;
          }
          return (PropertyEditor) instantiateClass(editorClass);
       }
       // Misbehaving ClassLoader returned null instead of ClassNotFoundException
       // - fall back to unknown editor type registration below
    }
    catch (ClassNotFoundException ex) {
       // Ignore - fall back to unknown editor type registration below
    }
    unknownEditorTypes.add(targetType);
    return null;
}

2.SpringFactoriesLoader 中运用

Spring 框架中用于加载 META-INF/spring.factories 文件的核心工具,实现了 Spring 的自动发现机制(Auto-detection),ConcurrentReferenceHashMap 充当缓存器,类加载器作为Key, 类加载器加载配置文件的结果做value。

java 复制代码
static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();

......................
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = cache.get(classLoader);
    if (result != null) {
       return result;
    }

    result = new HashMap<>();
    try {
       Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
       while (urls.hasMoreElements()) {
          URL url = urls.nextElement();
          UrlResource resource = new UrlResource(url);
          Properties properties = PropertiesLoaderUtils.loadProperties(resource);
          for (Map.Entry<?, ?> entry : properties.entrySet()) {
             String factoryTypeName = ((String) entry.getKey()).trim();
             String[] factoryImplementationNames =
                   StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
             for (String factoryImplementationName : factoryImplementationNames) {
                result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                      .add(factoryImplementationName.trim());
             }
          }
       }

       // Replace all lists with unmodifiable lists containing unique elements
       result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
             .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
       cache.put(classLoader, result);
    }
    catch (IOException ex) {
       throw new IllegalArgumentException("Unable to load factories from location [" +
             FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
}

3.SpringConfigurationPropertySources 中的运用

将 Spring 标准的 PropertySource 对象转换为 Spring Boot 特有的 ConfigurationPropertySource ,使用ConcurrentReferenceHashMap 缓存避免重复适配相同的 PropertySource,提升配置读取效率。在内存不足发生GC回收没有被强引用的对象,来减少OOM的风险

java 复制代码
private final Map<PropertySource<?>, ConfigurationPropertySource> cache = new ConcurrentReferenceHashMap<>(16,
       ReferenceType.SOFT);
..............................
private ConfigurationPropertySource adapt(PropertySource<?> source) {
    ConfigurationPropertySource result = this.cache.get(source);
    // Most PropertySources test equality only using the source name, so we need to
    // check the actual source hasn't also changed.
    if (result != null && result.getUnderlyingSource() == source) {
       return result;
    }
    result = SpringConfigurationPropertySource.from(source);
    if (source instanceof OriginLookup) {
       result = result.withPrefix(((OriginLookup<?>) source).getPrefix());
    }
    this.cache.put(source, result);
    return result;
}

4. TreadPoolTaskExcutor.java

看看线程池的初始化,和 线程池关闭在shutdown 时候调用关闭剩余任务的cancelRemainingTask方法

java 复制代码
// Runnable decorator to user-level FutureTask, if different
private final Map<Runnable, Object> decoratedTaskMap =
       new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.WEAK);

.....................
//1. 初始化线程池
protected ExecutorService initializeExecutor(
       ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {

    BlockingQueue<Runnable> queue = createQueue(this.queueCapacity);

    ThreadPoolExecutor executor;
    if (this.taskDecorator != null) {
       executor = new ThreadPoolExecutor(
             this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
             queue, threadFactory, rejectedExecutionHandler) {
          @Override
          public void execute(Runnable command) {
             Runnable decorated = taskDecorator.decorate(command);
             if (decorated != command) {
                decoratedTaskMap.put(decorated, command);
             }
             super.execute(decorated);
          }
       };
    }
    else {
       executor = new ThreadPoolExecutor(
             this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
             queue, threadFactory, rejectedExecutionHandler);

    }

    if (this.allowCoreThreadTimeOut) {
       executor.allowCoreThreadTimeOut(true);
    }
    if (this.prestartAllCoreThreads) {
       executor.prestartAllCoreThreads();
    }

    this.threadPoolExecutor = executor;
    return executor;
}


.............................
//在线程池关闭时取消剩余任务,在线程池关闭时取消剩余任务.当装饰任务(键)或原始任务(值)不再有其他强引用时,会被垃圾回收器自动清理,避免内存占用。
/** Perform a shutdown on the underlying ExecutorService.
* See Also:
* ExecutorService.shutdown(), ExecutorService.shutdownNow()
**/
@Override
protected void cancelRemainingTask(Runnable task) {
    super.cancelRemainingTask(task);
    // Cancel associated user-level Future handle as well
    Object original = this.decoratedTaskMap.get(task);
    if (original instanceof Future) {
       ((Future<?>) original).cancel(true);
    }
}

看了几个springboot 中使用软引用的场景我想大家对软弱因应用的使用场景已经比较清楚了吧。还不清楚的就看看下面几个简单的小例子吧!

🏹Java 内存管理技巧 实战

在构建可扩展的 Java 应用程序时,高效的内存使用和性能变得至关重要。当然呢,实际开发中很多服务的缓存都存放到了redis中,所以大家对 软弱应用的使用或者了解就非常的少了。

对于开发中没有使用redis的应用来说,软弱应用来实现缓存那就非常的重要了。下面就给大家展示几个软弱引用的例子吧

ConcurrentHashMap

缓存数据库查询结果、计算结果等大对象,在内存不足的情况下发生GC自动释放。

因为现在很多缓存数据,都是存放在redis中了,所以用软应用的就不多了。如果没有用redis的情况,使用软引用时非常不错的

java 复制代码
//缓存数据:比如缓存的一些 组织机构呀等信息
private final ConcurrentHashMap<String, SoftReference<Object>> cache =
        new ConcurrentHashMap<>();

public Object getData(String key) {
    SoftReference<Object> ref = cache.get(key);
    Object data = ref != null ? ref.get() : null;

    if (data == null) {
        data = loadDataFromDatabase(key);
        cache.put(key, new SoftReference<>(data));
    }
    return data;
}

// 模拟数据库查询
private Object loadDataFromDatabase(String key) {
    return new byte[1024 * 1024]; // 1MB数据
}

WeakHashMap 和 WeakHashSet:智能内存友好型容器

WeakHashMap 实战
java 复制代码
import java.util.Map; 
import java.util.WeakHashMap; 

public class WeakMapExample {
    public static void main(String[] args) {
        Map<Object, String> map = new WeakHashMap<>();
        Object key = new Object();

        map.put(key, "Some value");

        System.out.println("Before GC: " + map.size()); // 输出1

        key = null; // 移除强引用
        System.gc(); // 提示GC进行回收

        try { 
            Thread.sleep(100); 
        } catch (InterruptedException ignored) {}

        System.out.println("After GC: " + map.size()); // 可能输出0
    } 
}

💡 原理 :当key的强引用被移除后,GC 会自动清理。key会被垃圾回收,因为map仅持有对它的弱引用。

创建 WeakHashSet
java 复制代码
import java.util.*; 

public class WeakSetExample {
    public static void main(String[] args) {
        Set<Object> weakSet = Collections.newSetFromMap(new WeakHashMap<>());
        Object item = new Object();

        weakSet.add(item);
        System.out.println("Before GC: " + weakSet.size()); // 输出1

        item = null;
        System.gc();
        try { 
            Thread.sleep(100); 
        } catch (InterruptedException ignored) {}

        System.out.println("After GC: " + weakSet.size()); // 可能输出0
    } 
}

应用场景:用于GC时对象可以被回收的缓存(如临时数据、监听器注册等)。

⚙️ 预定义集合大小以提升性能

当您知道(或可以估算)元素数量时,设置初始容量可以节省时间和内存。

🆚 默认大小与预定义大小的 ArrayList 对比
java 复制代码
List<String> dynamicList = new ArrayList<>(); // 默认初始容量10
List<String> preSizedList = new ArrayList<>(1000); // 预定义容量1000

for (int i = 0; i < 1000; i++) {
    dynamicList.add("Item " + i);
    preSizedList.add("Item " + i); 
}

预定义大小的优势
preSizedList 在增长过程中避免了多次内部数组扩容,从而:

  • 减少堆内存波动
  • 降低 GC 循环频率
  • 内存使用更可预测
预定义 HashMap 大小
java 复制代码
Map<Integer, String> map = new HashMap<>(1000); // 初始容量1000
for (int i = 0; i < 1000; i++) {
    map.put(i, "Value " + i); 
}

内部机制:HashMap 的每次扩容都需要重新哈希所有条目,成本较高。预定义大小可减少此类操作。

基准测试(推荐)

如果需要确认具体场景下的性能提升,可使用 JMH(Java 微基准测试工具) 测试内存使用和执行时间差异。

🧵 总结

本篇文章介绍了,引用类型的基础知识,也分析springboot框架中软弱引用工具类ConcurrentReferenceHashMap,以及其在框架中哪些地方有使用,最后作者也简单了写了两个小例子。 通过本篇文章我们发现,平时大家很容易忽略的引用类型,实际在开发中也是非常重要的存在。

💬 互动时刻

你在项目中使用过弱引用或优化过集合大小吗?带来了怎样的影响?欢迎在评论区分享 👇

相关推荐
楚歌again6 分钟前
【如何在IntelliJ IDEA中新建Spring Boot项目(基于JDK 21 + Maven)】
java·spring boot·intellij-idea
酷爱码7 分钟前
IDEA 中 Maven Dependencies 出现红色波浪线的原因及解决方法
java·maven·intellij-idea
Magnum Lehar37 分钟前
vulkan游戏引擎test_manager实现
java·算法·游戏引擎
sss191s39 分钟前
校招 java 面试基础题目及解析
java·开发语言·面试
异常君42 分钟前
MySQL 中 count(*)、count(1)、count(字段)性能对比:一次彻底搞清楚
java·mysql·面试
wkj0011 小时前
QuaggaJS 配置参数详解
java·linux·服务器·javascript·quaggajs
异常君2 小时前
MyBatis 中 SqlSessionFactory 和 SqlSession 的线程安全性深度分析
java·面试·mybatis
crud2 小时前
Spring Boot 使用 spring-boot-starter-validation 实现优雅的参数校验,一文讲透!
java·spring boot
Dcs2 小时前
常见 GC 垃圾收集器对比分析
java
程序员岳焱2 小时前
Java高级反射实战:15个场景化编程技巧与底层原理解析
java·后端·编程语言