可视化JVM中的内存管理(Java,Kotlin,Scala,Groovy,Clojure)

Øriginally published in deepu.tech.

Please follow me on Twitter for updates and let me know if something can be improved in the post.


在这个由多部分组成的系列文章中,我旨在揭露内存管理背后的概念,并更深入地研究某些现代编程语言中的内存管理。 我希望该系列文章可以使您对这些语言在内存管理方面正在发生的事情有所了解。 在本章中,我们将研究Java虚拟机(JVM)由Java,Kotlin,Scala,Clojure,JRuby等语言使用。

If you haven't read the first part of this series, please read it first as I explained the difference between the Stack and Heap memory there which would be useful to understand this chapter.

JVM memory structure

首先,让我们看看JVM的内存结构是什么。 这是基于JDK 11向前。 以下是JVM进程可用的内存,并由操作系统(OS)分配。

JVM Memory structure

这是操作系统分配的本机内存,其数量取决于操作系统,处理器和JRE。 让我们看看不同的领域是什么:

Heap Memory

这是JAVA存储对象或动态数据的地方。 这是最大的内存区域,这是垃圾收集(GC)发生。 堆内存的大小可以使用Xms(初始)和Xmx(最大)标志。 整个堆内存未提交给虚拟机因为其中一些保留为虚拟空间,堆可以增长以使用它。 堆进一步分为“年轻”和“旧”生成空间。

  • 年轻一代: 年轻一代 or "New Space" is where new objects live和is further divided into "伊甸园空间"和"幸存者空间"。 This space is managed by “次要GC” also sometimes called "Young GC"伊甸园空间:这是创建新对象的地方。 创建新对象时,将在此处分配内存。幸存者空间:这是在次要GC中幸存的对象的存储位置。 这分为两半S0和S1。老一代: 老一代 or “租用空间” is where objects that reached the maximum tenure threshold during minor GC live。 This space is managed up by “主要GC”。

Thread Stacks

这是堆栈存储器区域,在此过程中,每个线程只有一个堆栈存储器。 在此存储特定于线程的静态数据,包括方法/功能框架和对象的指针。 可以使用s旗。

Meta Space

这是本机内存的一部分,默认情况下没有上限。 这曾经是永久世代(PermGen)空间在早期版本的JVM中。 类加载器使用此空间来存储类定义。 如果此空间持续增长,则操作系统可能会将此处存储的数据从RAM移至虚拟内存,这可能会使应用程序变慢。 为了避免这种情况,可以对与XX:MetaspaceSize和-XX:MaxMetaspaceSize标记,在这种情况下,应用程序可能会抛出内存不足错误。

Code Cache

这是准时制(JIT)编译器存储经常访问的已编译代码块。 通常,JVM必须将字节代码解释为本机机器代码,而无需解释JIT编译的代码,因为它已经采用本机格式并已缓存在此处。

Shared Libraries

在此存储使用的任何共享库的本机代码。 操作系统在每个进程中仅加载一次。


JVM memory usage (Stack vs Heap)

既然我们已经清楚了内存的组织方式,那么让我们看看执行程序时如何使用内存中最重要的部分。

让我们使用下面的Java程序,代码没有针对正确性进行优化,因此忽略诸如不必要的中间变量,不正确的修饰符之类的问题,重点是可视化堆栈和堆内存的使用情况。

class Employee {
    String name;
    Integer salary;
    Integer sales;
    Integer bonus;

    public Employee(String name, Integer salary, Integer sales) {
        this.name = name;
        this.salary = salary;
        this.sales = sales;
    }
}

public class Test {
    static int BONUS_PERCENTAGE = 10;

    static int getBonusPercentage(int salary) {
        int percentage = salary * BONUS_PERCENTAGE / 100;
        return percentage;
    }

    static int findEmployeeBonus(int salary, int noOfSales) {
        int bonusPercentage = getBonusPercentage(salary);
        int bonus = bonusPercentage * noOfSales;
        return bonus;
    }

    public static void main(String[] args) {
        Employee john = new Employee("John", 5000, 5);
        john.bonus = findEmployeeBonus(john.salary, john.sales);
        System.out.println(john.bonus);
    }
}

单击幻灯片,然后使用箭头键向前/向后移动,以查看如何执行上述程序以及如何使用堆栈和堆存储器:

ñote: If the slides look cut off at edges, then click on the title of the slide or here to open it directly in SpeakerDeck.

如你看到的:

  • 静态场被放在堆栈中的单独块中Every function call is added to the thread's stack memory as a frame-blockAll local variables including arguments and the return value is saved within the function frame-block on the StackAll primitive types like 整型直接存储在堆栈中。 这也适用于静态字段All object types like 雇员,整数,串 are created on the Heap and is referenced from the Stack using Stack po整型ers. This applies to static fields as wellFunctions called from the current function is pushed on top of the StackWhen a function returns its frame is removed from the StackOnce the main process is complete the objects on the Heap do not have any more po整型ers from Stack and becomes orphanUnless you make a copy explicitly,all object references within other objects are done using po整型ers

如您所见,堆栈是由操作系统自动管理的,而不是由JVM本身自动完成的。 因此,我们不必担心堆栈。 另一方面,Heap并不是由操作系统自动管理的,并且由于其最大的内存空间并保存动态数据,因此它可能呈指数增长,从而导致我们的程序随着时间的推移而耗尽内存。 随着时间的流逝,它也变得支离破碎,使应用程序变慢。 这就是JVM的帮助。 它使用垃圾回收过程自动管理堆。


JVM Memory management: Garbage collection

现在我们知道了JVM如何分配内存,让我们看看它如何自动管理Heap内存,这对于应用程序的性能非常重要。 当程序尝试在堆上分配的内存多于可用内存时(取决于Xmx配置)我们遇到内存不足错误。

JVM通过垃圾回收管理堆内存。 简而言之,它释放了孤立对象(即不再从堆栈直接或间接(通过另一个对象中的引用)引用的对象)使用的内存,从而为创建新对象腾出了空间。

GC Roots

JVM中的垃圾收集器负责:

  • 从操作系统分配内存,然后再分配回操作系统。根据请求向应用程序分配已分配的内存。确定应用程序仍在使用分配的内存中的哪些部分。回收未使用的内存,以供应用程序重用。

JVM垃圾收集器是世代的(Heap中的对象按其年龄分组并在不同阶段清除)。 有许多不同的算法可用于垃圾回收,但是Mark & Sweep是最常用的一种。

Mark & Sweep Garbage collection

JVM使用一个单独的后台驻留程序线程,该线程在后台运行以进行垃圾回收,并且该进程在满足某些条件时运行。 Mark&Sweep GC通常涉及两个阶段,根据所使用的算法,有时会有一个可选的第三阶段。

Mark & sweep GC

  • 打标:第一步,垃圾收集器确定正在使用的对象和未使用的对象。 从GC根目录(堆栈指针)递归使用或使用的对象被标记为活动对象。扫地:垃圾收集器遍历堆并删除所有未标记为活动的对象。 现在,该空间被标记为空闲。压实:删除未使用的对象后,所有剩余的对象将一起移动。 这将减少碎片并提高为新对象分配内存的性能

这种类型的GC也被称为“世界停止GC”,因为它们在执行GC时会在应用程序中引入暂停时间。

JVM offers few different algorithms to choose from when it comes to GC and there might be few more options available depending on the JDK vendor you use(Like the Shenandoah GC, available on OpenJDK). The different implementations focus on different goals like:

  • 通量:收集垃圾所花费的时间而不是应用程序时间会影响吞吐量。 理想情况下,吞吐量应该很高(即,GC时间很短时)。暂停时间:GC停止执行应用程序的持续时间。 理想的暂停时间应该非常短。脚印:使用的堆大小。 理想情况下,应将其保持在较低水平。

Collectors available as of JDK 11

从当前的LTE版本JDK 11开始,以下垃圾收集器可用,并且JVM根据使用的硬件和OS选择使用的默认垃圾收集器。 我们总是可以指定要与-XX以及切换。

  • 串行收集器:它为GC使用单线程,对于数据量较小的应用程序非常有效,并且最适合单处理器计算机。 可以使用以下方式启用-XX:+ UseSerialGC开关。并联收集器:此主题着重于高吞吐量,并使用多个线程来加速GC进程。 这适用于具有在多线程/多处理器硬件上运行的中到大型数据集的应用程序。 可以使用以下方式启用-XX:+ UseParallelGC开关。垃圾优先(G1)收集器:G1收集器主要是并发收集器(意味着仅同时执行昂贵的工作)。 这适用于具有大量内存的多处理器计算机,并且在大多数现代计算机和OS上均默认启用。 它着重于低暂停时间和高吞吐量。 可以使用以下方式启用-XX:+ UseG1GC开关。Z垃圾收集器:这是JDK11中引入的新实验GC。 它是一个可伸缩的低延迟收集器。 它是并发的,不会停止应用程序线程的执行,因此不会停下来。 它适用于要求低延迟和/或使用非常大的堆(数TB)的应用程序。 可以使用以下方式启用-XX:+ UseZGC开关。

GC process

不管使用哪种收集器,JVM都有两种类型的GC进程,取决于执行的时间和地点,即次要GC和主要GC。

Minor GC

这种类型的GC可使年轻一代的空间保持紧凑和清洁。 当满足以下条件时触发:

  • JVM无法从Eden空间获取所需的内存来分配新对象

最初,堆空间的所有区域都是空的。 伊甸园的记忆是第一个被填充的空间,其次是幸存者空间,最后是终身空间。

让我们看一下次要的GC流程:

单击幻灯片,然后使用箭头键向前/向后移动以查看该过程:

ñote: If the slides look cut off at edges, then click on the title of the slide or here to open it directly in SpeakerDeck.

  1. 让我们假设开始时伊甸园空间中已经有对象(将块01至06标记为已用内存)应用程序创建一个新对象(07)JVM尝试从Eden空间获取所需的内存,但是Eden中没有可用空间来容纳我们的对象,因此JVM会触发次要GCGC从堆栈指针开始递归遍历对象图,以标记用作活动对象(已用内存)和其余对象作为垃圾对象(孤儿)JVM从S0和S1中选择一个随机块作为“ To Space”,让我们假设它是S0。 现在,GC将所有活动对象移动到“ To Space”(S0)中,该空间在我们启动时为空,并将它们的寿命增加一。现在,GC清空了Eden空间,并且为新对象分配了Eden空间中的内存让我们假设已经过去了一段时间,现在伊甸园空间中还有更多的对象(块07至13标记为已用内存)应用程序创建一个新对象(14)JVM尝试从Eden空间获取所需的内存,但是Eden中没有可用空间来容纳我们的对象,因此JVM触发了第二个次要GC重复标记阶段,并标记存活/孤立对象,包括幸存者空间“ To Space”中的对象JVM现在将空闲的S1选择为“ To Space”,而S0变为“ From Space”。 现在,GC将所有活动对象从Eden空间和“ From Space” S0移入“ To Space” S1,当我们开始时该对象为空,并将它们的年龄增加一。 由于某些对象不适合放置在此处,因此由于幸存者空间无法增长,因此将它们移到“租用空间”,此过程称为过早提升。 即使其中一个幸存者空间是空闲的,也会发生这种情况现在,GC清空了Eden空间和“ From Space” S0,并且为新对象分配了Eden空间中的内存每个次要GC都会重复此操作,并且幸存者会在S0和S1之间转移,并且他们的年龄会增加。 年龄达到“最大年龄阈值”(默认为15岁)后,该对象将移至“租用空间”

因此,我们看到了次要GC如何从年轻一代那里回收空间。 这是一个停滞不前的过程,但是它是如此之快,以致在大多数情况下可以忽略不计。

Major GC

这种类型的GC可使旧的(Tenured)空间保持紧凑和清洁。 当满足以下条件时触发:

  • 开发人员电话System.gc(), 要么Runtime.getRunTime()。gc()从程序。JVM认为由于较小的GC周期已填满它,因此没有足够的使用期限空间。在次要GC期间,如果JVM无法从Eden或幸存者空间回收足够的内存。如果我们设置一个MaxMetaspaceSizeJVM选项,并且没有足够的空间来加载新类。

让我们看一下主要的GC流程,它不像次要的GC那样复杂:

  1. 让我们假设已经过去了许多次要的GC周期,并且使用空间几乎已满,并且JVM决定触发“主要GC”The GC recursively traverses the object graph starting from stack pointers to mark objects that are used as alive(Used memory) and remaining objects as garbage(Orphans) in the tenured space. If the major GC was triggered during a minor GC the process includes the young(Eden & Survivor) and tenured spaceGC现在删除了所有孤立对象,并回收了内存在重大GC事件期间,如果堆中没有更多对象,则JVM还会通过从元空间中删除已加载的类来从元空间中回收内存,这也称为完整GC

Conclusion

This post should give you an overview of the JVM memory structure and memory management. This is not exhaustive, there are a lot more advanced concepts and tuning options available for specific use cases and you can learn about them from https://docs.oracle.com. But for most JVM(Java, Kotlin, Scala, Clojure, JRuby, Jython) developers this level of information would be sufficient and I hope it helps you write better code, considering these in mind, for more performant applications and keeping these in mind would help you to avoid the next memory leak issue you might encounter otherwise.

希望您对JVM内部有一个愉快的了解,请继续关注本系列的下一篇文章。


References


如果您喜欢这篇文章,请留下喜欢或评论。

You can follow me on Twitter and LinkedIn.

from: https://dev.to//deepu105/visualizing-memory-management-in-jvm-java-kotlin-scala-groovy-clojure-19le

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值