源码随想 String -> SoftReference

2021年7月27日 15:38:14

今天实习时看 String的源码,发现其中的一个构造方法

public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}

StringCoding.decode()方法处点入之后,发现其实现如下:

static char[] decode(String charsetName, byte[] ba, int off, int len)
throws UnsupportedEncodingException
{
StringDecoder sd = deref(decoder);
String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
if ((sd == null) || !(csn.equals(sd.requestedCharsetName())
|| csn.equals(sd.charsetName()))) {
sd = null;
try {
Charset cs = lookupCharset(csn);
if (cs != null)
sd = new StringDecoder(cs, csn);
} catch (IllegalCharsetNameException x) {}
if (sd == null)
throw new UnsupportedEncodingException(csn);
set(decoder, sd);
}
return sd.decode(ba, off, len);
}

deref()方法处点入,发现其实现如下:

private static <t> T deref(ThreadLocal<softreference<t>> tl) {
SoftReference<t> sr = tl.get();
if (sr == null)
return null;
return sr.get();
}

没想到,这个 decoder居然是一个存放在 ThreadLocal里的 软引用!!!

关于 tl.get(),也就是 ThreadLocal :: get(),虽然还没看过这部分代码,但是在马士兵的课上和一些面经上面都强调过它的重要性,在这里一看到,忽然有一种他乡遇故知的感受。

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

不止如此,在 sr.get()方法中,有两个很奇怪的量 clocktimestamp

public T get() {
T o = super.get();
if (o != null && this.timestamp != clock)
this.timestamp = clock;
return o;
}

进入到 SoftReference.java类中之后,有如下定义:

    /**
* Timestamp clock, updated by the garbage collector
*/
static private long clock; /**
* Timestamp updated by each invocation of the get method. The VM may use
* this field when selecting soft references to be cleared, but it is not
* required to do so.
*/
private long timestamp;

也就是说,clock会在每次进行垃圾收集的时候更新;timestamp会在每次方法调用的时候更新。JVM通过对 timestamp字段的监测来判断是否需要清楚弱引用。

那么到底什么时候真正进行软引用的清理呢?

首先,软引用中有一个全局变量 clock,在每一次垃圾回收的时候,它的值都会更新(in millis)。

同时,每一个软引用都有一个 timestamp变量,当前软引用每次被访问的时候都会更新(包括 初始化 和 调用get()方法)

当一次新的垃圾回收开始时,是否要回收一个软饮用对象依据如下:

  1. timestamp的年龄多大了
  2. 剩下的堆空间还有多少

具体的计算方式如下:

  • free_heap为堆中的空余空间大小(以 MB为单位)
  • interval是上一次 GC和 现在timestamp的时间差,
  • ms_per_mb是一个常量,保持堆中空余的每MB空间中的 软引用 的毫秒值
interval <= free_heap * ms_per_mb

举个例子:

如果当前的某个软引用 A,它的 timestamp值为 2000ms,上一次 GC的 clock时间是 5000ms,ms_per_mb常量的大小是 1000,空闲的堆空间为 1MB,那么,计算可得

5000 - 2000 <= 1 * 1000 是假的,返回 false

所以我们需要清理掉这个软引用。

那么如果假设堆空间比较大,比如是 4MB,那么计算可得

5000 - 2000 <= 4 * 1000 是真的

所以我们不需要清理这个软引用


需要注意到的是:

一个软引用在一次访问之后,至少会存活一次 GC。

因为:

interval的值是我们通过 clocktimestamp计算出来的,而 clock是上一次的 FGC时间,既然有上一次的 GC,说明它肯定至少得活过一次 GC呀。

此外,每当一个引用在某次垃圾回收之后被访问到了,那么 timestamp的值会被设置成和 clock一样,然后 interval就变成了 0,软引用就肯定不会被回收了。

想想也是的呀,针对经常在用的对象,我们肯定是要保留的呀。</softreference

源码随想 String -> SoftReference的更多相关文章

  1. JDK源码学习--String篇(二) 关于String采用final修饰的思考

    JDK源码学习String篇中,有一处错误,String类用final[不能被改变的]修饰,而我却写成静态的,感谢CTO-淼淼的指正. 风一样的码农提出的String为何采用final的设计,阅读JD ...

  2. 深入源码剖析String,StringBuilder,StringBuffer

    [String,StringBuffer,StringBulider] 深入源码剖析String,StringBuilder,StringBuffer [作者:高瑞林] [博客地址]http://ww ...

  3. JDK源码之String类解析

    一 概述 String由final修饰,是不可变类,即String对象也是不可变对象.这意味着当修改一个String对象的内容时,JVM不会改变原来的对象,而是生成一个新的String对象 主要考虑以 ...

  4. JDK1.8源码学习-String

    JDK1.8源码学习-String 目录 一.String简介 String类是Java中最常用的类之一,所有字符串的字面量都是String类的实例,字符串是常量,在定义之后不能被改变. 二.定义 p ...

  5. JDK源码学习--String篇(四) 终结篇

    StringBuilder和StringBuffer 前面讲到String是不可变的,如果需要可变的字符串将如何使用和操作呢?JAVA提供了连个操作可变字符串的类,StringBuilder和Stri ...

  6. JDK源码学习--String篇(三) 存储篇

    在进一步解读String类时,先了解下内存分配和数据存储的. 数据存储 1.寄存器:最快的存储区,位于处理器的内部.由于寄存器的数量有限,所以寄存器是按需分配. 2.堆栈:位于RAM中,但是通过堆栈指 ...

  7. JDK源码学习--String篇(-)

    工作三年了,用了三年的JAVA,突然发现竟然没有好好的看下JDK的源码,整天用着的String,只是大概知道怎么回事,其中的实现逻辑却是一头雾水. 知耻而后勇,加油!!! java.lang.Stri ...

  8. java基础进阶一:String源码和String常量池

    作者:NiceCui 本文谢绝转载,如需转载需征得作者本人同意,谢谢. 本文链接:http://www.cnblogs.com/NiceCui/p/8046564.html 邮箱:moyi@moyib ...

  9. [Java源码解析] -- String类的compareTo(String otherString)方法的源码解析

    String类下的compareTo(String otherString)方法的源码解析 一. 前言 近日研究了一下String类的一些方法, 通过查看源码, 对一些常用的方法也有了更透彻的认识,  ...

随机推荐

  1. mysql主从复制学习笔记

    一.musql主从复制原理 MySQL之间数据复制的基础是二进制日志文件(binary log file).一台MySQL数据库一旦启用二进制日志后,其作为master,它的数据库中所有操作都会以&q ...

  2. 体验Lambda的更优写法和Lambda标准格式

    体验Lambda的更优写法 借助Java8的全新语法,上述Runnable接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效: public class Lambda02 { public ...

  3. 重写并自定义依赖的原生的Bean方法

    转载请注明出处: 在项目开发过程中,往往是直接应用很多jar包中依赖且声明好的Bean,拿来即用,但很多场景也需要对这些原生的Bean 进行自定义,定制化封装,这样在项目使用的过程中,可以使用自定义的 ...

  4. css基础02

    熟练快捷键!方便,要多练!  css复合选择器 不会选孙子,有一个儿子和另一个儿子的孩子(也是孙子)同名了,但子选择器子选择儿子,同名的孙子不选.和后代选择器有一点不一样的. " ,&quo ...

  5. YII容器类依赖注入

    程序 = 算法 + 数据结构 数据结构 制约了 算法的===>>>>依赖注入 依赖注入也就是解数据结构和算法耦合的思想 <?php /** * Created by Ph ...

  6. 在Centos下对高并发web框架Tornado的性能进行测试

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_89 在之前的一篇文章中,我们在1g1核的惨淡硬件环境下,对 uwsgi + django 和 gunicorn+ django 的 ...

  7. PostGresql listen与notify命令

    LISTEN与NOTIFY命令 PostgreSQL提供了client端和其他client端通过服务器端进行消息通信的机制.这种机制 是通过LISTEN和NOTIFY命令来完成的. 1.LISTEN与 ...

  8. 【FAQ】【Push Kit】 华为怎么设置角标

    ​[问题描述] HMS Core Push 怎么设置角标?角标设置不成功如何去排查问题? [问题解答] 华为推送怎么设置角标? 1.消息体设置 我们可以参考桌面角标这一篇文档进行设置角标 ​ 2.端测 ...

  9. Dolphin Scheduler 1.2.0 部署参数分析

    本文章经授权转载 1 组件介绍 Apache Dolphin Scheduler是一个分布式易扩展的可视化DAG工作流任务调度系统.致力于解决数据处理流程中错综复杂的依赖关系,使调度系统在数据处理流程 ...

  10. 记录一个i变量引发的事故

    概述 近期开发中遇到一个特别的问题,觉得很有必要与你下来.就是由于在开发中一个很小的疏忽,导致了很大的问题,是什么呢? 现象 我的程序突然引发了v8内部的错误,提示都是c++的,如下.程序一启动就直接 ...