JVM学习-垃圾回收(三)

System.gc
  • 通过System.gc()或Runtime.getRuntime().gc()的调用,会显示触发Full GC,同时对老年代和方法区进行回收,尝试释放被丢弃对象占用的内存
  • 然后System.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用
  • JVM实现者可以通过System.gc()调用来决定JVM的GC行为,而一般情况下,垃圾回收应该是主动进行的,无须手动触发,否则就太过于麻烦了,如我们在编写一个性能基准测试,会触发System.gc()
public class SystemGCTest {public static void main(String[] args) {new SystemGCTest();System.gc();  //提醒JVM的垃圾回收器执行gc,但是不确定是否马上执行gcSystem.runFinalization();//强制调用失去引用的对象的finalize()方法}@Overrideprotected void finalize() throws Throwable {System.out.println("SystemGCTest 重写finalize方法");}
}
//执行时使用 -XX: +PrintGCDetails参数
public class LocalVarGC {@Testpublic void localvarGC1() {byte[] buffer = new byte[10 * 1024 * 1024];System.gc();}@Testpublic void localvarGC2() {byte[] buffer = new byte[10 * 1024 * 1024];buffer = null;System.gc();}@Testpublic void localvarGC3() {{byte[] buffer = new byte[10 * 1024 * 1024];}System.gc();}@Testpublic void localvarGC4() {{byte[] buffer = new byte[10 * 1024 * 1024];}int value = 10;System.gc();}@Testpublic void localvarGC5() {localvarGC1();System.gc();}
}
内存溢出与内存泄漏
内存溢出(Memory Overflow)
  • 应用程序占用的内存增长速度非常快,造成垃圾回收已经跟不上内存消耗的速度,否则不太容易出现OOM
  • GC会进行各种年龄段的垃圾回收,实在不行了就放大招,来一次独占式的Full GC操作,这时候会回收大量内存,供应用程序继续使用
  • javadoc中对OutOfMemoryError的解释是,没有空闲内存,并且垃圾收集器也无法提供更多内存
    ①Java虚拟机堆内存设置不够
    • 比如可能存在内存泄漏疸,也很有可能是堆的大小不合理,比如我们要处理比较可观的数据量,但是没有显示指定JVM堆大小或者指定数值偏小,可以通过参数-Xms,-Xmx来调整
      ②代码中创建了大量大对象,并且长时间不能被垃圾收集器收集
    • 对于老版本的Oracle JDK,因为永久代的大小有限制,并且JVM对永久代(常量池信息,不再需要的类型)的回收非常不积极,所以当我们不断添加新类型的时候,永久代出现OutOfMemoryError非常常见,尤其在运行时存在大量动态类型生成的场合,类似intern字符串缓存占用太多空间,也会导致OOM,对应的异常信息会标记出来和永久代有关“java.lang.OutOfMemoryError:PermGen space”,随着元数据区的引入,方法区内存已经不再那么窘迫,所以相应的OOM有所改观,出现OOM,异常信息变成java.lang.OutOfMemoryError:Metaspace,直接内存不足,也会导致OOM
内存泄漏(Memory Leak)
  • 只有对象不会再被程序用到了,但是GC又不能回收他们的情况,称为内存泄漏
  • 但实际情况很多时候一些不太好的实践会导致对象的生命周期变得很长,甚至导致OOM,也叫做内存泄漏
  • 尽管内存泄漏不会立刻引发程序崩溃,但一旦发生内存泄漏,程序中的可用我忙得很就会被逐步蚕食,直到耗尽所有内存,最终出现OutOfMemory异常,导致程序崩溃
  • 注:此处存储空间指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小
Stop The World
  • 简称STW,指的是GC事件发生过程中,会产生应用程序的停顿,停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死,这上停顿称为STW
  • 可达性分析算法中枚举根节点(GC Roots)会导致Java执行线程停顿
    • 分析工作必须在一个能确保一致性的快照中进行
    • 一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上
    • 如果出现分析过程中对象引用关系还在不断变化,则分析结果的准备性无法保证
  • 被STW中断的应用程序线程会在完成GC之后恢复,频繁中断会让用户感觉像网速不快造成电影卡带一样,所以我们需要减少STW的发生
  • 所有GC都有STW事件
  • 垃圾回收器越优秀,回收效率越来越高,尽可能缩短了暂停时间
  • STW是JVM在后台自动发起和自动完成的,有用户不可见的情况下,把用户正常的工作线程全部停掉
  • System.gc()会导致Stop-The-World
public class StopTheWorldDemo {public static class PrintThread extends Thread {public final long startTime = System.currentTimeMillis();@Overridepublic void run() {try {while (true) {long t = System.currentTimeMillis() - startTime;System.out.println(t / 1000 + "." + t % 1000);Thread.sleep(1000);}} catch (Exception ex) {ex.printStackTrace();}}}public static class WorkThread extends Thread {List<byte[]> list = new ArrayList<>();@Overridepublic void run() {try {while (true) {for (int i = 0; i < 1000; i++) {byte[] buffer = new byte[1024];list.add(buffer);}if (list.size() > 10000) {list.clear();System.gc();   //触发Full GC,进而出现STW}}} catch (Exception e) {e.printStackTrace();}}}public static void main(String[] args) {PrintThread printThread = new PrintThread();printThread.start();WorkThread workThread = new WorkThread();workThread.start();}
}
垃圾回收的并行与并发
并发(Concurrent)
  • 在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理器上运行
  • 并发不是真正意义上的“同步进行”,只是CPU把一个时间段划分成几个时间片段(时间区间),然后在这几个时间区间之间来回切换,由于CPU处理的速度非常快,只要时间间隔处理得当,即可让用户感觉是多个应用程序同时在进行。
并行(Parallel)
  • 当系统有一个以上CPU时,当一个CPU执行一个线程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,我们称为并行(Parallel)
  • 其实决定并行的因素不是CPU的数量,而是CPU的核心数量,比如一个CPU多个核也可以并行
  • 适合科学计算,后台处理等弱交互场景
垃圾回收并发和并行
  • 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍处理等待状态
  • 如ParNew,Parallel Scavenge、Parallel Old
  • 串行(Serial)
  • 相较于并行的概念,单线程执行
  • 如果内存不够,则程序暂停,启动JVM垃圾回收器进行垃圾回收,回收完,再启动程序的线程
    在这里插入图片描述
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾回收线程在执行时不会停顿用户程序的运行
  • 用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上
  • 如CMS、G1
    在这里插入图片描述
安全点与安全区域
安全点

程序在执行时并非所有地方都能停顿下来开始GC,只有在特定的位置才能停顿下来开始GC,这个位置称为安全点(SafePoint)
Safe Point选择很重要,如果太少可能导致GC等待的时间太长,如果太频繁可能导致运行时的性能问题,大部分指令的执行时间都非常短暂,通常会根据“是否具有让程序长时间执行的特性”为标准,比如:选择一些执行时间较长的指令作为Safe Point,如方法调用、循环跳转和异常跳转等。

  • 如何在GC发生时,检查所有线程都跑到最近的安全点停顿下来
  • 抢先式中断(目前已经没有虚拟机使用了)
    首先中断所有线程,如果还有线程不在安全点,就恢复线程,让线程跑到安全点
  • 主动式中断
    设置一个中断标志,各个线程运行到Safe Point的时候主动轮询这个标志,如果中断标志为真,则将自己进行中断挂起
安全区域

安全区域是指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始GC是安全的,我们也可以把Safe Region看作扩展了的SafePoint。

  • 当线程运行到Safe Region的代码时,首先标识已经进入了Safe Region,如果这段时间发生GC,JVM会忽略标识为Safe Region状态的线程
  • 当线程即将离开Safe Region时,会检查JVM是否已完成GC,如果完成了,则继续运行,否则线程必须等待直到收到可以安全离开Safe Region的信号为止
引用
  • 强引用(StrongReference)
    在程序代码之中普遍存在的引用赋值,即类似Object obj = new Object()这种引用关系,无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用对象
  • 最常见的引用类型是强引用(普通系统99%以上都是强引用),也就是默认的引用类型
  • 在Java语言中,使用new操作符创建一个新的对象,并将其赋值给一个变量的时候,这个变量就成为指向该对象的一个强引用
  • 强引用的对象是可触及的,垃圾收集器永远不会回收掉被引用的对象
  • 对于普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或显式地将相应(强)引用赋值为null,就可以当做垃圾被收集了,当然回收时机还是看垃圾收集策略
  • 相对,软引用、弱引用和虚引用的对象是软可触及,弱可触及,虚可触及,在一定条件下,都可以被回收的,所以强引用是造成Java内存泄漏的主要原因之一
public class StrongReferenceTest {public static void main(String[] args) {StringBuilder sb = new StringBuilder("hello ,kobe");StringBuilder sb1 = sb;sb = null;System.gc();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(sb1);}
}
  • 软引用(SoftReference)
  • 在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中进行二次回收,这次回收后还没有足够内存,才会抛出内存溢出
  • 软引用通常用来实现内存敏感的缓存,比如:高速缓存就用到软引用,如果还有空闲内存,就暂时保留缓存,内存不足时清理掉,这种保证使用缓存的同时,不会耗尽内存
  • 垃圾回收器在某个时刻决定回收软可达的对象的时候,会清理软引用,并可选地把引用存放到一个引用队列(Reference Queue)
  • 类似弱引用,只不过Java虚拟机会尽量让软引用的存活时间长一些,迫不得已才清理
//-Xms10m -Xmx10m <---->执行参数public class SoftReferenceTest {public static class User {public int id;public String name;public User(int id, String name) {this.id = id;this.name = name;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}}public static void main(String[] args) {//创建对象,建立软引用SoftReference<User> userSoftReference = new SoftReference<>(new User(1,"edison"));/*User u1 = new User(1,"edison");SoftReference<User> userSoftReference = new SoftReference<>(u1);u1 = null;    //取消强引用*///从软引用重新获得强引用对象System.out.println(userSoftReference.get());System.gc();System.out.println("After GC:");//垃圾回收之后获得软引用中的对象System.out.println(userSoftReference.get());   //堆内存足够,不会回收软引用可达对象try {//让系统认为内存资源紧张byte[] b = new byte[1024 * 1024 * 7];} catch (Throwable e) {e.printStackTrace();} finally {//再次从软引用中获取数据System.out.println(userSoftReference.get());   //报OOM前,垃圾回收器会回收软引用可达对象}}
}
//输出结果
User{id=1, name='edison'}
After GC:
User{id=1, name='edison'}
null
java.lang.OutOfMemoryError: Java heap spaceat com.chapter08.SoftReferenceTest.main(SoftReferenceTest.java:42)
  • 弱引用(WeakReference)—发现即回收
  • 被弱引用关联的对象只能生存到下一次垃圾收集之前,当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象
  • 由于垃圾回收线程通常优先级很低,并不一定很快地发现持有弱引用的对象。在这种情况下,弱引用对象可以存在较长时间
  • 弱引用和软引用一样,在构造弱引用时,也可以指定一个引用队列,当弱引用对象被回收时,就会加入指定的引用队列,通过这个队列可以跟踪对象的回收情况
  • 软引用、弱引用都适合来保存那些可有可无的缓存数据,如果这么做,当系统内存不足时,这些缓存会被回收,不会导致内存溢出,而当内存资源充足时,这些缓存数据又可以相当长的时间,从而起到加速系统的作用
  • 弱引用对象与软引用对象的最大不同在于,当GC在进行回收时,需要通过算法检查是否回收软引用对象,而对于弱引用对象,GC总是进行回收,弱引用对象更容易、更快被GC回收
public class WeakReferenceTest {public static class User {public int id;public String name;public User(int id, String name) {this.id = id;this.name = name;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}}public static void main(String[] args) {//创建对象,建立软引用WeakReference<User> userWeakReference = new WeakReference<>(new User(1,"edison"));/*User u1 = new User(1,"edison");SoftReference<User> userSoftReference = new SoftReference<>(u1);u1 = null;    //取消强引用*///从软引用重新获得强引用对象System.out.println(userWeakReference.get());System.gc();System.out.println("After GC:");//垃圾回收之后获得软引用中的对象System.out.println(userWeakReference.get());try {byte[] b = new byte[1024 * 1024 * 7];} catch (Throwable e) {e.printStackTrace();} finally {//再次从软引用中获取数据System.out.println(userWeakReference.get());}}
}
//执行结果
User{id=1, name='edison'}
After GC:
null
null
  • 虚引用(PhantomReference)–对象回收跟踪
    一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知
public class PhantomReferenceTest {public static PhantomReferenceTest obj; //当前类对象的声明static ReferenceQueue<PhantomReferenceTest> phantomQueue = null;   //引用队列public static class CheckRefQueue extends Thread {@Overridepublic void run() {while (true) {if (phantomQueue != null) {PhantomReference<PhantomReferenceTest> objt = null;try {objt = (PhantomReference<PhantomReferenceTest>) phantomQueue.remove();} catch (InterruptedException e) {e.printStackTrace();}if (objt != null) {System.out.println("追踪垃圾回收过程:PhantomReferenceTest实例被GC了");}}}}}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("调用当前类的finalize()方法");obj = this;}public static void main(String[] args) {Thread t = new CheckRefQueue();t.setDaemon(true);   //设置为守护线程t.start();phantomQueue = new ReferenceQueue<>();obj = new PhantomReferenceTest();//构造了PhantomReferenceTest对象的虚引用,并指定了引用队列PhantomReference<PhantomReferenceTest> phantomRef = new PhantomReference<>(obj,phantomQueue);try {//不可获取虚引用的对象System.out.println(phantomRef.get());//将强引用去除obj = null;//第一次gcSystem.gc();Thread.sleep(1000);if (obj == null) {System.out.println("obj 是null");} else {System.out.println("obj可用");}System.out.println("第2次gc");obj = null;System.gc();  Thread.sleep(1000);if (obj == null) {System.out.println("obj 是null");} else {System.out.println("obj可用");}} catch (InterruptedException e) {e.printStackTrace();}}
}
//执行结果
null
调用当前类的finalize()方法
obj可用
第2次gc
追踪垃圾回收过程:PhantomReferenceTest实例被GC了
obj 是null
  • 终结器引用
  • 实现对象的finalize()方法,称为终结器引用
  • 无需手动编码,其内部配合引用队列使用
  • 在GC时,终结器引用入队,由Finalizer线程通过终结器引用找到被引用对象并调用它的finalize()方法,第二次调用GC时才能回收被引用对象

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

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

相关文章

欢聚笔试题求助帖

事情是这样的&#xff0c;这段时间一直在求职投简历&#xff0c;期望在暑假之前接到一份大数据开发的实习工作。投了很多公司&#xff0c;然后就收到了欢聚的笔试邀约&#xff0c;HR说要我一天之内做出来&#xff0c;恰巧第二天还有组会要汇报&#xff0c;我就先放下了&#xf…

2024年信息素养大赛图形化编程、Python、算法创真题汇总

2024年信息素养大赛编程赛道初赛&#xff08;Scratch图形化编程、Python、C算法创意&#xff09;已经结束&#xff0c;根据Scratch实验室的了解全国青少年信息素养大赛初赛晋级及初赛成绩内容如下&#xff1a; 1.参赛选手将在 5 个工作日(节假日不计在内)内&#xff0c;通过信…

K8s-yaml文件

一.Yaml文件详解&#xff1a; Kubernetes 支持 YAML 和 JSON 格式管理资源对象 JSON 格式&#xff1a;主要用于 api 接口之间消息的传递YAML 格式&#xff1a;用于配置和管理&#xff0c;YAML 是一种简洁的非标记性语言&#xff0c;内容格式人性化&#xff0c;较易读 YAML 语…

汽车IVI中控开发入门及进阶(十八):显示技术之Frame Buffer帧缓冲器

Frame Buffer帧缓冲器(帧缓冲器,有时是帧存储器)是随机存取存储器(RAM)的一部分,包含驱动视频显示器的位图。它是一个内存缓冲区,包含表示完整视频帧中所有像素的数据。现代视频卡的核心包含帧缓冲电路。该电路将内存中的位图转换为可以在计算机监视器上显示的视频信号。…

每日5题Day9 - LeetCode 41 - 45

每一步向前都是向自己的梦想更近一步&#xff0c;坚持不懈&#xff0c;勇往直前&#xff01; 第一题&#xff1a;41. 缺失的第一个正数 - 力扣&#xff08;LeetCode&#xff09; 今天这道题没有ac&#xff0c;写不动了&#xff0c;下次再通过吧&#xff0c;先给个半成品下次回…

MySQL主从复制+读写分离(ShardingJDBC)

MySQL主从复制读写分离 MySQL主从复制介绍二进制日志&#xff1a; MySQL的主从复制原理如下搭建主从复制准备工作主库配置从库配置 测试 读写分离案例ShardingJDBC介绍数据库环境初始工程导入读写分离配置测试1). 保存数据2). 修改数据3). 查询数据4). 删除数据 MySQL主从复制 …

adb 汇总

常用命令 adb devices 查看链接设备列表 adb kill-server 关闭并清楚所有链接 adb connect 设置链接 adb tcpip 该条命令是设置网络adb监听的端口&#xff0c;5555是默认&#xff0c;也可以设置其他的&#xff0c;在安卓设备重启后会失效&#xff0c;不需要root权限&#x…

【C++】多态(多态的原理)

在本篇博客中&#xff0c;作者将会带领你深入理解C中的多态。 声明&#xff01;&#xff01;&#xff01;本代码以及讲解都是在32位机器下进行完成的&#xff0c;64位机器下会有所不同&#xff0c;但大同小异。 一.多态的概念 什么是多态&#xff1f; 多态就是不同的对象去做…

简易CAD程序:Qt多文档程序的一种实现

注&#xff1a;文中所列代码质量不高&#xff0c;但不影响演示我的思路 实现思路说明 实现DemoApplication 相当于MFC中CWinAppEx的派生类&#xff0c;暂时没加什么功能。 DemoApplication.h #pragma once#include <QtWidgets/QApplication>//相当于MFC中CWinAppEx的派生…

以太坊(3)——智能合约

智能合约 首先明确一下几个说法&#xff08;说法不严谨&#xff0c;为了介绍清晰才说的&#xff09;&#xff1a; 全节点矿工 节点账户 智能合约是基于Solidity语言编写的 学习Solidity语言可以到WFT学院官网&#xff08;Hello from WTF Academy | WTF Academy&#xff09;…

以x为界,分隔链表为两个分区,各分区内元素相对位置不变

题目描述&#xff1a; 题目思路&#xff1a; 1.设置两个头指针表示两个分区&#xff0c;并对每个分区设置相对于的遍历指针&#xff0c;指向分区链表链尾 2.设置原链表的遍历指针&#xff0c;判断指针所指元素是否小于目标值x&#xff0c;小于的话将结点添加到第一个分区末尾…

搭建电商电子商务平台有哪些好用的电商API数据采集接口?

电商API接口主要用于帮助开发者将电商功能集成到自己的应用程序中&#xff0c;实现诸如商品检索、商品价格数据获取、订单处理、支付、物流跟踪等功能。以下是一些常用的电商API接口提供商&#xff1a; 主流电商平台API&#xff1a; 淘宝开放平台&#xff1a;提供淘宝、天猫、…

ngnix 入门 二,docker启动nginx, 安装ssl 证书,使用配置文件,映射后端服务 ,提供给前端项目访问

搭建生产环境真不是人做的事&#xff0c;特别是对于一知半解的人。仅以此文献给各位技术人 说一下背景&#xff1a;项目前后端分离&#xff0c;前端 vue3 、小程序端 &#xff0c;后端 go 提供服务。 微信小程序需要使用 https 请求。 这就必须让我们想到nginx 了 想要达到的…

MySQL学习之DQL语句(数据查询语言)

准备SQL CREATE TABLE student ( id int, -- 编号 name varchar(20), -- 姓名 age int, -- 年龄 sex varchar(5), -- 性别 address varchar(100), -- 地址 math int, -- 数学 english int -- 英语 );INSERT INTO student(id,NAME,age,sex,address,math,english) VALUES (1,…

【UE5.1 角色练习】02-添加慢走、快速跑、蹲伏功能

目录 前言 步骤 一、慢走 二、快速跑 三、蹲伏 前言 在上一篇文章基础上&#xff08;【UE5.1 角色练习】01-使用小白人蓝图控制商城角色移动&#xff09;继续实现角色的慢走、快速跑以及蹲伏功能 步骤 一、慢走 1. 打开项目设置&#xff0c;添加一个操作映射&#x…

思维导图-VPN

浏览器集成了受信任的机构的证书

linux---信号的捕捉和处理

提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、信号 可以简单理解为信号是一个进程给另一个信号发消息&#xff0c;进程收到对应的信号就执行对应的方法&#xff0c;linux信号可以分为实时信号和非实时信号 1-31为非实时信号&#xff0c;34-64为…

计算机精选期刊特辑

文章目录 一、征稿简介二、合作期刊三、投稿咨询四、咨询 一、征稿简介 艾思科蓝依托互联网信息与数据库技术、整合渠道与合作资源&#xff0c;提供EI/SCI/SCIE/SSCI期刊论文的内容审查、发表支持等服务。艾思科蓝与多所知名出版社达成战略合作关系&#xff0c;持续开展合作征…

利用神经网络学习语言(一)——自然语言处理的基本要素

相关说明 这篇文章的大部分内容参考自我的新书《解构大语言模型&#xff1a;从线性回归到通用人工智能》&#xff0c;欢迎有兴趣的读者多多支持。 本文涉及到的代码链接如下&#xff1a;regression2chatgpt/ch10_rnn/tokenizer.ipynb 本系列文章将深入探讨一种应用广泛的神经…

syncthing文件夹同步与版本管理

1 前言 syncthing可以用来同步文件夹里的所有文件&#xff0c;并且有不错的版本管理&#xff0c;基本每次更改文件&#xff0c;20-40秒就被扫描到了&#xff0c;非常丝滑&#xff1b;这次以此来同步obsidian的插件和文件&#xff0c;达到多端同步&#xff1b; 我家里有一台台…