JVM内部世界(内存划分,类加载,垃圾回收)(下)

简介: JVM内部世界(内存划分,类加载,垃圾回收)(下)

JVM内部世界(内存划分,类加载,垃圾回收)(上)

https://developer.aliyun.com/article/1480774?spm=a2c6h.13148508.setting.14.5f4e4f0ePc4m3X

💕"Echo"💕

作者:Mylvzi

文章主要内容:JVM内部世界(内存划分,类加载,垃圾回收)

3.垃圾回收机制(GC)

在C语言中我们学习过动态内存管理这一章节,通过malloc/realloc函数申请动态的内存,通过free来释放申请的动态内存,对于动态内存来说,最需要注意的一点是要及时通过free来释放申请的内存,如果不及时释放,就有可能造成内存泄露问题

在C++里面也是,都是需要通过手动的释放申请的内存(C++中是delete方法),这种手动释放内存的方式对于程序员来说是一个致命杀手,会常常突然出现,而且难以发现(往往是因为长时间的大量不释放内存所导致的),为了解决这种问题,最好的方法就是把释放内存这个操作交给计算机去执行

在Java中就引入了**垃圾回收机制(Garbage Collection)**来自动的完成内存的释放,可有这样的一个比喻说明C++和Java的垃圾回收机制的不同–“C++是手动挡,Java是自动挡”

GC的工作过程主要有以下两步:

  1. 找到垃圾
  2. 释放垃圾

1.找到垃圾

释放垃圾的第一步首先需要找到"垃圾",这里的垃圾就是不再使用的内存.具体找的方式大体上相同,都是需要有一组线程去不断的扫描的当前所有的对象,判断对象是否仍被引用,如果没有引用就认为是"垃圾"

不同的语言实现的方式有所差异,大概分为以下两种:

  1. 引用计数
  2. 可达性分析

1.引用计数

为new出来的出现单独创建一块内存空间,当做计数器,描述这个对象有多少引用指向

如果引用计数为0,就代表没有引用指向,也就代表此对象成为"垃圾",可以被释放

引用计数的问题

1.需要额外占用内存空间

引用计数需要额外的内存当做计数器,计数器少说也得2个字节,如果对象本身很小,那么计数器的内存占总体的内存的比例就会很大,而且随着对象数目的增多,这种额外的内存开销就不容忽视

2.存在循环引用问题

如果两个对象分别引用,就会形成环形引用,就有可能出现永远无法释放的问题

class Test {
  public Test t;
}
Test a = new Test();
Test b = new Test();
// 在内部分别引用
a.t = b;
b.t = a;
// 置空
a = null;
b = null;

在上述代码中,每置空之前,创建出的两个对象的引用计数都是2,分别给a,b置空,但是内部t对象仍在引用,所以创建的两个Test对象的引用变为1

此时a和b被销毁了,在代码中不可能再访问到这两个对象,但是此时这两个对象的引用计数不为0,要想释放对象1,需要先释放对象2,要想释放对象2,需要先释放对象1,构成了逻辑上的死环,这两个对象就永远无法进行释放了

2.可达性分析

Java的GC机制采用的是可达性分析,通过扫描的方式,从特定对象出发(GC Root),对扫描到的对象标记为可达,没有扫描到的对象就认为是不可达的,需要当做垃圾进行释放

可达性分析本质上是一种使用时间换空间的方式,通过一组扫描线程,不断的对所有的对象进行扫描,且这种扫描是周期性的,遍历方式类似于树的遍历(底层很可能是N叉树)

GC Root是扫描过程的起点,通常包括以下几种类型:

  1. 活动线程的本地变量和输入参数
  2. 静态对象的引用
  3. 活动线程的所有类对象

2.释放垃圾

释放垃圾的方法主要有三种:

  1. 标记清楚
  2. 复制算法
  3. 标记整理

1.标记清除

对于标记的对象,直接释放

标记清除是一种简单粗暴的方式,垃圾在哪里,就直接释放

演示:

缺陷:

  1. 会导致大量内存碎片的出现.申请内存是直接申请一个连续的空间,内存碎片的出现会导致可申请的连续空间变小,比如如果上述区域2的内存空间较小,新的对象所需的内存空间大于2,那么2区域的内存就永远无法使用了,随着内存碎片的增多,这种情况会更加明显

2.复制算法

将内存一分为2,一半用于对象的存储,一般用于复制

上述标记清楚的方式最大的缺陷就在于连续空间的减少,通过复制算法就能解决上述问题

将区域2和4删除之后,剩余的区域1和3一起复制到内存的另一半,这样当有新的对象尝试申请内存时,就可以利用到左侧的连续的内存空间

但是复制算法的方式的缺点也很明显:

  1. 内存利用率不高,整个内存一分为2
  2. 如果有效的数据很多,挪动一次需要移动的数据很多,开销不容忽视

3.标记整理

上述两种方法都有着各自的缺陷,通过标记整理的方式能够进一步的提高效率和内存利用率

标记整理处理垃圾的方式类似于顺序表删除任意位置元素的实现,在删除之后,需要从后往前挪动数据,来保证顺序表的连续性

标记整理也是这样,当有垃圾被回收之后,就把有效数据从后往前挪动,保证内存利用的连续性

但其实这种方式的开销也很大,也需要大量的挪动数据

JVM采用的实际上是一种更加高效的方式,利用一些经验规律,达到内存利用和垃圾回收效率的最大化,JVM内部采用的方式叫做分代回收

JVM把内存分为几个部分

  1. 伊甸区
  2. 幸存区
  3. 老年区

新new出来的对象会首先被存储到伊甸区(新生代)之中,经验表明,new出来的对象的生命周期是很短的,往往短时间内就会随着方法的结束而销毁,在一次扫描过程中就能被释放,没有被释放的对象就存储到幸存区之中

由于对象的销毁很快,大部分的对象在伊甸区中就被销毁了,所以在幸存去之中存储的对象很少,就比较适合使用复制算法,幸存区 的内存被一分为二.

幸存区也会被扫描线程扫描,不过扫描的频率比伊甸区之中要低很多,每扫描一次就利用复制算法对垃圾进行回收,往往在幸存区之中要进行多轮扫描

经过多轮扫描之后,如果仍有对象存储到幸存区之中,这些对象就会被转移到老年区之中,老年区的扫描频率更低

为什么扫描的频率越来越低呢?这其实也是一种经验规律,如果对象在第一次(伊甸区)之中没有被释放,那么其生存时间就比较长,证明该对象在短时间内不会被清除,如果在幸存区之中经过多轮扫描还是存活,就更加证明该对象在短时间之内不会被清除,不需要频繁的去扫描该对象

分代回收的这种机制就像是找工作,新生代就是笔试,对象多,淘汰率高,通过笔试就是进入了面试(幸存区),还要经过多轮的面试(在幸存去反复的被扫描),都通过了就进入老年代(拿到offer了,此时检查的频率就降低了,但是如果被标记为垃圾,就会被淘汰

以上就是JVM


目录
相关文章
|
24天前
|
存储 算法 Oracle
极致八股文之JVM垃圾回收器G1&ZGC详解
本文作者分享了一些垃圾回收器的执行过程,希望给大家参考。
|
1月前
|
Java Docker 索引
记录一次索引未建立、继而引发一系列的问题、包含索引创建失败、虚拟机中JVM虚拟机内存满的情况
这篇文章记录了作者在分布式微服务项目中遇到的一系列问题,起因是商品服务检索接口测试失败,原因是Elasticsearch索引未找到。文章详细描述了解决过程中遇到的几个关键问题:分词器的安装、Elasticsearch内存溢出的处理,以及最终成功创建`gulimall_product`索引的步骤。作者还分享了使用Postman测试接口的经历,并强调了问题解决过程中遇到的挑战和所花费的时间。
|
1月前
|
监控 Java
压力测试Jmeter的简单使用,性能监控-堆内存与垃圾回收 -jvisualvm的使用
这篇文章介绍了如何使用JMeter进行压力测试,包括测试前的配置、测试执行和结果查看。同时,还探讨了性能监控工具jconsole和jvisualvm的使用,特别是jvisualvm,它可以监控内存泄露、跟踪垃圾回收、执行时内存和CPU分析以及线程分析等,文章还提供了使用这些工具的详细步骤和说明。
压力测试Jmeter的简单使用,性能监控-堆内存与垃圾回收 -jvisualvm的使用
|
5天前
|
监控 算法 Java
Java中的内存管理:理解垃圾回收机制的深度剖析
在Java编程语言中,内存管理是一个核心概念。本文将深入探讨Java的垃圾回收(GC)机制,解析其工作原理、重要性以及优化方法。通过本文,您不仅会了解到基础的GC知识,还将掌握如何在实际开发中高效利用这一机制。
|
5天前
|
存储 监控 算法
Java中的内存管理与垃圾回收机制解析
本文深入探讨了Java编程语言中的内存管理策略和垃圾回收机制。首先介绍了Java内存模型的基本概念,包括堆、栈以及方法区的划分和各自的功能。进一步详细阐述了垃圾回收的基本原理、常见算法(如标记-清除、复制、标记-整理等),以及如何通过JVM参数调优垃圾回收器的性能。此外,还讨论了Java 9引入的接口变化对垃圾回收的影响,以及如何通过Shenandoah等现代垃圾回收器提升应用性能。最后,提供了一些编写高效Java代码的实践建议,帮助开发者更好地理解和管理Java应用的内存使用。
|
7天前
|
存储 缓存 算法
Java中的内存管理:理解垃圾回收机制
本文将深入探讨Java中的内存管理,特别是垃圾回收机制。我们将从基本的内存分配开始,逐步解析垃圾回收的原理和过程,以及它对Java应用程序性能的影响。通过实例演示,我们会展示如何在Java中有效地管理和优化内存使用。最后,我们将讨论一些常见的内存泄漏问题及其解决方案。
|
1月前
|
存储 算法 Oracle
不好意思!耽误你的十分钟,JVM内存布局还给你
先赞后看,南哥助你Java进阶一大半在2006年加州旧金山的JavaOne大会上,一个由顶级Java开发者组成的周年性研讨会,公司突然宣布将开放Java的源代码。于是,下一年顶级项目OpenJDK诞生。Java生态发展被打开了新的大门,Java 7的G1垃圾回收器、Java 8的Lambda表达式和流API…大家好,我是南哥。一个Java学习与进阶的领路人,相信对你通关面试、拿下Offer进入心心念念的公司有所帮助。
不好意思!耽误你的十分钟,JVM内存布局还给你
|
23天前
|
存储 监控 算法
内存泄漏还是高性能?深度揭秘.NET垃圾回收机制
【8月更文挑战第28天】垃圾回收是.NET框架中自动化内存管理的关键机制,它通过分代收集算法自动清理不再使用的对象,简化了开发者的内存管理工作。本文深入解析了垃圾回收器的工作原理、对象内存分配策略及优化技巧,并介绍了多种监控工具,帮助提升.NET应用性能与稳定性。掌握这些知识将使开发者能够更高效地管理内存,提高应用程序的运行效率。
25 3
|
26天前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
17 3
|
28天前
|
前端开发 JavaScript Java
揭开 JavaScript 垃圾回收的秘密——一场与内存泄漏的生死较量,让你的代码从此焕然一新!
【8月更文挑战第23天】本文通过多个实例深入探讨了JavaScript中的垃圾回收机制及其对应用性能的影响。首先介绍了基本的内存管理方式,随后分析了变量不再使用时的回收过程。接着,通过事件监听器未被移除及全局变量管理不当等场景展示了常见的内存泄漏问题。最后,文章介绍了使用`WeakRef`和`FinalizationRegistry`等现代API来有效避免内存泄漏的方法。理解并运用这些技术能显著提升Web应用的稳定性和效率。
72 0