JVM之内存与垃圾回收篇3

news2024/11/25 10:28:40

文章目录

  • 8 垃圾回收
    • 8.1 基本理论
      • 8.1.1 对象的finalization机制
      • 8.1.2 理解System.gc
      • 8.1.3 内存溢出和内存泄漏
      • 8.1.4 Stop The World
      • 8.1.5 安全点和安全区域
      • 8.1.6 Java中的引用
    • 8.2 垃圾回收算法
      • 8.2.1 引用计数法
      • 8.2.2 可达性分析
        • 8.2.2.1 使用MAT查看GC Roots
        • 8.2.2.2 使用JProfiler分析OOM
      • 8.2.3 复制算法
      • 8.2.4 标记清除
      • 8.2.5 标记压缩
      • 8.2.6 标记清除压缩
      • 8.2.7 小结
      • 8.2.8 增量收集算法
      • 8.2.9 分区算法
    • 8.3 垃圾回收器
      • 8.3.1 评估垃圾回收器的性能指标
      • 8.3.2 垃圾回收器间的组合关系
      • 8.3.3 Serial和Serial Old垃圾回收器—— 串行回收
      • 8.3.4 ParNew—— 并行回收
      • 8.3.5 Parrallel回收器—— 吞吐量优先

8 垃圾回收

8.1 基本理论

【什么是垃圾】
垃圾是指在运行程序中没有任何指针指向的对象。

8.1.1 对象的finalization机制

提供finalization机制来允许开发人员自定义对象销毁前的处理逻辑。 Object中定义了finalize方法,可以被覆写。

永远不要主动调用对象的finalize方法,这个应该交由垃圾收集器调用。 理由如下:
(1)在finalize时可能会导致对象复活。
(2)finalize的执行时刻是没有保障的,完全由GC线程决定。 极端情况下,如果不发生GC,那么finalize方法永远不会执行。
(3)一个糟糕的finalize方法严重影响GC的性能。

虚拟机中的对象存在三种状态:
① 可触及的:从根节点开始,可以到达这个对象
② 可复活的:对象的所有引用都被释放,但是对象有可能在finalize中复活。
③ 不可触及的:对象的finalize被调用,并且没有复活,那么就会进入不可触及状态。 不可触及的对象不可能被复活,因为finalize只会被调用一次。

判定一个对象objA是否可以回收,至少要经历两次标记过程:

  1. 如果对象objA到GC Roots没有引用链,则进行第一次标记。
  2. 进行筛选,判断此对象是否有必要执行finalize方法。
    ① 如果对象objA没有重写finalize方法,或者finalize方法已经被调用过,则虚拟机视为“没有必要执行”,objA被判定为不可触及的。
    ② 如果对象objA重写了finalize方法,并且还未执行,那么objA会被插入到F-Queue队列中,由一个虚拟机自动创建的、低优先级的Finalizer线程触发其finalize方法。
    ③ finalize方法是对象逃脱死亡的最后机会,稍后GC会对F-Queue中的对象进行二次标记。如果objA在finalize方法中与引用链上的任何一个对象建立了联系,那么在二次标记时,objA会被移出“即将回收”集合。之后,如果对象又出现了没有引用存在的情况,对象会直接变为不可触及的状态。 finalize方法只会被调用一次。
/**
 * 注释掉了finalize方法的结果
 * first gc
 * obj is dead
 * second gc
 * obj is dead
 * 
 * 没有注释掉finalize方法的执行结果:
 * first gc
 * Call override finalize method.
 * obj is still alive
 * second gc
 * obj is still alive
 */
public class CanReliveObj {
    public static CanReliveObj obj;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Call override finalize method.");
        obj = this;
    }

    public static void main(String[] args) throws InterruptedException {
        obj = new CanReliveObj();
        obj = null;
        System.gc();
        System.out.println("first gc");

        // finalizer线程优先级很低,主线程sleep两秒,等待finalizer执行。
        Thread.sleep(2000);

        if (obj == null) {
            System.out.println("obj is dead");
        } else {
            System.out.println("obj is still alive");
        }

        System.out.println("second gc");

        // finalizer线程优先级很低,主线程sleep两秒,等待finalizer执行。
        Thread.sleep(2000);

        if (obj == null) {
            System.out.println("obj is dead");
        } else {
            System.out.println("obj is still alive");
        }
    }
}

8.1.2 理解System.gc

显示触发full gc,同时对新生代和老年代进行回收。 但是System.gc无法保证对垃圾收集器的调用(无法确保开始执行的时间)。

public class SystemGcTest {
    public static void main(String[] args) {
        new SystemGcTest();
        System.gc();
        System.runFinalization();
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("SystemGcTest has override finalize() method");
    }
}

The java.lang.Runtime.runFinalization() method runs the finalization methods of any objects pending finalization. Calling this method suggests that the Java virtual machine expend effort toward running the finalize methods of objects that have been found to be discarded but whose finalize methods have not yet been run. When control returns from the method call, the virtual machine has made a best effort to complete all outstanding finalizations.

The virtual machine performs the finalization process automatically as needed, in a separate thread, if the runFinalization method is not invoked explicitly. The method System.runFinalization() is the conventional and convenient means of invoking this method.

8.1.3 内存溢出和内存泄漏

【内存溢出】
OOM:没有空闲的内存,并且垃圾收集器也无法提供更多的内存。

【内存泄漏】
内存泄漏:只有对象不会再被程序用到了,但是GC又不能回收他们的情况,称为内存泄漏。
实际情况中,可能会存在一些不好的实现,会导致对象的生命周期变得很长,甚至导致了OOM,这种就叫做广义上的“内存泄漏”。

举例:

  1. 单例模式
    如果单例对象持有外部对象的引用的话,这个外部对象是不能被回收的,会导致内存泄漏产生。

  2. 一些提供close的资源未关闭导致内存泄漏
    数据库连接、网络连接和IO连接必须手动close,否则是不会被回收的。

8.1.4 Stop The World

8.1.5 安全点和安全区域

用户线程只有在特定的位置才能停顿下来GC,这些位置称为“安全点(Safe Point)”。如果安全点太少会导致GC等待的时间太长。如果太多会导致运行时性能问题。通常会选择一些执行时间较长的指令作为safe point,如方法调用、循环跳转和异常跳转。

【如何保证在GC的时候,检查所有线程都跑到最近的安全点上停顿下来】

  1. 抢先式中断
    目前没有虚拟机采用了。
    首先中断所有线程。如果还有线程不在安全点,就恢复线程,让线程跑到安全点。
  2. 主动式中断
    设置一个中断标志,各个线程运行到安全点的时候主动轮询这个标志,如果中断标志为true,则将自己挂起。

线程处于Sleep状态或者Blocked状态,这时候无法响应JVM的中断请求,无法走到安全点。此种情况下,需要靠安全区域来解决这个问题。
安全区是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中任何位置开始GC都是安全的。

【安全区域的运作机制】

  1. 当线程运行到safe region的时候,标识自己已经进入了Safe Region,如果这段时间内发生了GC,JVM会忽略标识为Safe Region状态的线程。
  2. 当线程即将离开Safe Region时,会检查JVM是否已经完成了GC,如果完成了GC,继续运行。否则线程必须等待直到收到可以安全离开Safe Region的信号为止。

8.1.6 Java中的引用

强引用:不回收
软引用:内存不足即回收
弱引用:发现即回收
虚引用:对象回收跟踪
【终结器引用】
终结器引用用来实现对象的finalize方法。
无需手动编码,其内部配合引用队列使用。
在GC时,终结器引用入队。由Finalizer线程通过终结器引用找到被引用对象并调用它的finalize方法,第二次gc的时候才能回收被引用对象。

8.2 垃圾回收算法

在这里插入图片描述

8.2.1 引用计数法

【怎样判断垃圾——引用记数法】
在这里插入图片描述
优点:实现简单,垃圾对象便于识别;判定效率高,回收没有延迟性(只要引用计数是0,随时都可以回收)。

Python使用的是引用计数法。Python解决循环引用的方法:(1)手动接触引用(2)弱引用。

8.2.2 可达性分析

又名根搜索算法,追踪性垃圾收集算法。
在这里插入图片描述

【可以作为GC Roots的对象】

  • 虚拟机栈中引用的对象
    方法的参数,局部变量等。
  • 本地方法栈中引用的对象
  • 方法区中类静态属性引用的对象
    Java应用类型的静态变量
  • 方法区中常量引用的对象
    字符串常量池中的引用
  • 所有被同步锁synchorinized持有的对象
  • Java虚拟机内部的引用
    基本数据类型对应的Class对象,一些常驻的异常对象(如NPE,OOM),系统类加载器。
  • 反应虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

【Stop The World】
可达性分析必须要在一个能保障一致性的快照中进行。 如果不满足的话,分析结果的准确性无法保证。 这就是GC进行时必须“Stop the World”的一个重要原因。 即使几乎不发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。

8.2.2.1 使用MAT查看GC Roots

① 待查看的代码

package org.example.gcroot;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Scanner;

public class GcRootTest {
    public static void main(String[] args) {
        List<Object> numList = new ArrayList<>();
        Date birth = new Date();

        for (int i = 0; i < 100; i++) {
            numList.add(String.valueOf(i));
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        System.out.println("数据添加完毕,请操作");
        new Scanner(System.in).next();
        numList = null;
        birth = null;
        System.out.println("numList, birth已经置空,请操作");
        new Scanner(System.in).next();
        System.out.println("程序结束");
    }
}

② 在运行过程中使用visualvm导出堆dump,在第一次程序阻塞和第二次程序阻塞的时候都dump一次
在这里插入图片描述
③ 使用MAT查看两次导出的dump文件,查看gc roots
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.2.2.2 使用JProfiler分析OOM

① 启用在OOM的时候导出HeapDump
在这里插入图片描述
② 使用JProfiler查看Biggest Object
在这里插入图片描述
③ 查看Thread Dump,查看哪个线程的哪一行有问题。
在这里插入图片描述

8.2.3 复制算法

Copying

【优点】
实现简单,运行高效。
可以保证空间连续性,不会出现“碎片”问题。
【缺点】
需要两倍的内存空间。空间浪费。
G1将内存拆分成很多的region,在复制过程中,GC需要维护region之间对象的引用关系。内存占用和时间开销都是不小的。

如果系统中存活的对象很多,复制算法就不理想。这样会复制很多存活的对象,过犹不及。

8.2.4 标记清除

在这里插入图片描述
在这里插入图片描述
标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。 注意:标记清除算法的标记标记的是可达对象,不是垃圾对象。
清除:Collector对堆内存进行线性遍历,如果发现某个对象的Header中没有可达标记,则将其回收。

缺点:
效率不算高。 两次扫描
在GC的时候需要停止整个应用程序。
存在内存碎片,需要维护空闲列表。

【何为清除?】
清除并不是真的置空,而是把要清除的对象地址保存在空闲的地址列表里面。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果足够就存放。

8.2.5 标记压缩

标记压缩算法又叫标记整理算法。
在这里插入图片描述
【优点】

  • 没有内存碎片
  • 没有复制算法内存减半的高额代价。
    【缺点】
  • 效率上讲,低于复制算法
  • 需要调整引用的地址
  • STW

8.2.6 标记清除压缩

在这里插入图片描述

8.2.7 小结

在这里插入图片描述
在这里插入图片描述
Mark阶段的开销与存活对象的数量成正比。
Sweep阶段的开销与所管辖的区域的大小成正比。
Compact阶段的开销与存活对象的数据成正比。

以HotSpot中的CMS回收器为例,CMS是基于MARK-SWEEP实现的,对于对象的回收效率很高。对于碎片问题,CMS使用基于标记压缩算法的Serial Old收集器进行补偿。 当内存回收不佳的时候,将采用Serial Old执行Full GC以达到对老年代内存的整理。

8.2.8 增量收集算法

优点:
缺点:系统吞吐量的下降。

8.2.9 分区算法

将大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而非整个堆空间。 从而减少一次GC所产生的停顿。

8.3 垃圾回收器

8.3.1 评估垃圾回收器的性能指标

【吞吐量】
运行用户代码时间占总运行时间的比例。
总运行时间 = 程序的运行时间 + 内存回收的时间
【垃圾收集开销】
吞吐量的补数,垃圾收集所用时间占总运行时间的比例。
【暂停时间】
进行垃圾收集时,程序的工作线程被暂停的时间
【收集频率】
收集操作发生的频率
【内存占用】
Java堆区所占内存的大小。
【回收速率】
一个对象从诞生到被回收所经历的时间。

串行回收器:Serial、Serial Old
并行回收器:ParNew、Parallel Scavenge、Parallel Old
并发回收器:CMS、G1

8.3.2 垃圾回收器间的组合关系

新生代收集器:Serial、ParNew、 Parallel Scavenge
老年代收集器:Serial Old、Parallel Old、CMS
整堆收集器:G1
在这里插入图片描述
在这里插入图片描述
【查看当前系统使用Garbage Collector】
在这里插入图片描述
在这里插入图片描述

8.3.3 Serial和Serial Old垃圾回收器—— 串行回收

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.3.4 ParNew—— 并行回收

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.3.5 Parrallel回收器—— 吞吐量优先

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

【docker,typeorm】docker时区与本地时区的不同步【已解决】

前言 我使用账号登陆vuecms.cn网站&#xff0c;查看登陆日志&#xff0c;发现所有时间全部少8个小时。懵逼树上懵逼果&#xff0c;懵逼树下你和我… 我的开源网站后端是基于nestjs&#xff0c;数据库使用typeorm进行连接操作 原因分析&#xff1a; 原因一: docker环境与本地环…

结构型设计模式之适配器模式【设计模式系列】

系列文章目录 C技能系列 Linux通信架构系列 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 设计模式系列 期待你的关注哦&#xff01;&#xff01;&#xff01; 现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everythi…

Type [unknown] not present(主要问题是jar冲突)

解决方案&#xff1a;1选择pom.xml 2鼠标移动到打开的pom.xml点击右键选择maven 显示图 3ctrl鼠标左键移动找到红线 可以看到引入冲突 4按照实际需求对pom.xml的引入进行增删或者版本升级降级&#xff0c;直到以下图标中没有红线冲突即可

SQL注入 三范式

学习目标 了解三范式的要求 1. 什么是范式 设计关系数据库时&#xff0c;遵从不同的规范要求&#xff0c;设计出合理的关系型数据库&#xff0c;这些不同的规范要求被称为不同的范式&#xff0c;各种范式呈递次规范&#xff0c;越高的范式数据库冗余越小。 实际上家用电器都有…

香农极限是如何影响光纤容量的

1 引言 上世纪末&#xff0c;DWDM技术开始在干线通信中使用并迅速普及。虽然当时DWDM系统的容量只有402.5G&#xff0c;但实验室中DWDM支持的波道数甚至超过了1000波&#xff0c;单波道速率也飙到了惊人的160G&#xff08;超1000波和单波160G是两个独立事件&#xff09;。人们普…

docker快速搭建并使用Zabbix

docker搭建并使用Zabbix 0 zabbix基础知识 zabbix-server zabbix 的server 端&#xff0c;负责接收agent发送过来的监控数据&#xff0c;并且提供zabbix的所有核心功能。database 用于存储监控数据和配置信息的数据库&#xff0c;目前常用的有mysql和postgresql两种数据库。za…

【AutoSAR应用软件设计】

AutoSAR总体架构 是本文讲解内容。 接口类型 AUTOSAR接口 –SWCs和/或BSW模块交换的信息–独立于实施/网络/硬件–端口接口 标准化AUTOSAR接口 –AUTOSAR接口–标准化的语法和语义–标准化端口接口 标准化接口 –标准化API–通常为特定编程语言&#xff08;“C”&#x…

火狐浏览器鼠标点击页面区域文字时,出现光标的问题

点击一些资源卡片和查看别的页面时&#xff0c;发现点击非输入框的地方&#xff0c;出现了光标&#xff0c;误以为光标处可以填写东西&#xff0c;就试着敲了几次键盘&#xff0c;发现没有任何反应&#xff1b;然后就叫开发那个页面的同事过去看看&#xff1b;那个同事按F12各种…

如何监控Linux和Oracle数据库运行状态

背景: 在生产环境中,一般可能会发生服务器宕机或者数据库宕机的情况,如何准确的把握找准”生产事故“的具体发生时间,其实有很多方法,可以借助第三方的监控软件或者其他收费软件。 但是本人就是穷逼一个,不可能买或者使用盗版的三方软件。所有设计了以下流程检测Linux和…

16_LinuxINPUT子系统

目录 input子系统简 input驱动编写流程 注册input_dev 上报输入事件 input_event结构体 按键input驱动程序编写 编写测试APP 运行测试 input子系统简 按键、鼠标、键盘、触摸屏等都属于输入(input)设备,Linux内核为此专门做了一个叫做input子系统的框架来处理输入事件。…

node中间件-express框架

文章目录 前置 Express安装1. 基本使用2. 中间件2.1 中间件应用 3. 中间件的注册方式3.1 普通中间件的注册3.2 path匹配中间件3.3 method与路径匹配3.4 案列中间件匹配与执行方法 4. 中间件request数据解析4.1 解析request body中间件4.2 urlencoded解析 5. 第三方中间件5.1 mo…

CHI协议保序之Comp保序

一致性系统中&#xff0c;需要 memroy model 使用 multi-copy atomicity; 一致性系统中&#xff0c;使用三种保序方式&#xff1b; Completion response □ Comp 响应 表示当前的请求&#xff0c;己经达到了 POC/POS, POC/POS 将保证其命令的执行顺序和接收的顺序是一致的&…

Python(三十三)分支结构——嵌套if的使用

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

Android Studio下载

目录 确定版本下载地址 确定版本 如果是入职工作&#xff0c;先和同事确定好版本。因为每个项目使用的gradle插件版本&#xff0c;是在根目录的build.gralde文件中统一定义的&#xff0c;这个文件在添加第三方库&#xff08;例如GreenDao&#xff0c;Arouter等&#xff09;或者…

SpringBoot项目的创建

等待maven下载完成 删除无用文件 此时我们就创建成功了

Shiro权限绕过漏洞(CVE-2020-1957,CVE-2020-11989、CVE-2020-13933)

一、Apache Shiro Apache Shiro 是一个强大且易用的Java安全框架,能够用于身份验证、授权、加密和会话管理。 二、Shiro漏洞指纹&#xff08;部分&#xff09; 1、在请求包的cookie中有remember字段赋任意值 2、返回包中存在set-Cookie&#xff1a;remeberMedeleteMe 三、受…

通过宝塔面板将vue项目放到阿里云服务器(两个vue项目)

前提条件&#xff1a; 端口提前开放所需环境安装好&#xff08;Nginx node…&#xff09;以及打包好的vue文件&#xff08;dist&#xff09; 第一步&#xff1a;上传文件到自定义目录 第二步&#xff1a;在首页找到Nginx软件并配置 第三步&#xff1a;配置具体的配置 这里说…

「雕爷学编程」Arduino动手做(39)——DS18B20温度传感器3

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

厦门枫叶时代看电视的新趋势

随着“5G电视”技术的不断普及和城市数字化转型的加速&#xff0c;电视行业也在发生着翻天覆地的变化。在这个背景下&#xff0c;第28届上海电视节推出了高端论坛板块——“白玉兰对话”&#xff0c;共同探讨电视行业的未来发展。其中备受瞩目的中国电视剧发展论坛&#xff0c;…

荔枝派Zero(全志V3S)驱动开发之USB摄像头

文章目录 前言一、内核配置 UVC二、编译内核并烧录到 SD 卡三、上电测试四、mjpeg-streamer 视频流服务器测试1、jpeg 库的移植2、移植 mjpeg-streamer①、前置软件②、下载 mjpg-streamer 源码③、修改 mjpg-streamer 源码④、编译 mjpg-streamer 源码 3、搬移文件到 nfs 目录…