Java中的内存管理

本文概述

  • JVM内存结构
  • 垃圾收集器的工作
  • 标记和扫描算法
在Java中, 内存管理是对象分配和取消分配的过程, 称为内存管理。 Java自动执行内存管理。 Java使用称为垃圾收集器的自动内存管理系统。因此, 我们无需在应用程序中实现内存管理逻辑。 Java内存管理分为两个主要部分:
  • JVM内存结构
  • 垃圾收集器的工作
JVM内存结构 JVM在堆中创建各种运行时数据区域。这些区域在程序执行期间使用。当JVM退出时, 内存区域被破坏, 而当线程退出时, 数据区域被破坏。
Java中的内存管理

文章图片
方法范围
方法区域是堆内存的一部分, 在所有线程之间共享。它在JVM启动时创建。它用于存储类结构, 超类名称, 接口名称和构造函数。 JVM在方法区域中存储以下信息:
  • 类型的完全限定名称(例如:字符串)
  • 类型的修饰符
  • 类型的直接超类名称
  • 超级接口的完全限定名称的结构化列表。
堆面积
堆存储实际对象。它在JVM启动时创建。如果需要, 用户可以控制堆。它可以是固定大小或动态大小。当你使用new关键字时, JVM将为堆中的对象创建一个实例。而该对象的引用存储在堆栈中。每个运行的JVM进程只有一个堆。当堆变满时, 将收集垃圾。例如:
StringBuilder sb= new StringBuilder();

上面的语句创建StringBuilder类的对象。该对象分配给堆, 引用sb分配给堆栈。堆分为以下部分:
  • 年轻一代
  • 幸存空间
  • 老一代
  • 永久的一代
  • 缓存代码
参考类型
引用有四种类型:强引用, 弱引用, 软引用和幻像引用。引用类型之间的区别在于, 它们引用的堆上的对象可以在不同条件下进行垃圾收集。
有力的参考:在日常编程中使用它非常简单。附加了“强引用”的任何对象均不符合垃圾回收的条件。我们可以使用以下语句来创建强大的参考:
StringBuilder sb= new StringBuilder();

弱引用:在下一个垃圾回收过程之后, 它将无法生存。如果我们不确定何时会再次请求数据。在这种情况下, 我们可以为其创建一个弱引用。以防万一, 如果垃圾回收器进行处理, 它将破坏该对象。当我们再次尝试检索该对象时, 我们得到一个空值。它在java.lang.ref.WeakReference类中定义。我们可以使用以下语句创建一个弱引用:
WeakReference< StringBuilder> reference = new WeakReference< > (new StringBuilder());

软参考:当应用程序内存不足时收集。垃圾收集器不会收集柔软可触及的对象。所有软引用的对象s都会在抛出OutOfMemoryError之前被收集。我们可以使用以下语句创建一个软引用:
SoftReference< StringBuilder> reference = new SoftReference< > (new StringBuilder());

幻像参考:它在java.lang.ref包中可用。它在java.lang.ref.PhantomReference类中定义。只要垃圾收集器想要收集, 就可以收集只有幻像引用指向它们的对象。我们可以使用以下语句创建幻像引用:
PhantomReference< StringBuilder> reference = new PhantomReference< > (new StringBuilder());

堆叠面积
创建线程时会生成堆栈区域。它可以是固定大小, 也可以是动态大小。堆栈内存是按线程分配的。它用于存储数据和部分结果。它包含对堆对象的引用。它还保留值本身, 而不是对堆中对象的引用。存储在堆栈中的变量具有一定的可见性, 称为作用域。
堆栈框架:堆栈框架是一种包含线程数据的数据结构。线程数据表示当前方法中线程的状态。
  • 它用于存储部分结果和数据。它还执行动态链接, 方法返回的值和调度异常。
  • 调用方法时, 将创建一个新框架。方法调用完成后, 它将破坏框架。
  • 每个帧都包含自己的局部变量数组(LVA), 操作数堆栈(OS)和帧数据(FD)。
  • LVA, OS和FD的大小在编译时确定。
  • 在给定的控制线程中的任何一点上, 只有一个框架(用于执行方法的框架)处于活动状态。该帧称为当前帧, 其方法称为当前方法。方法的类称为当前类。
  • 如果框架的方法调用另一个方法或该方法完成, 则框架将停止当前方法。
  • 线程创建的框架是该线程的本地框架, 任何其他线程都无法引用。
本机方法堆栈
也称为C堆栈。它是使用非Java语言编写的本机代码的堆栈。 Java本机接口(JNI)调用本机堆栈。本机堆栈的性能取决于操作系统。
PC寄存器
每个线程都有一个与之关联的程序计数器(PC)寄存器。 PC寄存器存储返回地址或本机指针。它还包含当前正在执行的JVM指令的地址。
垃圾收集器的工作 垃圾收集器概述
当程序以Java执行时, 它以不同的方式使用内存。堆是对象所在的内存的一部分。它是垃圾收集过程中唯一的内存部分。也称为垃圾可收集堆。所有垃圾收集都确保堆具有尽可能多的可用空间。垃圾收集器的功能是查找和删除无法访问的对象。
对象分配
分配对象时, JRockit JVM将检查对象的大小。它区分大小物体。大小取决于JVM版本, 堆大小, 垃圾回收策略以及所使用的平台。对象的大小通常在2到128 KB之间。
小对象存储在线程本地区域(TLA)中, 该区域是堆的空闲块。 TLA不与其他线程同步。当TLA变满时, 它将请求新的TLA。
另一方面, 不适用于TLA的大对象直接分配到堆中。如果线程正在使用年轻空间, 那么它将直接存储在旧空间中。大对象需要线程之间更多的同步。
Java垃圾收集器是什么?
JVM控制垃圾收集器。 JVM决定何时执行垃圾回收。我们还可以请求JVM运行垃圾回收器。但是, 在任何情况下都无法保证JVM会遵守。如果JVM感觉到内存不足, 则运行垃圾回收器。当Java程序请求垃圾收集器时, JVM通常以短顺序授予该请求。它不能确保请求接受。
要理解的重点是“什么时候对象可以进行垃圾收集?”
每个Java程序都有多个线程。每个线程都有其执行堆栈。在Java程序中有一个要运行的线程是main()方法。现在我们可以说, 当没有活动线程访问对象时, 该对象就有资格进行垃圾回收。垃圾收集器认为该对象符合删除条件。如果程序具有引用对象的引用变量, 该引用变量可用于活动线程, 则该对象称为可达对象。
这里出现一个问题, 即“ Java应用程序是否会耗尽内存?”
答案是肯定的。垃圾收集系统在不使用对象时会尝试从内存中获取对象。但是, 如果要维护许多活动对象, 则垃圾回收不能保证有足够的内存。仅可用内存将得到有效管理。
垃圾收集的类型
垃圾收集有五种类型, 如下所示:
  • 串行GC:它采用标记和清除方法, 适用于年少的GC和主要的GC。
  • 并行GC:与串行GC相似, 不同之处在于, 它生成N(系统中CPU核心数)个线程用于年轻一代的垃圾回收。
  • 并行旧式GC:与并行GC相似, 不同之处在于, 两代均使用多个线程。
  • 并发标记扫描(CMS)收集器:它为旧一代进行垃圾收集。你可以使用XX:ParalleCMSThreads = JVM选项限制CMS收集器中的线程数。它也被称为并发低暂停收集器。
  • G1垃圾收集器:它是Java 7中引入的。其目标是替换CMS收集器。它是一个并行, 并发和CMS收集器。没有年轻人和老年人的空间。它将堆分成几个相等大小的堆。它首先收集实时数据较少的区域。
标记和扫描算法 JRockit JVM使用标记和清除算法来执行垃圾收集。它包含两个阶段, 标记阶段和扫描阶段。
标记阶段:可从线程, 本机句柄和其他GC根源访问的对象标记为活动。每个对象树都有多个根对象。 GC根始终是可访问的。因此, 任何在其根上具有垃圾回收根的对象。它标识并标记所有正在使用的对象, 其余的可以视为垃圾。
Java中的内存管理

文章图片
扫描阶段:在此阶段, 遍历堆以查找活动对象之间的间隙。这些间隔记录在空闲列表中, 可用于新对象分配。
标记和清除有两个改进的版本:
  • 并发标记和扫描
  • 平行标记和扫掠
并发标记和扫描
它允许线程在大部分垃圾回收期间继续运行。有以下几种标记类型:
  • 初始标记:它标识活动对象的根集。它是在线程暂停时完成的。
  • 并发标记:在此标记中, 遵循根集中的引用。它查找并标记堆中其余的活动对象。它是在线程运行时完成的。
  • 清洗前标记:它标识并发标记所做的更改。标记并找到其他活动对象。它是在线程运行时完成的。
  • 最终标记:它标识了通过预清洁标记所做的更改。标记并找到其他活动对象。它是在线程暂停时完成的。
平行标记和扫掠
它使用系统中所有可用的CPU来尽快执行垃圾回收。它也称为并行垃圾收集器。当并行垃圾回收执行时, 线程不执行。
Mark and Sweep的优点
  • 这是一个重复的过程。
  • 这是一个无限循环。
  • 在算法执行期间不允许额外的开销。
【Java中的内存管理】标记和扫描的缺点
  • 当垃圾收集算法运行时, 它将停止正常程序执行。
  • 它在一个程序上运行多次。

    推荐阅读