day2_内存区域2垃圾回收算法

news2024/12/28 3:54:39

文章目录

  • 方法区
    • 1.StringTable
    • 2.StringTable的位置
    • 3.StringTable的调优
  • 垃圾回收
    • 1. 判断垃圾
    • 2. 5种引用
    • 3. 垃圾回收算法

方法区

前面提到了方法区中的组成,它的组成主要是:

  • class(例如它的属性,方法等)
  • 常量池(StringTable等)
  • 类加载器
    在jdk 1.8中,方法区的实现是一个元空间,这个元空间是在本地内存中,同样是包括上面的3种信息,只是这时候的StringTable是在堆中的。
    而在jdk 1.6中,方法区的实现是永久代,这个永久代也是包括上面3种信息的。

方法区出现内存溢出,可能就会抛出错误,如果是在jdk 1.8中,抛出的是OutOfMemoryError: Metaspace,如果是jdk 1.6中,那么抛出的是OutOfMemoryError: PerGon space

1.StringTable

首先说一下StringTable所具有的特点:

  • 具有延迟特性,也就是说这个字符串只有在使用的时候,如果发现这个字符串没有在StringTable中,那么就会生成对应的字符串对象,并放入到StringTable中。如果已经在StringTable中了,那么就不需要生成对象放入到StringTable中
  • 字符串常量利用+进行拼接的时候,实现的原理首先经过编译器的优化,然后判断拼接好的字符串是否有StringTable中了,如果没有,那么就会生成对应的字符串对象,并放入到StringTable中,否则,就是从StringTable中取出的。
    如下面的代码所示:
public class Demo1 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab"; 
        String s4 = "a" + "b";
        System.out.println(s3 == s4); //true(都是串池中的字符串对象)
    }
}

通过命令javap -v Demo1.class反编译(要求要在正确的路径中),然后可以看到下面的信息:
在这里插入图片描述

  • 字符串变量利用+进行拼接的时候,实现的原理是通过生成一个StringBuilder对象,调用append方法来进行字符串拼接的,最后再调用toString方法返回一个字符串对象的,而在toString方法的内部,是通过new创建的字符串对象。所以最后返回的字符串对象是在堆中的
    如下面的代码所示:
public class Demo1 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2; 
        System.out.println(s3 == s4); //false
    }
}

通过javap -v Demo1.class反编译之后,如下图所示:
在这里插入图片描述
我们可以尝试将上面的代码,将s3和s4的顺序调换,即先执行String s4 = s1 + s2;,再执行String s3 = "ab",判断是否输出的是true还是false,如果是true,说明字符串变量拼接之后,赋值给s4的是串池中的字符串对象,否则是堆中的字符串对象。运行完毕之后,发现结果为false,证明字符串变量拼接之后的,最后返回的是堆中的字符串对象

  • 可以尝试调用intern方法,来将字符串放入到StringTable中,然后将StringTable中的对应的串对象返回。但是在不同版本中的jdk中,实现的原理是不同的
    如果是在jdk 1.8中,字符串调用intern方法的时候,如果发现这个字符串已经在StringTable中了,那么就会直接从串池中取出对应的字符串对象返回,否则,如果不在StringTable中,那么就会将这个字符串放入到StringTable中,然后再返回StringTable中的字符串对象
public class Demo1 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s4 = s1 + s2;
        //在jdk 1.8中,通过调用intern方法,尝试将字符串对象添加到串池中
        //串池中已经有这个字符串对象对应的字符串了,那么就不会添加到串池中,否则
        //就会添加到串池中,并将串池中的字符串对象返回
        s4.intern();
        String s3 = "ab";
        System.out.println(s3 == s4); //true
    }
}

在上面的代码中,最后的结果输出为true。因为先执行s4.intern(),会尝试将s4这个字符串添加到串池中,如果串池中没有"ab"这个字符串就添加,此时的确没有,所以就将s4添加到串池中,然后执行s3 = "ab"代码,发现串池中有"ab"这个字符串,所以直接从串池中取出,此时s3同样是指向的是s4对应的字符串对象,所以结果返回true.

如果我们将代码s4.intern()String s3 = "ab"调换一下顺序,最后的结果就变成了false.这是因为s3 = "ab"会创建对应的字符串对象保存在串池中,然后再执行s4.intern(),发现串池中已经有"ab"这个字符串了,就不会将s4这个字符串对象添加到串池中,而s4是堆中的字符串对象,而s3是串池中的,所以就不相同,因此返回的是false。
在这里插入图片描述
如果是在jdk 1.6中,字符串调用intern方法的时候,如果发现这个字符串已经在StringTable中了,那么就会直接从串池中取出对应的字符串对象返回,否则如果不在StringTable中,那么就会将这个字符串复制一份,然后将复制好的对象放入到StringTable中,再返回StringTable中的对象
在这里插入图片描述

2.StringTable的位置

上面说到了,在jdk 1.6中,StringTable是保存在永久代中,而在jdk 1.8中,StringTable是保存在堆中的。我们可以通过验证最后抛出的异常,从而得知StringTable所在的位置。如果抛出的是OutOfMemoryError: ParGon space,说明在jdk 1.6中StringTable是在永久代中,而在jdk 1.8中,抛出的是OutOfMemoryError: heap space,那么是在堆中的。

3.StringTable的调优

由于StringTable底层是哈希表,所以只要我们将这个结构的初始容量设计的合理,从而可以让字符串更加分散,减少发生碰撞的次数。同时我们也可以通过调用interrn方法,从而避免有相同内容的字符串变量加入到StringTable中,从而造成溢出。

垃圾回收

1. 判断垃圾

要判断哪些对象是垃圾,然后回收这些对象,主要有以下几种方式:

  • 引用计数法
    所谓的引用计数法,就是说每一个对象都有一个引用计数器,如果这个对象被其他的对象引用了,那么引用计数器就会+1,同理,如果这个对象被其他的对象释放,那么就会将他的引用计数器的值-1.当这个对象的引用计数器的值变成了0,那么就可以认为是一个垃圾,是需要被回收的
    但是引用计数器会存在循环依赖的问题,就是说,如果A对象引用了B,而B对象也引用了A,此时两者的引用计数器的值都为1,这时候2者都没有办法被释放,所以并不建议使用引用计数器来判断哪些对象是垃圾,是需要被回收的。

  • 可达性分析法
    所谓的可达性分析法,就是说从GC ROOT开始出发,沿着某一个路径,可以访问到的某一些节点,那么这些路径就称为引用链。如果某一个对象和GC ROOT之间没有引用链,那么这个对象就可以认为是一个垃圾,是需要被回收的

    GC Roots的对象分为以下几种:

    虚拟机栈中的引用对象,入线程调用方法堆栈的参数、局部变量、临时变量等。
    ②在方法区中类静态属性引用的对象。如Java类的引用类型静态变量。
    ③在方法区中常量引用对象,如字符串常量池的引用。
    ④在本地方法栈中的JNI(Native方法)引用的对象
    ⑥Java虚拟机内部的引用,如基本类型对应的Class对象,一些常驻异常对象(NullPointException)等,还有系统类加载器。
    所有被同步锁(synchronize关键字)持有的对象
    ⑧反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地缓存代码等。

  • 三色标记法
    所谓的三色标记法,就是说用三种颜色(灰,黑,白)来标记不同的对象。

    • 白色来标记垃圾对象
    • 灰色表示GC ROOT可以到达的这个节点,但是这个节点内部的引用对象还没有被GC ROOT扫描过
    • 黑色表示GC ROOT可以到达这个节点,并且内部的引用对象已经被GC ROOT扫描过了

    但是使用三色标记法会存在多标和漏标的情况,例如下面的图示:
    出现多标的情况:在将D引用的所有内部节点都扫描了之后,这时候D节点就被标记为黑色,而它的内部引用E就标记为灰色,此时如果将D中的内部引用E设置为null,那么本来应该是要回收E这个节点的,但是由于我们已经将E标记为灰色,那么就不会回收E,同时会去扫描E中的内部引用,此时就会将F,G标记为灰色,然后E就标记为黑色,那么多标了E,F,G这3个对象(本应该被回收,但是实际没有被回收的对象就称为浮动垃圾)
    在这里插入图片描述
    出现漏标的情况:在已经将中内部的所有引用对象都扫描了之后,此时会将D标记为黑色,之后即使重新回到D这个节点,由于标记成了黑色,所以不会再扫描其内部引用了,所以这时候我们在添加D的内部引用G,此时G就不会被标记了,从而出现了漏标的情况。
    在这里插入图片描述

2. 5种引用

JVM中,主要有5种引用:

  • 强引用: 我们平常使用的大多是强引用对象,例如Object obj = new Object(),这就是一个强引用。当所有的GC ROOT都没有强引用这个对象的时候,那么这个对象就会被回收

  • 软引用:这里主要是利用SoftReference引用对象,如下所示:
    在这里插入图片描述
    软引用的特点是:如果弱引用指向的对象已经没有被GC ROOT强引用了,那么在进行垃圾回收的时候,如果发现内存依旧是充足的,那么不会回收这个软引用对象,但是如果内存不足,就会将这个软引用对象回收
    同时软引用还可以配合引用队列来使用,因为我们将软引用对象A1回收之后,SoftReference这个软引用依旧是占用着内存,所以将其放入到引用队列中,然后通过遍历这个引用队列,之后将这个SoftReference引用所占用的内存释放

  • 弱引用:通过WeakReference引用指向对象,其实和软引用差不多,只是弱引用在进行垃圾回收的时候,不管内存是否充足,都会将这个弱引用对象回收,而软引用则是只有在内存不足的时候,才会将这个软引用对象回收
    同样的,弱引用也可以配合使用引用队列,通过使用引用队列,来将弱引用所占用的内存释放
    至于哪里使用到了弱引用,我们可以来看一下ThreadLocal中的静态内部类ThreadLocalMap,这个ThreadLocalMap继承了WeakReference,然后在构造ThreadLocalMap的时候,会用ThreadLocal对象作为参数传入,然后就会调用super(threadLocal),从而使得ThreadLocal作为ThreadLocalMap的key,并且这个key是一个弱引用

  • 虚引用:需要配合引用队列使用的。我们将这个虚引用添加到引用队列中,然后ReferenceHandler线程会遍历整个引用队列,发现存在虚引用,就会调用相应的方法,在方法的内部通过Unsafe这个类调用freeMemory方法来释放内存。例如直接内存,就是通过虚引用对象Cleaner调用clean方法来释放内存的,而clean方法的内部,是通过Unsafe调用freeMemory方法释放内存。

  • 终结器引用:同样需要配合引用队列使用的。第一次GC的时候,并没有将对应的对象回收,而是将这个终结器引用添加到引用队列中,然后有一个优先级较低的Finalize线程遍历这个引用队列,然后发现存在终结器引用,就会找到这个终结器引用所指向的对象,通过这个对象调用finalize来释放内存,第二次GC才会将这个对象释放

3. 垃圾回收算法

常见的垃圾回收算法主要有以下几种:

  • 标记-清除法
    所谓的标记清除法,就是说,第一次将从GC ROOT开始搜索,然后将活着的对象做个标记,第二次再从GC ROOT开始往下搜索,将没有标记的对象回收
    这样的好处是效率高,只需要将没有标记的对象回收,但是缺点就会导致出现大量的内存碎片。

  • 复制法
    所谓的标记复制法,就是将整个内存一分为二,然后每次添加对象的时候,只是添加到同一个内存块中,另一个内存块一直是空着的,当发现使用的内存块不足的时候,就会将活着的对象复制到另一个空着的内存中,然后当前使用的这一块内存全部清空。然后交换这2个内存块的指向。如下所示:
    在这里插入图片描述
    复制法的好处就是不会产生内存碎片,但是它的内存利用率低,因为只能使用的一半的内存,同时需要涉及到对象的复制。

  • 标记-整理法
    所谓的标记整理法,就是第一次从GC ROOT开始往下搜索,将活着的对象往内存的另一侧压缩,然后将最后一个压缩对象后面的内存清空即可。如下所示:
    在这里插入图片描述
    使用标记整理法好处就是不会像标记清除法那样产生大量的内存碎片,但是由于需要将对象压倒另一侧,此时就会涉及到对象的地址发生改变等问题,所以效率较低。

所以在jvm中,采用的是分代回收来进行垃圾回收:
所谓的分代,就是说,将整个内存区域划分为新生代和老年代。而在新生代中,还可以划分为Eden, from, to(比例默认是8:1:1)。对于不同的年代,采用的回收算法是不同的,新生代采用的是复制法,而对于老年代则采用的是标记清除或者标记整理算法
在这里插入图片描述
新生代采用的是复制法,首先会尝试将对象添加到Eden中,但是如果发现Eden的内存不足,那么就会触发Minor GC,此时就会将Eden中活着的对象复制到from中,并且将这些活着的对象的寿命 + 1,然后将Eden中的内存清空,再将对象添加到Eden中。重复上面的操作,如果发现from的内存区域也不足,那么同样会将from中活着的对象复制到to区域,然后调整from,to的指向,从而让to保证是空的。同样的,当活着的对象从from复制到to的时候,也需要将活着的对象寿命 + 1.
之所以要将活着的对象寿命 + 1,是因为一旦它的寿命超过了某一个阈值,那么就会将这个对象晋升为老年代,需要将其放到老年代中

值得一提的是,Minor GC会引起STW(stop the world),也即是进行垃圾回收的时候,会暂定其他所有线程继续运行,直到垃圾回收结束之后,才能够继续运行。因为新生代垃圾回收算法采用的是复制法,那么就会涉及到对象的地址变化问题,此时如果没有STW,那么就可能引起数据的混乱(线程在使用对象A,但是垃圾回收,需要将对象A复制到from中,垃圾回收之后,此时地址的指向不同了,就造成数据的混乱)。

同时,如果要添加的对象占用的内存太大了,新生代中无法分配内存给这个对象,那么这个对象直接晋升为老年代,也即直接分配到了老年代这个区域中

老年代则采用的是标记清除法或者标记整理法。一旦发现老年代的内存也不足了,那么首先尝试进行一次Minor GC,进行之后,内存还是不足,就会进行Full GC操作。Full GC操作同样会引起STW的

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

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

相关文章

【python学习】基础篇-文件与系统-文件信息获取与目录操作

python内置文件高级操作函数 删除文件 Python 没有内置删除文件的函数,但是在内置的 os 模块中提供了删除文件的 remove()函数,语法格式如下: os.remove(path) 其中,path 为要删除的文件路径,可以使用相对路径,也可以…

P1058 [NOIP2008 普及组] 立体图

题目描述 小渊是个聪明的孩子,他经常会给周围的小朋友们讲些自己认为有趣的内容。最近,他准备给小朋友们讲解立体图,请你帮他画出立体图。 小渊有一块面积为 ��mn 的矩形区域,上面有 �&#x…

number类型超出16位的问题(前端、后端处理)

目录 1、前端解决方案 1.1 甩链接 1.2 接口返回数据过程中将数据处理为字符串(过过嘴瘾) 1.3 对返回的json字符串进行数据预处理代码如下 2、后端解决方案 2.1 toString、String、 、new String() 自己悟、就是要改的地方多。 2.2拦截器 (可能超出…

为什么越来越多的企业选择云计算?

一、前言 1.当下企业信息化的痛点 企业信息化,这也算是一个老生常谈的话题了,整个中国业内前前后后应该喊了有十多年了。不过到目前为止,我国很多企业公司都还没真正形成一个完整的信息化框架,或者只是运用了一个简单财务或客户…

Vue3组件通信 含有详细的步骤和解释

提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、父传子 defineProps1.使用运行时声明2.使用针对类型的声明 二、子传父 defineEmits1.使用运行时声明2.使用针对类型的声明 总结 前言 Vue2的父子组件通信是…

PMP课堂模拟题目及解析(第4期)

31. 首席执行官要求项目经理提供绩效报告。项目经理应该在这份报告中包含哪些内容? A. 已完成百分比和经验教训 B. 问题的当前状态以及更新后的干系人参与评估矩阵 C. 项目风险的绩效测量指标 D. 已完成的工作和关键绩效指标 32. 在一个项目的最终收尾活动期…

九联UNT413A-S905L3A-免拆U盘卡刷固件包-当贝纯净桌面-内有教程

九联UNT413A-S905L3A-免拆U盘卡刷固件包-当贝纯净桌面-内有教程 特点: 1、适用于对应型号的电视盒子刷机; 2、开放原厂固件屏蔽的市场安装和u盘安装apk; 3、修改dns,三网通用; 4、大量精简内置的没用的软件&#…

迪赛智慧数——饼图(玫瑰饼图):菜品味道受欢迎程度

效果图 大家最爱吃的竟是它!咸鲜占比高达23.53%! 民以食为天,你最喜欢的美食口味是什么呢? 好吃的太多,你应该很难确切地评出你心中的第一名吧。据数据调查显示,咸鲜口味最受欢迎,其次是麻辣、…

SUNTANS模型学习(9)——学习Tidal forcing算例

学习Tidal forcing算例 简介网格配置与地形定解条件设置初始条件设置边界条件设置开边界处的通量计算(OpenBoundaryFluxes)开边处的速度、水位(BoundaryVelocities) 其它参数配置模拟结果 简介 SUNTANS中 tidal forcing 算例的全…

数据脱敏的几种方案

文章目录 什么是数据脱敏?数据脱敏在生活中的应用静态脱敏与动态脱敏数据脱敏的几种方案sql数据脱敏java代码实现脱敏mybatis-mate实现脱敏springCloud网关拦截响应体实现脱敏openGauss 动态数据脱敏解决方案 什么是数据脱敏? 数据脱敏也叫数据的去隐私…

5款办公必备的好软件,你值得拥有

随着网络信息技术的发展,越来越多的人在办公时需要用到电脑了。如果你想提高办公效率,那么就少不了工具的帮忙,今天给大家分享5款办公必备的好软件。 1.文件管理工具——TagSpaces TagSpaces 是一款开源的文件管理工具,它可以通过标签来组织…

测试20K要什么水平?25岁测试工程师成功斩下offer(附面试题)

年少不懂面试经,读懂已是测试人。 大家好,我是一名历经沧桑,看透互联网行业百态的测试从业者,经过数年的勤学苦练,精钻深研究,终于从初出茅庐的职场新手成长为现在的测试老鸟,早已看透了面试官…

@Test单测方法和main方法的区别

区别1:常量池的符号引用 有所不同 今天在下面链接中 学习并测试 string在不同创建方式下 产生几个对象的问题 流程图详解 new String(“abc“) 创建了几个字符串对象_"new string(\"abc\")创建了几个对象"_程序员囧辉的博客-CSDN博客 看下面例子(取自上…

家居家具行业外贸软件解决方案

家具行业,属于装饰行业范畴,如果出售是零售行业,如果生产属于加工行业,如果是带加工,也可以叫二次加工行业。家居行业,泛指家具、床上用品、厨卫用具、室内配饰及日常生活需要的商品,属于消费产…

Ubuntu 桌面版无网络标识

Ubuntu 配置网络的方式有两种: 通过桌面网络标识直接配置通过编辑配置文件配置(很麻烦,而且不方便) 因此,下面介绍如何恢复桌面的网络标识,以便于后续的网络配置。 目录 1、修改配置文件 2、删除原本的…

【腾讯云 FinOps Crane 集训营】Crane平台介绍与实践

🍁 作者:知识浅谈,CSDN博客专家,阿里云签约博主,InfoQ签约博主,华为云云享专家,51CTO明日之星 📌 擅长领域:全栈工程师、爬虫、ACM算法 🤞Crane平台介绍与实践…

腾讯云轻量4核8G12M服务器带宽CPU流量系统盘测评

腾讯云轻量应用服务器4核8G12M带宽,12M公网带宽,下载速度峰值为1536KB/s,即1.5M/秒,2000GB月流量,折合每天66GB流量,系统盘为180GB SSD盘,腾讯云百科来详细说下轻量4核8G12M配置服务器CPU计算性…

【SpringCloud】Eurake注册中心与Ribbon负载均衡原理

目录 一、服务提供者与服务消费者 二、远程调用存在的问题 三、Eureka原理 四、Eureka架构 五、搭建Eureka服务 六、Eureka服务注册 七、Eureka服务发现 八、Ribbon负载均衡流程 九、Ribbon负载均衡源码 十、Ribbon负载均衡策略 十一、Ribbon饥饿加载 一、服务提供者…

【Linux】进程间通信 —— 管道

文章目录 📕 进程间通信介绍📕 匿名管道原理使用读写规则特点 📕 命名管道原理使用匿名管道和命名管道的区别 📕 进程间通信介绍 进程间通信,顾名思义,就是两个进程之间的 “交流” ,我们知道&…