细说jvm(三)、对象创建的内存分配

简介: 细说jvm(三)、对象创建的内存分配

对象创建的内存分配


在对象创建的时候给对象分配内存总共是可能有如下的几种可能:

(1)将对象分配在栈上 (2)使用TLAB (3)分配在eden

我们一点一点的来说下,每一点展开都是个知识点


栈上分配

这里需要先说的一个是逃逸分析,在计算机语言编译器优化原理中,逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他过程或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。而hotspot能够在动态加载方法的时候对代码进行逃逸分析,如果发现一个新对象的引用仅仅是在这个方法的范围内,那么这个对象的分配区域就会仅仅在栈上。为了证明我说的是对的,我需要用一段代码来证明一下,在代码开始之前,我先简单介绍几个会用到的jvm的参数,不然你可能会比较懵逼。

  • -Xss  这个参数是指明栈空间的大小,我们这里为了让一个栈有足够的大小,因此给2m的大小
  • -Xms  这个是堆的初始化大小
  • -Xmx  这个是堆的最大大小
  • -XX:+PrintGCDetails  开启打印垃圾回收日志
  • -XX:+UseConcMarkSweepGC 使用CMS垃圾回收器
  • -XX:+PrintGCDateStamps  打印GC发生的时间,用的humanbeing的方式
  • -XX:-DoEscapeAnalysis  关闭逃逸分析


我们仔细观察上面的参数,发现有的参数前面带着+或者-,jvm的参数有两类,一类是需要设置具体的值的,另外一类只是单纯的开启和关闭的,
单纯的开启和关闭就用的是+和-,+指的是开启,-指的是关闭,设置值的在后面跟上值就行了


上面是我使用的参数,具体的设置如下:


-Xss2048k
-Xms50m
-Xmx50m
-XX:+PrintGCDetails
-XX:+UseConcMarkSweepGC
-XX:+PrintGCDateStamps
-XX:-DoEscapeAnalysis


代码如下:


public static void main(String[] args) throws IOException {
        while (true) {
            new MyEntity(1, "a");
        }
}
// MyEntity类如下
public class MyEntity {
    private Integer id;
    private String name;
    public MyEntity(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}


然后我们开始跑main方法,如我们想的,GC会疯狂运行,因为我们关闭了逃逸分析,GC日志如下:


1686814433725.png


我们先不在本章教你读GC日志(这是个很长的话题),这里贴出来的目的只是证明关闭逃逸分析的影响而已,我们再从另外一方面证明堆内存中有很大量的MyEntity对象,我们打开终端,windows是打开cmd,输入jvisualVM,它的界面如下:


1686814441541.png


双击我画红框的进程,这个进程就是我们正在跑着的java程序,打开之后界面如下:


1686814447802.png


按顺序选择我画红框的按钮,之后你可以看到的如下图:


1686814455150.png


可以看到MyEntity实例数量在堆内存中非常多,其实它的数量应该是一会儿多一会儿少,因为在每次GC的时候总会有很多被回收掉(GC的细节我们下篇文章开始说)。

接下来我们证明下,逃逸分析开启之后,对象是有可能被分配到栈上的。

参数如下:注意仅仅修改最后一项的-为+号


-Xss2048k
-Xms50m
-Xmx50m
-XX:+PrintGCDetails
-XX:+UseConcMarkSweepGC
-XX:+PrintGCDateStamps
-XX:+DoEscapeAnalysis


代码和上面一样,但是这次跑起来根本没有GC日志输出,我的IDEA控制台干干净净一片:


1686814464256.png


同时,继续看jvisualVM的内存监控如下,你会发现内存中根本没有MyEntity的实例


1686814472183.png


TLAB分配


TLAB全称是Thread Local Allocate Buffer,即线程本地分配缓存区,这是一个线程专用的内存分配区域。 由于对象一般会分配在堆上,而堆是所有线程共享的。因此在同一时间,可能会有多个线程在堆上申请空间。因此,每次对象分配都必须要进行同步(jvm采用CAS配上失败重试的方式保证更新操作的原子性),而在竞争激烈的场合分配的效率又会进一步下降。JVM使用TLAB来避免多线程冲突,在给对象分配内存时,每个线程使用自己的TLAB,这样可以避免线程同步,提高了对象分配的效率。

TLAB本身占用Eden区空间,在开启TLAB的情况下,虚拟机会为每个Java线程分配一块TLAB空间。参数-XX:+UseTLAB开启TLAB,默认是开启的。TLAB空间的内存非常小,缺省情况下仅占有整个Eden空间的1%,当然可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。

由于TLAB空间一般不会很大,因此大对象无法在TLAB上进行分配,总是会直接分配在堆上。TLAB空间由于比较小,因此很容易装满。比如,一个100K的空间,已经使用了80KB,当需要再分配一个30KB的对象时,肯定就无能为力了。这时虚拟机会有两种选择,第一,废弃当前TLAB,这样就会浪费20KB空间;第二,将这30KB的对象直接分配在堆上,保留当前的TLAB,这样可以希望将来有小于20KB的对象分配请求可以直接使用这块空间。实际上虚拟机内部会维护一个叫作refill_waste的值,当请求对象大于refill_waste时,会选择在堆中分配,若小于该值,则会废弃当前TLAB,新建TLAB来分配对象。这个阈值可以使用TLABRefillWasteFraction来调整,它表示TLAB中允许产生这种浪费的比例。默认值为64,即表示使用约为1/64的TLAB空间作为refill_waste。默认情况下,TLAB和refill_waste都会在运行时不断调整的,使系统的运行状态达到最优。如果想要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,并使用-XX:TLABSize手工指定一个TLAB的大小。 -XX:+PrintTLAB可以跟踪TLAB的使用情况。

一般不建议手工修改TLAB相关参数,推荐使用虚拟机默认行为。


为什么说不推荐修改TLAB相关的东西呢?这是因为TLAB的优化是极其难控制的,在不同的业务场景下对象创建的情况差别会非常大,
因此我们一般不会优化这里的参数,只是使用默认的参数。


分配在eden区域


当jvm判断不能使用前两种分配方式的时候就会触发这种分配方式,在这种情况下,会有两种选择:


1、指针碰撞:所谓的连续内存是指Java堆中的内存是绝对规整的,用过的内存在一边,空闲的内存在另一边。中间有个指针作为分界点,
这时如果要分配新内存,只要指针向空闲的内存一方移动一下就可以了。这种分配内存的方式就叫指针碰撞。
2、空闲列表:如果Java堆中的内存并不是完整的,也就是不是连续的。这时使用的内存和空闲的内存没有任何规则,无法用指针碰撞的方
式来分配内存。这时虚拟机只能采取其它办法来标识出哪些内存是使用的,哪些内存是空闲的,所以虚拟机就要维护一个列表,用来存储哪些
内存是空闲的,分配内存时,只要从列表中划分一块区域存储对象实例,并更新列表上的记录就可以了。这种方式就叫空闲列表


具体使用的是哪种,取决于使用的垃圾回收器使用的哪种算法,一般来说,我们的hotspot在使用CMS和G1垃圾回收器的时候都是用的第二种。

其实分配在eden这种说法并不绝对,因为当一个对象非常大大到了eden都放不下的时候,这时候还要保证这个分配一定成功,这时候就会让这个对象进入老年代,这个是jvm的内存分配担保机制。

对象创建时候的内存分配就说这么多,下一篇我们开始说垃圾回收

目录
相关文章
|
4天前
|
存储 缓存 算法
深入浅出JVM(二)之运行时数据区和内存溢出异常
深入浅出JVM(二)之运行时数据区和内存溢出异常
|
4天前
|
存储 安全 Java
Python中的引用和赋值机制允许变量引用内存中的对象,并通过引用计数来管理对象的生命周期
【5月更文挑战第14天】Python中的变量是对象引用,不存储数据,而是在内存中创建对象。赋值操作创建新变量并使其指向已有对象。引用计数用于管理对象生命周期,引用数为0时对象被回收。理解这些机制对编写高效Python代码很重要。
18 6
|
4天前
|
Java Linux Arthas
linux上如何排查JVM内存过高?
linux上如何排查JVM内存过高?
594 0
|
4天前
|
存储 缓存 算法
深入浅出JVM(十四)之内存溢出、泄漏与引用
深入浅出JVM(十四)之内存溢出、泄漏与引用
|
4天前
|
存储 算法 NoSQL
深入浅出JVM(十一)之如何判断对象“已死”
深入浅出JVM(十一)之如何判断对象“已死”
|
4天前
|
存储 缓存 算法
深入浅出JVM(一)之Hotspot虚拟机中的对象
深入浅出JVM(一)之Hotspot虚拟机中的对象
|
4天前
|
存储 缓存 Java
JVM 运行时内存篇
JVM 运行时内存篇
9 0
|
4天前
|
Arthas 监控 Java
JVM工作原理与实战(三十一):诊断内存泄漏的原因
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了诊断内存溢出的原因、MAT内存泄漏检测的原理等内容。
18 0
|
4天前
|
存储 Arthas 监控
JVM工作原理与实战(三十):堆内存状况的对比分析
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了堆内存状况的对比分析、产生内存溢出的原因等内容。
15 0
|
4天前
|
Arthas Prometheus 监控
JVM工作原理与实战(二十九):监控内存泄漏的工具
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了解决内存溢出的步骤、Top命令、VisualVM、Arthas、Prometheus + Grafana等内容。
21 0