Java强软弱虚引用和ThreadLocal工作原理(一)

news2025/2/23 12:51:50

一、概述

        本篇文章先引入java的四种引用在android开发中的使用,然后结合弱引用来理解ThreadLocal的工作原理。

二、JVM名词介绍

        在提出四种引用之前,我们先提前说一下 Java运行时数据区域   虚拟机栈  堆  垃圾回收机制 这四个概念。

2.1 java运行时数据区域

        java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机的进程的启动而一直存在,有些区域是依赖用户线程的启动/结束 对应的 建立/销毁。 根据java虚拟机的规范,java虚拟机所管理的内存包括以下几个运行时数据区域:

 其中方法区堆区是所有线程共享的,而 虚拟机栈 本地方法栈  程序计数器 是属于线程私有。

先介绍在本文中必须要提前理解的 虚拟机栈  和 

2.2 虚拟机栈

        Java虚拟机栈(Java Virtual Machine Stack)是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是java方法执行时的线程内存模型:每个方法被执行的时候,java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储 局部变量表  操作数栈  动态链接  方法返回地址等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。虚拟机栈结构示意图如下:

局部变量表存放了编译期可知的各种java虚拟机基本类型变量:

1.基本数据类型(boolean  byte  char short int  float  long  double)

2.对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也有可能是指向一个代表对象的句柄)

3. 方法Return Address类型(指向了一条字节码指令的地址)。

2.3 java堆

        对于java应用程序来说,java堆(Java Heap)是虚拟机所管理的内存中最大的一块。java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,在此内存区域的唯一目的就是存放对象实例。在《java虚拟机规范》中对java堆的描述:“几乎所有的对象实例以及数组都应在堆上分配”。

堆,是GC(Garbage Collection,垃圾回收器)执行垃圾回收的重点区域。

Java 堆从 GC 的角度还可以细分为:新生代(Eden 区、SurvivorFrom 区和 SurvivorTo 区)和老年代。示意图如下:

了解 虚拟机栈 和 堆 这两个概念后,对我们下面画代码运行时内存分配图有帮助。

2.4 垃圾回收机制

1. 引用计数算法

判断对象是否存活的算法是这样的:在对象中添加一个引用计数器,每当有一个地方
引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的,

最大的缺陷:A如果引用B,B引用A 但是其他对象没有任何的引用A和B,相互存相互依赖,无法被垃圾回收。

2.  可达性分析算法

当前主流的商用程序语言(Java、C#,上溯至前面提到的古老的Lisp)的内存管理子系统,都是
通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。如图3-1所示,对象object 5、object 6、object 7虽然互有关联,但是它们到GC Roots是不可达的,因此它们将会被判定为可回收的对象。示意图:

        Java虚拟机的内存区域分成五块,其中三个是线程私有的:程序计数器、Java虚拟机栈、本地方法栈;另两个是线程共享的:Java堆、方法区。线程私有的区域等到线程结束时(栈帧出栈时)会自动被释放,空间较易被清理。而线程共享的Java堆和方法区中空间较大且没有线程回收容易,GC垃圾回收主要负责这部分。

可作为GC Roots的对象:
1)虚拟机栈(栈帧中的本地变量表)中引用的对象;

2)方法区中类静态属性引用的对象;

3)方法区中常量引用的对象;

4)本地方法栈中JNI(即一般说的Native方法)引用的对象;

GC Roots即指对象的引用,对对象的操作是通过引用来实现的,引用是指向堆内存对象的指针,如果当前对象没有引用指向,那该对象无法被操作,被视为垃圾。可达性分析算法主要从对象的引用出发,寻找对象是否存在引用,若不存在进行标识处理,为GC做准备

如何判断一个对象真正的死亡?

要真正宣告一个对象死亡,至少需要经历两次标记过程:

(1)第一次标记
在可达性分析后发现到GC Roots没有任何引用链相连时,被第一次标记,进行一次筛选,此对象是否有必要执行finalize()方法

没有必要执行情况:对象没有覆盖finalize()方法;finalize()方法已被JVM调用过一次;这两种情况可认为对象已死,可以回收;

有必要执行:对有必要执行finalize()方法的对象被放入F-Queue队列中,稍后JVM自动建立、低优先级的Finalizer线程(可能多个线程)中触发此方法;

(2)第二次标记
GC将F-Queue队列中的对象进行第二次小规模标记,finalize()方法是对象逃脱死亡的最后一次机会

A)若对象在finalize()方法中重新与引用链上任何一个对象建立关联,第二次标记时会将其移出“即将回收”的集合;

B)若对象没有,也可认为对象已死,可以回收了。

finalize()方法执行时间不确定,甚至是否被执行也不确定(Java程序不正常退出),且运行代价高昂,无法保证各对象调用顺序。


 

三、代码运行内存分配图

先复习一下java成员变量和局部变量的定义

成员变量:成员变量是指在类内   方法外定义的变量;

局部变量:是指在方法中定义的变量,作用范围是其所在的方法。

class Car {
    String mColor;  // 成员变量  作用范围是整个类
    float price;    // 成员变量  作用范围是整个类

    
    private String test(String arg) {  //这个arg是方法的形式参数,是局部变量
// str1是在方法内部定义的变量,作用域为方法内部,当方法执行完后,生命周期就结束了,为局部变量
        String str1 = "hello";
        return str1;
    }
}

1、在类中位置不同:    局部变量在方法中,成员变量在方法外。
2、在内存中位置不同:局部变量在栈内存中成员变量在堆内存中
3、生命周期不同:
     局部变量随方法的调用而存在,当方法被执行时局部变量被创建,当方法执行完毕出栈,局部变量跟随方法消失。
    成员变量随对象的存在而存在,当对象被创建之后,成员变量作为对象的属性,会与对象一同被存储在堆内存中,一直到对象消失才跟着消失。
4、初始化值不同:
    局部变量定义之后,必须主动赋值初始化才能被使用。
    成员变量作为对象的一部分,当对象被创建后,成员变量会被自动赋默认值(一般基本数据类型赋0,引用数据类型赋null)。
5、作用范围不同:局部变量只在其所在的方法内部生效,成员变量在其所在的整个类中生效。

我们看一个简单的java程序:

public class CarTest {
    static String DEFAULT_COLOR = "Whites";

    public static void main(String[] args) {
        Car c1 = new Car();
        c1.mColor = DEFAULT_COLOR;
        c1.price = 10000;
        c1.run();

        String redColor = "Red";
        Car c2 = new Car();
        c2.mColor = redColor;
        c2.price = 10000;
        c2.run();
    }
}

class Car {
    String mColor;
    float price;

    public void run() {
    }

    private String test(String arg) {
        String str1 = "hello";
        return str1;
    }
}

代码运行时的内存分析图:

 具体运行解析:

1. 运行程序,CarTest.java由编译器编译就会变为CarTest.class,将CarTest.class加入方法区,检查字节码是否有常量,若有(DEFAULT_COLOR)加入运行时常量池;

2. 遇到main方法,创建一个栈帧,入虚拟机栈,然后开始运行main方法中的程序

3. Car c1 = new Car(); 第一次遇到Car这个类,所以将Car.java编译为Car.class文件,然后加入方法区,跟第一步一样。然后new Car()。就在堆中创建一块区域,用于存放创建出来的实例对象,地址为0X0010.其中有两个属性值 color和num。默认值是null 和 00


4. 然后通过c1这个引用变量去设置color和num的值,

5. 调用run方法,然后会创建一个栈帧,用来装run方法中的局部变量的,入虚拟机栈,run方法结束之后,该栈帧出虚拟机栈。又只剩下main方法这个栈帧了

6. 接着又创建了一个Car对象,所以又在堆中开辟了一块内存,之后就是跟之前的步骤一样了。

四、强软弱虚引用

        从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

4.1 强引用

官方定义:

强引用是最常见的一种,一般在代码中直接通过new出来的对象,都是强引用。比如:

public class ObjectTest {

    public static void main(String[] args) {
//obj为强引用对象, 在方法内部定义,obj是一个局部变量,在栈内存中

        Object obj = new Object();

    }
}

如果一个对象具有强引用,只要强引用没有被销毁,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

加入一下我的理解

我们先来画一下运行时内存分析图:

就是说你的代码在运行期间,如果一个对象(new Object())具有强引用(指强引用 obj ),就算你执行System.gc()方法,它(指new Object()对象 )也不会被垃圾回收器回收。

那么就有一个问题,Object obj=new Object(),obj作为强引用存在虚拟机栈中而new Object()作为对象存在于堆中,当obj的作用域结束,对应的虚拟机栈消失,obj引用也同时消失,但new Object()对象却仍然存在于堆中,“JVM必定不会回收这个对象” ,那jvm不是很容易就OOM了吗?
 

你所考虑的问题,写垃圾回收机制的工程师肯定也考虑到了这点,第一种程序员设置obj=null,这样gc就会主动地将new Object()对象回收。

通过这个例子来说明:

public class StrongReference {

    public static void main(String[] args) {

        Method m = new Method();

        m = null; //这句代码是关键

        System.gc();

        System.out.println(m);

        try {
            System.in.read(); //阻塞主线程,给垃圾回收线程时间工作
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
public class Method {

    public Method() {
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        //finalize方法只有在JVM执行gc时才会被执行
        System.out.println("当对象引用被GC回收时,会调用此方法");
    }
}

1. 如果不设置 m = null  打印log如下:

com.example.javademo.reference.Method@2a139a55

对于强引用,不管你怎么调用System.gc()方法,这个new Objcet() 对象都不会被回收。

2. 手动设置 m = null 后,打印log如下:

null
当对象引用被GC回收时,会调用此方法

这种方式是程序员手动设置强引用 m = null 后,这个new Objcet()  对象就会被回收了。

写到这里,你肯定会杠精一下,平时做项目写代码,好像并没有特意去设置强引用对象为空啊,还是一样的跑咧。是的,没错,其实你也不用太担心这些问题,因为垃圾回收器已经默默的帮我们把这些事情做了,当你的程序运行结束后,这些强引用对象,垃圾回收机制会处理但并不是立即处理,至于原理,参考2.4 垃圾回收机制 可达性分析算法 

4.2 软引用

 如果一个对象只具有软引用,则内存空间足够时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。(备注:如果内存不足,随时有可能被回收。)。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

使用场景:

图片缓存。图片缓存框架中,“内存缓存”中的图片是以这种引用保存,使得 JVM 在发生 OOM 之前,可以回收这部分缓存。

如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出这时候就可以使用软引用

public class SoftReferenceTest<T> {

    public static void main(String[] args) {

        SoftReference<byte[]> sr = new SoftReference<>(new byte[1024 * 1024 * 5]);

        System.out.println(sr.get());

        System.gc();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(sr.get());
    }
}

 总结:虚引用就是只要运行垃圾回收器,当内存不足的情况下才会被回收

4.3 弱引用

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。

每次执行GC的时候,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

public class WeakReferenceTest {

    public static void main(String[] args) {

        WeakReference<Method> wr = new WeakReference<>(new Method());

        System.out.println(wr.get());

        System.gc();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(wr.get());


    }
}

打印log:

当对象引用被GC回收时,会调用此方法
null

总结:虚引用就是只要运行垃圾回收器,则引用对象就会被回收

弱引用比较重要,在Android开发中,很多地方用到,比如HandlerThread   ActivityThread   AMS 都有用到。

4.4 虚引用

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

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

 

五、扩展

        这些基础概念有新的理解在补充进来

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/28854.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

freeswitch通过limit限制cps

概述 freeswitch在业务开发中有极大的便利性&#xff0c;因为fs内部实现了很多小功能&#xff0c;这些小功能组合在一起&#xff0c;通过拨号计划就可以实现很多常见的业务功能。 在voip云平台的开发中&#xff0c;我们经常会碰到资源的限制&#xff0c;有外部线路资源方面的…

Linux环境下安装并使用使用Git命令实现文件上传

⭐️前面的话⭐️ 本篇文章将介绍在Linux环境下安装Git并使用Git实现代码上传到gitee&#xff0c;上传操作的核心就是三把斧&#xff0c;一是add&#xff0c;二是commit&#xff0c;三是push&#xff0c;此外还会简单介绍一下.gitignore配置文件的作用。 &#x1f4d2;博客主页…

【broadcast-service】一个轻量级Python发布订阅者框架

本文节选至本人博客&#xff1a;https://www.blog.zeeland.cn/archives/broadcast-service-description Introduction 前两天在Python最佳实践-构建自己的第三方库文章中介绍了自己构建的一个轻量级的Python发布订阅者框架&#xff0c;今天来简单介绍一下。 项目地址&#xf…

不同的量化交易软件速度差距大吗?

哪家券商的软件交易速度快&#xff1f;那个平台有极速柜台系统&#xff1f;成为了一个热门的话题&#xff0c;我来说下我的看法。其实呢&#xff0c;大部分的主流券商速度都是差不多的&#xff0c;否则的话&#xff0c;那速度有差距大家肯定都会冲向最快的那一家了。极速柜台系…

查看mysql的版本

1. mysql --version linux下使用命令&#xff1a; mysql --version 2. mysql -V 没有连接到MySQL服务器&#xff0c;就想查看MySQL的版本。打开cmd&#xff0c;切换至mysql的bin目录&#xff0c;运行下面的命令即可&#xff1a; 2.1 mysql -V e:\mysql\bin> mysql -V mys…

k8s编程operator——client-go中的informer

文章目录1、介绍1.1 简单使用1.2 List & Watch1.3 informer简介2、store2.1 ThreadSafeMap建立索引&#xff1a;threadSafeMap源码分析&#xff1a;2.2 Indexer2.3 DeltaFIFO3、reflector3.1 Reflector的定义3.2 Reflector的创建3.3 Reflector的循环执行3.4 List操作3.5 Wa…

JAVA 之 Spring框架学习 1:Springの初体验 IOC DI 注入 案例

Spring技术是JavaEE开发必备技能&#xff0c;企业开发技术选型命中率>90% 专业角度 简化开发&#xff0c;降低企业级开发的复杂性 框架整合&#xff0c;高效整合其他技术&#xff0c;提高企业级应用开发与运行效率 1.学习Spring框架设计思想 2.学习基础操作&#xff0c;思…

数据结构之选择排序(堆排序)

选择排序 选择排序分为两种一个是堆排序 一个是简单选择排序 简单选择排序 就是从头到尾扫描一遍待排序元素找出最小的 最小的之前的数的往后一位&#xff0c;第一个空间空出来 把最小的元素存入 然后从第二个空间开始变为待排序元素 最后一个元素不用处理 代码实现 算法性…

【python】根据自定义曲线函数进行拟合

【参考】 官网 curve_fit示例与评估&#xff1a;拟合curve_fit使用矫正的R^2评估非线性模型&#xff1a;拟合评估其他&#xff1a; curve_fit()实现任意形式的曲线拟合-csdn拟合优度r^2-csdn 官网示例 拟合函数&#xff1a; f(x)ae−bxcf(x)ae^{-bx}cf(x)ae−bxc import m…

Git——IDEA集成Git(详细)

目录 一、配置Git忽略文件 1.1 为什么忽略&#xff1f; 1.2 怎么忽略&#xff1f; 二. IDEA定位Git程序&#xff08;准备环境&#xff09; 三、IDEA操作Git 3.1 初始化Git本地库、添加暂存区、提交本地库 3.2 切换版本 3.3 创建分支 3.4 切换分支 3.5 合并分支 3.5.1…

存储模块 --- Cache

Cache 高速缓冲存储器 内存一般采用SDRAM芯片&#xff0c;对内存的访问肯定是不及CPU的速度的&#xff0c;通常说内存访问要比CPU的速度慢的多。也就是说内存拖后腿了。 CPU访问内存并不是完全随机的。 在某个时间段内&#xff0c;CPU总是访问当前内存地址的相邻内存地址&…

数理统计笔记5:参数估计-区间估计

引言 数理统计笔记的第5篇先介绍了参数估计的区间估计。包括单总体均值、单总体比例、单总体方差以及两总体均值之差、两总体方差之比几种常见的情况。 引言总体均值的置信区间&#xff08;σ2\sigma^2σ2已知&#xff09;-Z法总体均值的置信区间&#xff08;σ2\sigma^2σ2未知…

【Call for papers】SP-2023(CCF-A/网络与信息安全/2022年12月2日截稿)

文章目录1.会议信息2.时间节点Since 1980, the IEEE Symposium on Security and Privacy has been the premier forum for presenting developments in computer security and electronic privacy, and for bringing together researchers and practitioners in the field. The…

whylogs工具库的工业实践!机器学习模型流程与效果监控 ⛵

&#x1f4a1; 作者&#xff1a;韩信子ShowMeAI &#x1f4d8; 机器学习实战系列&#xff1a;https://www.showmeai.tech/tutorials/41 &#x1f4d8; 本文地址&#xff1a;https://www.showmeai.tech/article-detail/395 &#x1f4e2; 声明&#xff1a;版权所有&#xff0c;转…

如何理解相位噪声与时间抖动的关系?

每当介绍相位噪声测试方案时&#xff0c;都会提到时间抖动&#xff0c;经常提到二者都是表征信号短期频率稳定度的参数&#xff0c;而且是频域和时域相对应的参数。正如题目所示&#xff0c;相位噪声与时间抖动有着一定的关系&#xff0c;那么相噪是与哪种类型的抖动相对应&…

葡萄糖-聚乙二醇-醛基/羟基 Glucose-PEG-CHO/OH

葡萄糖-聚乙二醇-醛基Glucose-PEG-CHO 羰基中的一个共价键跟氢原子相连而组成的一价原子团&#xff0c;叫做醛基&#xff0c;醛基结构简式是-CHO&#xff0c;醛基是亲水基团&#xff0c;因此有醛基的有机物&#xff08;如乙醛等&#xff09;有一定的水溶性。 葡萄糖-聚乙二醇…

ILRuntime热更的小技巧

文章目录前因后果前因 因为ILRuntime热更项目直接打包出来的DLL不能放置到AssetBundle里面打包所以我看网上的代码都是读取DLL的bytes然后放置到一个text文件里面后缀是bytes public class DefaultPath {public static string ProjectRootPath Environment.CurrentDirectory…

超市便利店微信小程序引流拉新_分享超市便利店微信小程序开发的作用

作为消费主力的年轻人&#xff0c;习惯用手机。习惯在手机下单&#xff0c;享受足不出户的方便快捷。 再加上疫情反复&#xff0c;线上购物&#xff0c;无接触配送显得更安全卫生。 再这样的市场环境下&#xff0c;做一个线上的超市小程序&#xff0c;就能解决很多问题&…

SpringCloud学习笔记(三)

文章目录SpringCloud学习笔记(一)1.Ribbon 概述2.负载均衡3.Ribbon 快速入门3.1 本次调用设计图3.2 项目搭建3.3 创建 provider-1和provider-23.4 编写 provider-1 和 provider-23.5 创建 consumer3.6 编写 consumer 的启动类3.7 编写 consumer 的 ConsumerController3.8 启动测…

83.Django项目中使用验证码

1. 概述 ​ 验证码&#xff08;CAPTCHA&#xff09;是“Completely Automated Public Turing test to tell Computers and Humans Apart”&#xff08;全自动区分计算机和人类的图灵测试&#xff09;的缩写&#xff0c;是一种区分用户是计算机还是人的公共全自动程序。可以防止…