深入理解JVM:内存管理与垃圾回收机制探索

1. JVM简介

  • JVM是什么?

Java虚拟机(JVM)是Java程序的运行环境,它负责将编写的Java字节码转换为特定操作系统上的机器指令,并管理程序的运行时环境。简而言之,JVM是Java跨平台特性的基石。

  • JVM的重要性

JVM允许Java程序在不同的硬件和操作系统上无缝运行,无需重新编译。它不仅管理内存分配、垃圾回收等底层任务,还通过即时编译(JIT)提升运行效率,确保了Java应用的高性能与可移植性。

2. JVM结构图

在这里插入图片描述

  • 类加载子系统:负责查找并加载类文件的字节码,转换成JVM能理解的数据结构,并存储在方法区中。包括类的加载、验证、准备、解析和初始化等阶段。

  • 运行时数据区域:主要包括堆、栈、方法区、运行时常量池和本地方法栈。

  • 方法区:存储已被加载的类信息、常量、静态变量等。

  • 运行时常量池:存放编译期生成的各种字面量和符号引用。

  • 堆:存放几乎所有的对象实例。

  • 虚拟机栈:JVM为每个线程创建的,用来存储Java方法调用的状态信息,包括局部变量、操作数栈、动态链接和方法返回地址等。它是线程私有的,遵循后进先出(LIFO)原则。

  • 本地方法栈:为JVM使用到的Native方法服务。

  • 程序计数器:每个线程私有的,它记录了当前线程执行的字节码指令的地址,用于控制程序流程,实现指令间的跳转和调用返回,是线程切换和异常处理的关键部件。

  • 执行引擎:负责解释或编译字节码,执行Java程序。包含解释器、即时编译器(JIT)等组件,实现高效代码执行。

  • 本地方法接口(JNI):一种标准的编程接口,它定义了Java代码如何调用用其他编程语言(如C、C++)编写的方法的规则。

  • 本地库(Native Libraries):一组用非Java语言编写的代码库,它们通常以动态链接库(如Windows的.dll文件,Linux的.so文件,macOS的.dylib文件)的形式存在。

3. 内存模型演变

在这里插入图片描述

  • JDK 1.6: JVM的内存模型包含了一个被称为永久代(PermGen)的区域,它用于存储类的元数据、静态变量以及JVM内部的数据结构。这个区域是在JVM启动时创建的,并且它的大小是固定的,不会因为JVM的运行而改变。

  • JDK 1.7:虽然永久代仍然存在,但是JVM开始逐渐减少对永久代的使用。字符串常量池和静态变量被移动到了堆上。这是因为永久代的空间有限,如果永久代满了,就会触发Full GC,这会严重影响系统的性能。因此,将这些数据移动到堆上,可以更好地管理这些数据的生命周期,并且避免频繁的Full GC。

  • JDK 1.8及之后:永久代被完全移除,取而代之的是元空间(Metaspace)。元空间并不在Java堆中,而是在本地内存中。这意味着元空间的大小只受本地内存的限制。运行时常量池和类常量池都被移动到了元空间。但是,字符串常量池仍然在堆上,这是因为字符串常量池中的数据生命周期和普通的Java对象一样,更适合放在堆上管理。

    总的来说,从JDK 1.6到1.8,最大的变化是永久代被元空间取代,这改善了内存管理和GC性能,减少了内存溢出的风险,并为开发者提供了更好的内存使用透明度。

在这里插入图片描述

4. JVM垃圾回收

  • 什么是垃圾回收?*自动管理内存的过程,回收不再使用的对象所占的内存空间,防止内存泄露。

  • 垃圾回收如何工作?*通过可达性分析判断对象是否可达,不可达的对象被视为垃圾。常见的算法包括标记-清除、复制、标记-压缩等。

4.1垃圾回收器的类型及组合方式

在这里插入图片描述

(红色虚线)在jdk8时将这两个组合声明为废弃,并在jdk9中完全取消(绿色虚线)在jdk14中废弃(绿色虚线)jdk14中,删除CMS垃圾收集器

  • 新生代收集器:

ParNew(Serial 收集器的多线程版本,采用的也是复制算法。可通过-XX:ParallelGCThreads 参数来限制垃圾收集器的线程数。命令参数:-XX:+UseParNewGC)。

Serial(最基本、最古老的垃圾回收器,它在进行垃圾回收时会暂停所有的应用线程,适用于单核处理器的环境以及对延迟不敏感的应用。参数命令:-XX:+UseSerialGC)。

Paralel Scavenge(并行收集的多线程收集器,采用的是复制算法。通过设置参数命令,达到可控制吞吐量(Thoughput ,CPU 用于运行用户代码的时间/CPU总消耗时间)ParallelScavenge 是可以保证新生代的吞吐量优先,但是不能保证整体吞吐量。参数命令:-XX:+UseParallelGC。

  • 老年代收集器:

CMS(多线程并发的收集器,基于标记清除算法。参数命令:-XX:+UseConcMarkSweepGC)。

Serial Old GC(单线程串行的收集器,是基于标记-整理算法。-XX:+UseSerialGC参数可以指定年轻代和老年代都使用串行收集器)。

Parallel Old GC(多线程并发的收集器,基于标记整理算法。参数命令:-XX:+UseParallelOldGC)。

G1(一种全新的垃圾收集器,它既可以用于新生代,也可以用于老年代。G1收集器将堆内存划分为多个小块,可以并行和并发地进行垃圾收集。G1收集器主要用于大堆内存的环境,它的目标是实现高吞吐量和可预测的停顿时间。参数命令:-XX:+UseG1GC)。

4.2 JVM垃圾回收算法
  • 标记清除算法

它的基本思想是分两个阶段进行:标记阶段和清除阶段。
在这里插入图片描述

1.标记阶段:在标记阶段,垃圾回收器会遍历所有的对象,对每个对象进行检查。如果一个对象被其他对象引用,或者是根对象(例如全局变量或者栈上的变量),那么这个对象就被标记为“存活”的。

2.清除阶段:在清除阶段,垃圾回收器会再次遍历所有的对象,如果一个对象没有被标记为“存活”的,那么这个对象就被视为垃圾,垃圾回收器会回收这个对象所占用的内存。

标记-清除算法的优点是实现简单,且可以处理循环引用的情况。但是它的缺点是在清除阶段会产生大量的内存碎片,这可能会导致后续的内存分配变得困难。此外,标记-清除算法需要暂停应用程序来进行垃圾回收,可能会导致应用程序的响应时间增加。

  • 复制算法

复制算法是为了解决标记清除的效率问题。

它的基本思想是将可用内存分为两个相等的部分,每次只使用其中的一半。当这一半的内存用完时,就将还在使用的对象复制到另一半,然后再把已使用的内存清空,用于下一次的内存分配。
在这里插入图片描述

复制算法的步骤如下:

1.将堆内存分为两个相等的部分,只在其中一个部分(称为From区域)上进行内存分配。

2.当From区域的内存用完时,启动垃圾回收过程。遍历From区域中的所有对象,检查它们的引用。如果一个对象被引用,则将它3.复制到另一个区域(称为To区域)。

4.复制过程中,会更新所有指向被复制对象的引用,使它们指向新的位置。

5.复制完成后,交换From区域和To区域的角色。原来的From区域现在变成了To区域,将被清空并用于下一次的内存分配。

复制算法的优点是实现简单,且没有内存碎片。但是它的缺点是需要两倍的内存空间,且复制过程需要暂停应用程序,可能会导致应用程序的响应时间增加。这种算法通常用于新生代的垃圾回收,因为新生代的对象大多数都是“朝生暮死”的,只有少数对象会存活下来,所以复制算法的效率较高。

  • 标记整理算法

它的基本思想也是分两个阶段进行:标记阶段和整理阶段。
在这里插入图片描述

1.标记阶段:在标记阶段,垃圾回收器会遍历所有的对象,对每个对象进行检查。如果一个对象被其他对象引用,或者是根对象(例如全局变量或者栈上的变量),那么这个对象就被标记为“存活”的。

2.整理阶段:在整理阶段,垃圾回收器会移动所有存活的对象,使它们在内存中连续排列,然后直接回收边界以外的内存。

标记-整理算法的优点是可以有效地处理内存碎片问题,因为它会将存活的对象集中到内存的一端。但是,这种算法的缺点是需要移动对象,这会增加垃圾回收的开销,并且需要暂停应用程序来进行垃圾回收,可能会导致应用程序的响应时间增加。

  • 分代收集算法

它基于一个观察:大部分对象在内存中存在的时间很短。这种策略将Java堆分为两个或更多的部分,每个部分称为一个代(Generation)。常见的有新生代(Young Generation)和老年代(Old Generation)。

新生代通常包含新创建的对象。当新生代满了,垃圾回收器就会清理新生代中不再使用的对象,这个过程称为Minor GC。如果一个对象在Minor GC后仍然存活,那么它就会被移动到老年代。 老年代包含长时间存活的对象。只有当老年代满了,垃圾回收器才会清理老年代中不再使用的对象,这个过程称为Major GC或Full GC。

分代收集的优点是可以高效地回收短生命周期的对象,同时减少了对长生命周期对象的回收频率。这种策略在处理大量短生命周期的对象时特别有效,例如在处理HTTP请求或者GUI事件时创建的临时对象。 在Java中,新生代通常使用复制算法进行垃圾回收,而老年代通常使用标记-清除-整理算法进行垃圾回收。这样可以兼顾垃圾回收的效率和内存的利用率。

4.3 JVM垃圾回收过程

在这里插入图片描述

  1. 创建新对象先放到Eden区空间

  2. 当 Eden 区装满时,会对 Eden 区进行垃圾回收(Minor GC),在 Eden 区实现清除策略,没有被引用的对象直接被回收,再加载新的对象放到 Eden 区。

  3. Eden 区中依然存活的对象会被移送到 Survivor 区。

  4. 在每次Minor GC后,存活的对象会被从当前使用的Survivor区域(假设是Survivor 0)转移到另一个Survivor区域(即Survivor 1),并且对象的年龄会增加。这一过程称为对象的“存活跨越”。

  5. 随着对象在Survivor区域之间被复制的次数增多,它们的年龄(即age󠁪计数器)也随之增加。当对象达到预定义的最大年龄阈值(默认是15,可通过-XX:MaxTenuringThreshold󠁪参数调整)时,它们将被晋升到老年代(Old Generation)。

  6. 老年代用于存储长期存活或大尺寸的对象,当老年代内存不足时,再次触发GC: Major GC, 进行老年代的内存清理。

  7. 当老年代空间不足时,JVM将执行Major GC(也称作Full GC),这通常包括对整个年轻代和老年代的垃圾回收。Major GC比Minor GC更消耗资源,因为它涉及更多的内存空间。

  8. 如果在执行Major GC后,仍然没有足够的空间来满足新的内存分配需求,JVM将抛出OutOfMemoryError󠁪(OOM)异常,表明无法继续为程序分配所需的内存。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值