首页 > 资讯 > 科技 > 正文
2024-04-05 21:20

深入理解Java虚拟机:堆详解

大家好,我是伊安~

前言

本节主要讲运行时数据区(堆),也就是下图中的部分,也就是类加载完成后的阶段:

当我们通过前面的阶段:类加载->验证->准备->解析->初始化时,执行引擎就会使用我们的类,执行引擎就会使用它来运行我们的类。 时间数据区。

内存是非常重要的系统资源。 它是硬盘和CPU之间的中间仓库和桥梁。 它承载着操作系统和应用程序的实时运行。 JVM内存布局指定了Java在运行过程中的内存申请、分配和管理策略,保证了JVM高效、稳定的运行。 不同的JVM在内存划分方式和管理机制上有一些差异。

文本

我们通过磁盘或者网络IO获取的数据需要先加载到内存中,然后CPU从内存中获取数据并读取。 换句话说,内存充当了CPU和磁盘之间的桥梁。

线

线程是程序中的执行单元。 JVM 允许应用程序并行执行多个线程。 在JVM中,每个线程都直接映射到操作系统的本机线程。

当Java线程准备好执行时,操作系统的本机线程也会同时创建。 Java线程执行终止后,本地线程也会被回收

操作系统负责将所有线程调度到任何可用的CPU。 本地线程一旦初始化成功,就会调用Java线程中的run()方法。

JVM系统线程:

堆是JVM进程所特有的,即一个进程只有一个JVM,但进程中包含多个线程,它们共享同一个堆空间。

数组和对象可能永远不会存储在堆栈上,因为堆栈帧存储一个引用,该引用指向对象或数组在堆中的位置。 方法结束后,堆中的对象不会立即被移除,只有在垃圾回收时才会被移除。

堆内存崩溃

Java 7及之前的堆内存逻辑上分为三部分:新区+旧区+永久区

Java 8及以后的堆内存逻辑上分为三部分:新区+旧区+元空间

Jdk1.6

Jdk1.7

Jdk1.8

设置堆内存大小

通常,-Xms 和-Xmx 这两个参数配置为相同的值。 目的是提高性能,而无需在Java垃圾收集机制清理堆区后重新划分和计算堆区的大小。

一旦堆区内存大小超过-Xmx指定的最大内存,就会抛出异常。

年轻一代和老一代

JVM中存储的Java对象可以分为两类:

类方法中可以直接调用对象变量_实例方法可直接调用超类的类方法_类方法能用实例和类名调用

对象分配过程

为新对象分配内存是一项非常严格且复杂的任务。 JVM设计者不仅需要考虑如何以及在哪里分配内存,还需要考虑GC,因为内存分配算法与内存回收算法密切相关。 内存回收后内存空间会产生内存碎片吗?

新对象首先放置在伊甸园中。 该区域有大小限制。

当伊甸园的空间被填满时,程序需要再次创建对象。 JVM的垃圾收集器会对Eden 进行垃圾收集(),销毁Eden 中不再被其他对象引用的对象,然后加载新的。 该物体被放置在伊甸公园。

然后将Eden中剩余的对象移动到幸存者区s0。

如果再次触发垃圾回收,上次幸存下来的会被放到 s0区。 如果没有被回收,就会被放到 s1区。

如果再次进行垃圾回收,则会被放回 s0区域,然后再进入 s1区域。

我什么时候可以去退休社区? 次数可以设定。 默认15次,设置-Xx:=N。

退休区,相对悠闲。 当退休区内存不足时,会再次触发GC:Major GC,清理退休区内存。

如果在退休区执行Major GC后,发现对象仍然无法保存,就会出现OOM异常。

次要GC、全面GC

JVM在进行GC时,并不是每次都将以上三个内存区域一起回收。 大多数时候,它会回收新一代。

对于VM的实现来说,其中的GC根据回收区域分为两种:一种是部分回收(GC),另一种是全堆回收()

全堆收集(Full GC):收集整个J​​ava堆和方法区的垃圾收集。

年轻代GC(Minor GC)触发机制 老年代GC(Major GC/Full GC)触发机制 内存分配策略

如果Eden诞生后对象仍然存活,并且经过第一次Minor GC并且可以容纳,则会将其移动到该空间,并将对象年龄设置为1。对象每在该区域存活一次,其年龄就会增加到 1 年。 当它的年龄达到一定程度时(默认是15岁,其实每个JVM和每次GC都不同),就会晋升到老年龄。 时代。

不同年龄段的对象分配原则如下:

TLAB为什么是TLAB什么是TLAB

虽然并非所有对象实例都能成功在TLAB中分配内存,但JVM确实将TLAB作为内存分配的首选。

堆空间参数设置

-XX:+PrintFlagsInitial  //查看所有的参数的默认初始值
-XX:+PrintFlagsFinal  //查看所有的参数的最终值(可能会存在修改,不再是初始值)
-Xms  //初始堆空间内存(默认为物理内存的1/64)
-Xmx  //最大堆空间内存(默认为物理内存的1/4)
-Xmn  //设置新生代的大小。(初始值及最大值)
-XX:NewRatio  //配置新生代与老年代在堆结构的占比
-XX:SurvivorRatio  //设置新生代中Eden和S0/S1空间的比例
-XX:MaxTenuringThreshold  //设置新生代垃圾的最大年龄
-XX:+PrintGCDetails //输出详细的GC处理日志
//打印gc简要信息:①-Xx:+PrintGC ② - verbose:gc
-XX:HandlePromotionFalilure://是否设置空间分配担保

堆是分配对象的唯一选择吗?

随着JIT编译的发展和逃逸分析技术的逐渐成熟,栈分配和标量替换优化技术会导致一些微妙的变化,所有对象到堆的分配会逐渐变得不那么绝对。

在Java虚拟机中,众所周知,对象是在Java堆中分配内存的。 但有一种特殊情况,即如果经过逃逸分析()后发现某个对象没有逃逸方法,则可能会优化为在栈上分配。 这样就无需在堆上分配内存,也无需进行垃圾回收。 这也是最常见的堆外存储技术。

逃逸分析的基本行为是分析对象的动态范围:

public class EscapeAnalysis {

    public EscapeAnalysis obj;

    
    public EscapeAnalysis getInstance() {
        return obj == null ? new EscapeAnalysis() : obj;
    }

    
    public void setObj() {
        this.obj = new EscapeAnalysis();
    }

    
    public void useEscapeAnalysis() {
        EscapeAnalysis e = new EscapeAnalysis();
    }

    
    public void useEscapeAnalysis2() {
        EscapeAnalysis e = getInstance();
    }
}

使用转义分析,编译器可以对代码进行如下优化:

如果本文对您有帮助或启发,请帮忙分享、收藏、点赞、阅读。 您的支持是我坚持下去的最大动力!