Java方法中不使用的对象应该手动赋值为NULL吗?

在java方法中,不使用的对象是否应该手动赋值为null?我们先来通过一个示例看一下。

垃圾回收示例一

public class GuoGuoTest {
    public static void main(String[] args) {
        byte[] placeholder = new byte[64 * 1024 * 1024];
        System.gc();
    }
}

上面代码向内存填充了64MB的数据,然后通知虚拟机进行垃圾回收。我们在运行代码启动的时候,加上参数 “-verbose:gc” ,观察一下虚拟机垃圾回收的情况。

运行完代码之后,发现64MB内存并没有被回收。这个结果很正常,因为System.gc()执行的时候placeholder还处于作用域范围以内,虚拟机自然不会回收它。

垃圾回收示例二

现在我们将示例一的代码稍作修改,给placeholder用花括号加了一个作用域。在代码执行之前我们可以猜测一下,现在placeholder和System.gc()不处于一个作用域范围,placeholder不会再被访问,所以当执行System.gc()时,placeholder应当被虚拟机认作可以回收的变量。

public class GuoGuoTest {
    public static void main(String[] args) {
        {
            byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        System.gc();
    }
}

执行结果如下图,可以看到结果出乎我们的预料,placeholder并没有被回收,这是什么原因呢?

垃圾回收示例三

在解释原因之前,我们可以将上面代码再次修改,加入一行代码 int guoguo = 0。这次运行代码之后,placeholder会被垃圾回收吗?

public class GuoGuoTest {
    public static void main(String[] args) {
        {
            byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        int guoguo = 0;
        System.gc();
    }
}

增加的这一行代码看起来很无厘头,但程序运行的结果居然是内存这次被回收了!

栈帧

想要知道上述现象的原因,就要从栈帧结构说起。栈帧(Stack Frame)是虚拟机用于方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧的结构包括:

  • 方法的局部变量表
  • 操作数栈
  • 动态连接
  • 方法返回地址

在编译程序代码时,需要多大的局部变量表,多深的操作数栈,是由Class文件结构中方法表的Code属性决定的,也就是在程序运行之前就已经约定好了。程序运行期间变量数据的大小并不会影响栈帧的内存分配,而取决于虚拟机的具体实现。

从逻辑概念上看,栈帧结构如图所示。在活动线程中,只有顶端的栈帧才是有效的,叫做当前栈帧(Current Stack Frame),与当前栈帧相关联的方法叫做当前方法(Current Method),每一个方法在从调用开始到结束的过程,就对应着栈帧在虚拟机栈里从入栈到出栈的过程。虚拟机的执行引擎执行的所有字节码指令都只针对当前栈帧进行操作。

局部变量表

局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。局部变量表容量的最小单元是变量槽(Variable Slot,简称Slot)。虚拟机规范并没有明确指明Slot占用的内存空间大小,只是向导性的表示应能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据,即32位及其以下的数据都可以存放。returnAddress类型现在已经很少见了。reference类型表示对一个对象实例的引用,虚拟机规范没有说明它的长度和它的结构,但虚拟机的实现至少应满足两点:

  • 从此引用中直接或间接地查找到对象在Java堆中的数据存放的起始地址索引
  • 从此引用中直接或间接地查找到对象所属数据类型在方法区中的存储的类型信息

对于64位的数据类型,虚拟机将用高位对齐的方式为其分配两个连续的Slot空间。64位的数据类型只有long和double两种,reference可能是32位也可能是64位。虽然long和double数据类型的一次读写会被分割称两次32位的读写,这样就有可能造成非原子性的数据安全问题。但是由于局部变量表是线程的堆栈元素,是线程私有的数据,所以读写两个连续的Slot无论是否原子操作,都不会造成数据安全问题。

虚拟机使用局部变量表通过索引定位,索引范围从0开始到Slot最大数量结束。对于32位数据类型的变量,索引n代表使用第n个Slot;对于64位数据类型的变量,则会使用第n和n+1个Slot。虚拟机规范不允许单独访问其中某一个。

如果虚拟机执行的是实例方法,而非static方法,局部变量表中索引为0的Slot默认用来传递方法所属实例对象的引用,在方法中用关键字“this”来访问这个隐含参数。其余参数按照参数表的顺序,从1开始占用其他Slot。

原理

以上对于虚拟机运行时数据区的栈帧和局部变量表做了简单介绍之后,我们回头再来看看本文一开始讲到的垃圾回收问题。示例三种加了一行 int guoguo = 0 的代码之后,就能正确回收placeholder变量了,这是什么原因呢?

在公布真相之前,我们首先要了解到一个事实,那就是局部变量表里的Slot是可以重用的。这么做的目的是为了节省更多的栈帧空间。在同一个方法体中,某个变量不可能覆盖整个方法。例如示例三种GuoGuoTest的main方法,placeholder变量被花括号包裹之后的作用域只限于花括号里面。此时,当字节码PC计数器的值已经超出placeholder的作用域时,那么placeholder对应的Slot就应该释放出来交由其他变量使用。

public class GuoGuoTest {
    public static void main(String[] args) {
        {
            byte[] placeholder = new byte[64 * 1024 * 1024];
        }
        int guoguo = 0;
        System.gc();
    }
}

placeholder能否被回收的根本原因是:局部变量表中的Slot是否还存有关于placeholder数组对象的引用。示例二中,当还没有 int guoguo = 0 这行代码的时候,代码虽然已经离开了placeholder变量的作用域,但之后没有对局部变量表的任何其他读写操作,placeholder占用的Slot也就不会被其他变量所复用,所以作为GC Roots一部分的局部变量表仍然保持着对它的关联。在绝大多数时候,这种情况造成的影响非常小。但是如果后面的代码非常耗时,而前面又定义了大量占用内存又实际不再使用的变量,那么手动将其设为null就变得非常具有意义。当然,这种情况非常罕见,一般我们也没有必要所有的变量都手动设为null,并且代码在经过JIT编译之后会将赋null值的操作给消除掉,所以从编码的角度来说,最优雅的解决方式还是通过变量的作用域来控制变量回收的时间。

局部变量初始化

文章最后,再写一点关于局部变量的小知识。类变量有两次赋初值的过程,准备阶段赋予系统初始值;初始化阶段赋予程序员定义的初始值。例如,int类型的类变量会首先被赋予系统初始值0,如果程序员的代码没有显式给其赋值,那么也没有关系,类变量仍然有一个确定的系统初始值。但是局部变量则不同,如果一个局部变量只声明没有初始化,编译器是会报错的,即使编译器不提示错误直接手动生成字节码,字节码校验的时候也会被虚拟机发现而类加载失败。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Radiology 谈人工智能在放射学领域的10个预测方向 [文献阅读]

人工智能(AI)和信息学正在改变放射学。十年前,没有哪个专家会预测到今天放射人工智能行业的蓬勃发展,100多家人工智能公司和近400种放射人工智能算法得到了美国食品和药物管理局(FDA)的批准。 不到一年前,即使是最精明的预言家也不会相信这些…

kubernetes资源管理

资源管理 资源管理介绍 在kubernetes中,所有的内容都抽象为资源,用户需要通过操作资源来管理kubernetes。 kubernetes的本质上就是一个集群系统,用户可以在集群中部署各种服务,所谓的部署服务,其实就是在kubernetes集…

Java设计模式-结构型模式-适配器模式

适配器模式 适配器模式应用场景案例类适配器模式对象适配器模式接口适配器模式适配器模式在源码中的使用 适配器模式 如图:国外插座标准和国内不同,要使用国内的充电器,就需要转接插头,转接插头就是起到适配器的作用 适配器模式&…

unity使用vs进行c#代码提示,查看F12unity元代码

unity关联vs 在vs中让cs.meta显示,鼠标右键,包含在内 提示GameObject类了 感谢下面这位的提示https://zhuanlan.zhihu.com/p/551119106

esp-01刷固件/下载软件到内部单片机的方法

此文章为转载,非原创 一、准备 需要准备三个东西: 1.esp模块。ESP-01 和 ESP-01s 的引脚及 flash 容量基本完全兼容,只是内部硬件设计粗糙与否的区别,所以理论上都适用。 2.官方固件。此部分可以从安信可官方教程中下载&#xff0…

腾讯待办停止运营怎么办?导出的ics文件数据怎么打开查看

待办提醒类工具是日常办公及生活中必不可少的工具,使用待办提醒类工具可以记录很多容易忘记的事情,其可以帮助大家轻松管理各项事务和提高办事的效率。而随着工作的不断变动,大家选择待办提醒类工具也会不断的发生改变。 比如就拿我自己的使…

【算法总结】归并排序专题(刷题有感)

思考 一定要注意归并排序的含义,思考归并的意义。 主要分为两个步骤: 拆分 每次对半分(mid l r >> 1)输入:raw整块,输出:raw左块 raw右块 合并 每次都要对raw左块、 raw右块按照某种规则进行合并输入&#xf…

2023.11.15-hivesql之炸裂函数explode练习

把一个容器的多个数据炸裂出单独展示: explode(容器) 需求:将NBA总冠军球队数据使用explode进行拆分&#xff0c;并且根据夺冠年份进行倒序排序。 1.建表 --step1:建表 create table the_nba_championship(team_name string,champion_year array<string> ) row format…

Minio安装

环境 centos8&#xff0c;关闭防火墙 minio-20231101183725版本 参考官网&#xff1a;部署 MinIO&#xff1a;单节点单硬盘 — 适用于 Linux 的 MinIO 对象存储 单例 下载rpm&#xff0c;用中国镜像 wget https://dl.minio.org.cn/server/minio/release/linux-amd64/arch…

C语言判断水仙花数(ZZULIOJ1027:判断水仙花数)

题目描述 春天是鲜花的季节&#xff0c;水仙花就是其中最迷人的代表&#xff0c;数学上有个水仙花数&#xff0c;他是这样定义的&#xff1a;“水仙花数”是指一个三位数&#xff0c;它的各位数字的立方和等于其本身&#xff0c;比如153135333。 现在要求输入一个三位数&#…

数据分析 - 分散性与变异的量度

全距 - 极差 处理变异性 方差度量 数值与均值的距离&#xff0c;也就是数据的差异性 标准差描述&#xff1a;典型值 和 均值的距离的方法&#xff0c;数据与均值的分散情况

windows 安装 Oracle Database 19c

目录 什么是 Oracle 数据库 下载 Oracle 数据库 解压文件 运行安装程序 测试连接 什么是 Oracle 数据库 Oracle数据库是由美国Oracle Corporation&#xff08;甲骨文公司&#xff09;开发和提供的一种关系型数据库管理系统&#xff0c;它是一种强大的关系型数据库管理系统…

用这个平台制作电子杂志,,还能实时分享,太方便啦!

在我们看电子杂志的时候&#xff0c;总会觉得图文效果有点枯燥&#xff0c;如果能做出翻页书效果的电子杂志&#xff0c;还给人一种身临其境的真实翻书感就好了。 其实制作这种翻页电子杂志很简单&#xff0c;不需要下载安装任何软件&#xff0c;只需登录FLBOOK这个平台 &…

OCR文字识别标注小助手

目录 背景 工具界面 操作 1、选择目录 2、更改为正确的信息 3、保存 4、说明 项目 代码 下载 背景 为什么要写这么一个小工具呢&#xff1f;因为要对文字进行标注。 为什么对文字进行标注呢&#xff1f;因为要重新训练识别。 为什么要重新训练识别呢&#xff1f;因…

Linux 小程序-进度条

1.进度条准备知识 1.1回车与换行 以前的键盘会有一个这样的按键 &#xff0c;这个键就是回车与换行。 回车&#xff1a;从当前行回退到当前行的起始位置。 换行&#xff1a;从当前行切换到下一行的该位置。 有了以上的认识我们可以写出一个简单的倒计时代码&#xff1a; 注意&a…

【华为HCIP | 华为数通工程师】IPV4与IPV6 高频题(2)

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

机器学习的线性回归与非线性回归

一元线性回归 回归分析用来建立方程模拟两个或者多个变量之间如何关联 一元线性回归包括一个自变量和一个因变量 如果包含两个以上的自变量&#xff0c;则称为多元线性回归 代价函数&#xff08;损失函数&#xff09; 损失函数的最终目的是为了使得误差平方和最小 用梯度下…

【装包拆包----泛型】

文章目录 装箱和拆箱泛型创建一个泛型数组泛型的上界泛型方法 装箱和拆箱 装箱&#xff1a; 把基本数据类型给到引用数据类型 public static void main(String[] args) {//自动装包//第一种装包Integer c 12;//第二种装包int a 7;Integer b a;//显示装包Integer aa Intege…

大语言模型|人工智能领域中备受关注的技术

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️其他领域】 文章目录 前言关于大语言模型大语言模型是什么&#xff1f;大语言模型有什么用?文案写作知识库回答文本分类代码生成 AWS 如何通过 LLM 提供帮助&#xff1f;Amazon BedrockAmazon SageM…

QT day3作业

1.思维导图 2、 完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密…