Java 对象的内存布局

目录

一. 前言

二. Java 对象的内存布局

三. Java 对象结构

3.1. 对象头

3.1.1. Mark Word

3.1.2. 类型指针(Class Metadata Pointer)

3.1.3. 数组长度(Length)

3.2. 实例数据

3.3. 对齐填充(Padding)

四. JVM 之 指针压缩

4.1. 压缩指针的由来

4.2. 如何压缩指针

4.3. 如何进一步扩大寻址空间

4.4. JVM 压缩指针参数

五. Java 对象大小计算

5.1. 对象大小计算公式

5.2. 对象分析

5.3. 非数组对象,开启指针压缩

5.4. 非数组对象,关闭指针压缩

5.5. 数组对象开启指针压缩

5.6. 数组对象关闭指针压缩

六. 总结


一. 前言

    作为一名 Java 程序员,我们在日常工作中使用这门面向对象的编程语言时,做的最频繁的操作大概就是去创建一个个的对象了。对象的创建方式有很多,可以通过 new、Spring 管理 Bean、反射、clone、反序列化等不同方式来创建,但最终使用时对象都要被放到内存中,那么你知道在内存中的 Java 对象是由哪些部分组成以及是怎么存储的吗?这篇文章可带你深入了解 Java 对象的内存布局。

二. Java 对象的内存布局

    Java 对象的内存布局分为两种,普通对象和数组对象。

通过图中可以看出,数组对象只是在对象头里多了数组长度这一项,普通对象(非数组对象)没有这一项,也不分配内存空间。

三. Java 对象结构

在JVM中,对象在内存中的布局分为三块区域:对象头实例数据对齐补全

3.1. 对象头

对象头由三部分组成:

  1. Mark Word:存储自身的运行时数据,例如 HashCode、GC 年龄、锁相关信息等内容。
  2. Class Metadata Pointer:类型指针指向它的类元数据的指针。
  3. Length:记录数组长度。只有对象是数组的情况下,才有这部分数据,若对象不是数组,则没有这部分,不分配空间。

3.1.1. Mark Word

    用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持久的锁、偏向线程的 ID 等,通过存储的内容得知对象头是锁机制和 GC 的重要基础。

32位和64位的Mark Word

32位虚拟机的 Mark Word 的字节分配:

  1. 无锁 —— 对象的 hashcode:25bit;存放对象分代年龄:4bit; 存放是否偏向锁的标志位:1bit; 存放锁标志位为01:2bit。
  2. 偏向锁 —— 在偏向锁中划分更细。开辟 25bit 的空间,其中存放线程 ID:23bit;存放Epoch:2bit;存放对象分代年龄:4bit;存放是否偏向锁标志:1bit (0表示无锁,1表示偏向锁);锁的标志位为01:2bit。
  3. 轻量级锁 —— 在轻量级锁中直接开辟 30bit 的空间存放指向栈中锁记录的指针,2bit 存放锁的标志位,其标志位为00。
  4. 重量级锁 —— 在重量级锁中和轻量级锁一样,30bit 的空间用来存放指向重量级锁的指针,2bit 存放锁的标志位,其标志位为10。
  5. GC 标记 —— 开辟 30bit 的内存空间却没有占用,2bit 空间存放锁标志位为11。其中无锁和偏向锁的锁标志位都是01,只是在前面的 1bit 区分了这是无锁状态还是偏向锁状态。

64位虚拟机的 Mark Word 的字节分配:

  1. 无锁 —— unused:25bit;对象的 hashcode:31bit;Cmc_free:1bit;存放对象分代年龄:4bit; 存放是否偏向锁的标志位:1bit; 存放锁标志位为01:2bit。
  2. 偏向锁 —— 偏向线程 ID:54bit;存放Epoch:2bit;Cmc_free:1bit;存放对象分代年龄:4bit;存放是否偏向锁标识:1bit (0表示无锁,1表示偏向锁);锁的标志位为01:2bit。
  3. 轻量级锁 —— 在轻量级锁中直接开辟 62bit 的空间存放指向栈中锁记录的指针,2bit 存放锁的标志位,其标志位为00。
  4. 重量级锁 —— 在重量级锁中和轻量级锁一样,62bit 的空间用来存放指向重量级锁的指针,2bit 存放锁的标识位,为10。
  5. GC 标记 —— 开辟 62bit 的内存空间却没有占用,2bit 空间存放锁标志位为11。其中无锁和偏向锁的锁标志位都是01,只是在前面的 1bit 区分了这是无锁状态还是偏向锁状态。

3.1.2. 类型指针(Class Metadata Pointer)

    类型指针指向类的元数据地址,JVM 通过这个指针确定对象是哪个类的实例。32位的 JVM 占32位,4个字节,64位的 JVM 占64位,8个字节,但是64位的 JVM 默认会开启指针压缩,压缩后也只占4字节。

    如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的 JVM 将会比32位的JVM 多耗费50%的内存。所以会默认开启指针压缩(如不开启,类型指针将占用8字节),UseCompressedOops 是默认开启的,该参数表示开启指针压缩,会将原来64位的指针压缩为32位(即由原8字节压缩到4字节大小)。

-XX:+UseCompressedClassPointers //开启压缩类指针
-XX:-UseCompressedClassPointers //关闭压缩类指针

// 这个JVM参数依赖UseCompressedOops这个参数,UseCompressedOops开启,UseCompressedClassPointers默认开启,可手工关闭,
// UseCompressedOops关闭,UseCompressedClassPointers不管开启还是关闭都不生效即不压缩。

3.1.3. 数组长度(Length)

    如果对象是普通对象(非数组对象),则没有这部分,不占用空间。如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着 JVM 架构的不同而不同:32位的JVM上,长度为32位(4字节);64位 JVM 则为64位(8字节)。64位 JVM 如果开启+UseCompressedOops 选项,该区域长度也将由64位压缩至32位。

3.2. 实例数据

普通对象:实例数据。

数组对象:数组中的实例数据。

实例数据存放的是非静态的属性,也包括父类的所有非静态属性(private 修饰的也在这里,不区分可见性修饰符),基本类型的属性存放的是具体的值,引用类型及数组类型存放的是引用指针。

实例数据不同的类型所占的空间不同:

数据类型占用空间
char2个字节
boolean1个字节
byte1个字节
short2个字节
int4个字节
long8个字节
float4个字节
double8个字节
对象引用

对象指针压缩默认是被开启的,占用4个字节,配置 JVM 参数

-XX:+UseCompressedOops 后,占用8个字节

64 位的 JVM 默认开启普通对象指针压缩 -XX:+UseCompressedOops (OOP 即 ordinary object pointer),由原8字节压缩到4字节大小。

3.3. 对齐填充(Padding)

    用于补齐对象内存长度的。因为 JVM 要求 Java 代码的对象必须是 8bit 的倍数。如果一个对象用不到 8N 个字节则需要对其填充,以此来补齐对象头和实例数据占用内存之后剩余的空间大小。如果对象头和实例数据已经占满了 JVM 所分配的内存空间,那么就不用再进行对齐填充了。所有的对象分配的字节总 SIZE 需要是8的倍数,如果前面的对象头和实例数据占用的总 SIZE 不满足要求,则通过对齐数据来填满。

四. JVM 之 指针压缩

4.1. 压缩指针的由来

    计算机操作系统分32位和64位,这里的位在计算机里是用0和1来表示的,用32个(或64个)二进制0和1的组合来表示内存地址。

    以32位为例,在普通的内存中,对象的大小最小是以1字节来计算的,通过0和1的排列组合,能够表示寻址的内存空间最大就是2^{32}个,换算成内存空间就是2^{32} / 1024 / 1024 / 1024 = 4G,也就是说32位的操作系统最大能寻址的内存空间只有4G。

    同理,64位的操作系统(查阅资料显示其实没有用到64位,最多只用到了48位,这个可自行查阅资料,反正肯定比32位大的多)2^{48} / 1024 / 1024 / 1024 / 1024 = 256TB,这样内存就足够大了,但是目前还没有厂商能生产出这么大的内存。

    4G 对于现在的 Java 应用系统来说,内存已经算小的了,那我们就会想到使用64位的系统,这样内存就可以更大了,但是当我们准备将32位系统切换到64位系统,起初我们可能会期望系统性能会立马得到提升,但现实情况可能并不是这样的,为什么呢?

  1. 32位系统对象指针是4字节,64位系统对象指针是8字节(1位表示1bit,8个bit 表示1字节),这样64位系统中的对象引用占用的内存空间是32位系统中的两倍大小,因此间接的导致了在64位系统中更多的内存消耗以及更频繁的 GC 发生,GC 占用的 CPU 时间越多,那么我们的应用程序占用 CPU 的时间就越少,响应会变慢,吞吐量会降低。
  2. 对象的引用变大了,那么 CPU 可缓存的对象相对就少了,降低了 CPU 缓存命中率,增加了对内存的访问,CPU 对 CPU 缓存的访问速度可比对内存的访问速度快太多了,所以大量的对内存访问,会降低 CPU 的执行效率,增加了执行时间,从而影响性能。

既然32位系统内存不够,64位内存够但又影响性能,那有没有折中方案来解决这两个问题呢,于是聪明的 JVM 开发者想到了利用压缩指针,在64位的操作系统中利用32位的对象指针引用获得超过 4G 的内存寻址空间。

4.2. 如何压缩指针

    由于在 JVM 里,对象都是以8字节对齐的(即对象的大小都是8的倍数),所以不管用32位还是64位的二进制表示,末尾3位始终都是0。既然 JVM 已经知道了这些对象的内存地址后三位始终是0,那么这些无意义的0就没必要在堆中继续存储。相反,我们可以利用存储0的这3位 bit 存储一些有意义的信息,这样我们就多出3位 bit 的寻址空间,也就是说如果我们继续使用32位来存储指针,只不过后三位原本用来存储0的 bit 现在被我们用来存放有意义的地址空间信息,当寻址的时候,JVM 将这32位的对象引用左移3位即可(后三位补0)。我们原本32位的内存寻址空间一下变成了35位,可寻址的内存空间变为 2^{35} / 1024 / 1024 / 1024 = 32G,也就是说在64位系统 JVM 的内存可扩大到 32G 了,基本上可满足大部分应用的使用了。

    所以在64位系统下,通过压缩指针我们可以继续使用32位来处理(引用指针由8字节可降低到4字节),存储的时候右移3位,寻址的时候左移3位,如下图所示:

    这样一来,JVM 虽然额外的执行了一些位运算但是极大的提高了寻址空间,并且将对象引用占用内存大小降低了一半,节省了大量空间,况且这些位运算对于 CPU 来说是非常容易且轻量的操作,可谓是一举两得。

4.3. 如何进一步扩大寻址空间

    前边提到我们在 Java 虚拟机堆中对象起始地址均需要对齐至8的倍数,不过这个数值我们可以通过 JVM 参数 -XX:ObjectAlignmentInBytes 来改变(默认值为8)。当然这个数值的必须是2的次幂,数值范围需要在 8 - 256 之间。

    正是因为对象地址对齐至8的倍数,才会多出3位 bit 让我们存储额外的地址信息,进而将 4G 的寻址空间提升至 32G。

    同样的道理,如果我们将 ObjectAlignmentInBytes 的数值设置为16呢?

    对象地址均对齐至16的倍数,那么就会多出4位 bit 让我们存储额外的地址信息。寻址空间变为 2^{36} / 1024 / 1024 / 1024 = 64G。

    通过以上规律,我们就能知道,在64位系统中开启压缩指针的情况,寻址范围的计算公式:4G * ObjectAlignmentInBytes = 寻址范围。

    但是并不建议大家贸然这样做,因为增大了 ObjectAlignmentInBytes 虽然能扩大寻址范围,但是这同时也可能增加了对象之间的字节填充,导致压缩指针没有达到原本节省空间的效果。

4.4. JVM 压缩指针参数

可以通过以下命令查看 Java 命令默认的启动参数:

java -XX:+PrintCommandLineFlags -version

通过下面这个命令,可以看到所有JVM参数的默认值:

java -XX:+PrintFlagsFinal -version

关于压缩指针的两个参数:

  • UseCompressedClassPointers:压缩类指针(开启时类指针占4字节,关闭时类指针占8字节);
  • UseCompressedOops:压缩普通对象指针(开启时引用对象指针占4字节,关闭时引用对象指针占8字节)。

Oops 是 Ordinary object pointers 的缩写,这两个参数默认是开启的,即                                        -XX:+UseCompressedClassPointers,-XX:+UseCompressedOops,也可手动设置,如下所示:

-XX:+UseCompressedClassPointers //开启压缩类指针
-XX:-UseCompressedClassPointers //关闭压缩类指针
-XX:+UseCompressedOops  //开启压缩普通对象指针
-XX:-UseCompressedOops  //关闭压缩普通对象指针

注:32位 HotSpot VM 是不支持 UseCompressedOops 参数的,只有64位 HotSpot VM 才支持。Oracle JDK 从6 update 23开始在64位系统上会默认开启压缩指针。

五. Java 对象大小计算

5.1. 对象大小计算公式

以下表格展示了对象中各部分所占空间大小,单位:字节。

类型所属部分占用空间大小(压缩开启)占用空间大小(压缩关闭)
Markwork对象头88
类型指针对象头48
数组长度对象头44
byte对象体11
boolean对象体11
short对象体22
char对象体22
int对象体44
float对象体44
long对象体88
double对象体88
对象引用指针对象体48
对齐填充对齐填充对象头+对象体是8的倍数?0 :8 -(对象头+对象体)% 8对象头+对象体是8的倍数?0 :8 -(对象头+对象体)% 8

计算公式:对象大小 = 对象头 + 对象体(对象是数组时,对象体的大小=引用指针占用空间大小 *对象个数) + 对齐填充。

64位操作系统 32G 内存以下,默认开启对象指针压缩,对象头是12字节,关闭指针压缩,对象头是16字节。内存超过 32G 时,则自动关闭指针压缩,对象头占16字节。

5.2. 对象分析

使用 JOL 工具分析 Java 对象大小:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>

常用类及方法:
查看对象内部信息: ClassLayout.parseInstance(obj).toPrintable();
查看对象外部信息:GraphLayout.parseInstance(obj).toPrintable();
查看对象占用空间总大小:GraphLayout.parseInstance(obj).totalSize();
查看类内部信息:ClassLayout.parseClass(Object.class).toPrintable()。

使用到的测试类:

@Setter
class Goods {
    private byte b;
    private char type;
    private short age;
    private int no;
    private float weight;
    private double price;
    private long id;
    private boolean flag;
    private String goodsName;
    private LocalDateTime produceTime;
    private String[] tags;
    public static String str;
    public static int temp;
}

5.3. 非数组对象,开启指针压缩

64位 JVM,堆内存小于 32G 的情况下,默认是开启指针压缩的。

public static void main(String[] args) {
    Goods goods = new Goods();
    goods.setAge((short) 10);
    goods.setNo(123456);
    goods.setId(111L);
    goods.setGoodsName("方便面");
    goods.setFlag(true);
    goods.setB((byte)1);
    goods.setPrice(1.5d);
    goods.setProduceTime(LocalDateTime.now());
    goods.setType('A');
    goods.setWeight(0.065f);
    goods.setTags(new String[] {"food", "convenience", "cheap"});
    Goods.str = "test";
    Goods.temp = 222;
    System.out.println(ClassLayout.parseInstance(goods).toPrintable());
}

先不看输出结果,按上面的公式计算一下对象的大小:

  • 对象头:8字节(Mark Word)+4字节(类指针)=12字节;
  • 对象体:1字节(属性 b)+ 2字节(属性 type)+ 2字节(属性 age)+ 4字节(属性 no)+ 4字节(属性 weight)+ 8字节(属性 price)+ 8字节(属性 id)+ 1字节(属性 flag) + 4字节(属性 goodsName 指针) + 4字节(属性 produceTime 指针) + 4字节(属性 tags 指针)= 42字节(注意:静态属性不参与对象大小计算);
  • 对齐填充:8 -(对象头+对象体)% 8 = 8 - (12 + 42) % 8 = 2字节;
  • 对象大小 = 对象头 + 对象体 + 对齐填充 = 12字节 + 42字节 + 2字节 = 56字节。

执行看运行结果:

com.star95.study.jvm.Goods object internals:
OFF  SZ                      TYPE DESCRIPTION               VALUE
  0   8                           (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                           (object header: class)    0x2000c043
 12   4                       int Goods.no                  123456
 16   8                    double Goods.price               1.5
 24   8                      long Goods.id                  111
 32   4                     float Goods.weight              0.065
 36   2                      char Goods.type                A
 38   2                     short Goods.age                 10
 40   1                      byte Goods.b                   1
 41   1                   boolean Goods.flag                true
 42   2                           (alignment/padding gap)   
 44   4          java.lang.String Goods.goodsName           (object)
 48   4   java.time.LocalDateTime Goods.produceTime         (object)
 52   4        java.lang.String[] Goods.tags                [(object), (object), (object)]
Instance size: 56 bytes
Space losses: 2 bytes internal + 0 bytes external = 2 bytes total

这里有一个特殊的地方,打印输出的属性顺序跟代码里的顺序不一致,这是因为 JVM 进行优化,也就是指令重排序,会根据属性类型的大小、执行的先后顺序对结果是否有影响、最小填充大小等因素计算出对象最小应占用的空间。

5.4. 非数组对象,关闭指针压缩

关闭压缩指针,类指针和引用对象指针都占8字节,推算一下对象大小:

  • 对象头:8字节(Mark Word)+ 8字节(类指针)= 16字节;
  • 对象体:1字节(属性 b)+ 2字节(属性 type)+ 2字节(属性 age)+ 4字节(属性 no)+ 4字节(属性 weight)+ 8字节(属性 price)+ 8字节(属性 id)+ 1字节(属性 flag) + 8字节(属性 goodsName 指针) + 8字节(属性 produceTime 指针) + 8字节(属性 tags 指针)= 54字节(注意:静态属性不参与对象大小计算);
  • 对齐填充:8 -(对象头+对象体)% 8 = 8 - (16 + 54) % 8 = 2字节;
  • 对象大小 = 对象头 + 对象体 + 对齐填充 = 16字节 + 54字节 + 2字节 = 72字节。

运行时增加JVM参数如下:

-XX:-UseCompressedClassPointers -XX:-UseCompressedOops

public class ObjectLayOut1 {
    public static void main(String[] args) {
        Goods goods = new Goods();
        goods.setAge((short) 10);
        goods.setNo(123456);
        goods.setId(111L);
        goods.setGoodsName("方便面");
        goods.setFlag(true);
        goods.setB((byte)1);
        goods.setPrice(1.5d);
        goods.setProduceTime(LocalDateTime.now());
        goods.setType('A');
        goods.setWeight(0.065f);
        goods.setTags(new String[] {"food", "convenience", "cheap"});
        Goods.str = "test";
        Goods.temp = 222;
        System.out.println(ClassLayout.parseInstance(goods).toPrintable());
    }
}

执行看运行结果:

com.star95.study.jvm.Goods object internals:
OFF  SZ                      TYPE DESCRIPTION               VALUE
  0   8                           (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   8                           (object header: class)    0x00000000175647b8
 16   8                    double Goods.price               1.5
 24   8                      long Goods.id                  111
 32   4                       int Goods.no                  123456
 36   4                     float Goods.weight              0.065
 40   2                      char Goods.type                A
 42   2                     short Goods.age                 10
 44   1                      byte Goods.b                   1
 45   1                   boolean Goods.flag                true
 46   2                           (alignment/padding gap)   
 48   8          java.lang.String Goods.goodsName           (object)
 56   8   java.time.LocalDateTime Goods.produceTime         (object)
 64   8        java.lang.String[] Goods.tags                [(object), (object), (object)]
Instance size: 72 bytes
Space losses: 2 bytes internal + 0 bytes external = 2 bytes total

5.5. 数组对象开启指针压缩

默认是开启压缩指针的,类指针和引用对象指针都占4字节,推算一下对象大小:

  • 对象头:8字节(Mark Word)+ 4字节(类指针) + 4字节(数组长度)= 16字节;
  • 对象体:4字节 * 3 = 12字节;
  • 对齐填充:8 -(对象头+对象体)% 8 = 8 - (16字节 + 12字节)% 8 = 4字节;
  • 对象大小 = 对象头 + 对象体 + 对齐填充 = 16字节 + 12字节 + 4字节 = 32字节。
public class ObjectLayOut1 {
    public static void main(String[] args) {
        Goods goods = new Goods();
        goods.setAge((short) 10);
        goods.setNo(123456);
        goods.setId(111L);
        goods.setGoodsName("方便面");
        goods.setFlag(true);
        goods.setB((byte)1);
        goods.setPrice(1.5d);
        goods.setProduceTime(LocalDateTime.now());
        goods.setType('A');
        goods.setWeight(0.065f);
        goods.setTags(new String[] {"food", "convenience", "cheap"});
        Goods.str = "test";
        Goods.temp = 222;
        Goods[] goodsArr = new Goods[3];
        goodsArr[0] = goods;
        System.out.println(ClassLayout.parseInstance(goodsArr).toPrintable());
    }
}

 执行看运行结果:

[Lcom.star95.study.jvm.Goods; object internals:
OFF  SZ                         TYPE DESCRIPTION               VALUE
  0   8                              (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                              (object header: class)    0x2000c18d
 12   4                              (array length)            3
 16  12   com.star95.study.jvm.Goods Goods;.<elements>         N/A
 28   4                              (object alignment gap)    
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

5.6. 数组对象关闭指针压缩

关闭压缩指针,类指针和引用对象指针都占8字节,推算一下对象大小:

  • 对象头:8字节(Markword)+8字节(类指针) + 4字节(数组长度)= 20字节;
  • 对象体:8字节 * 3 = 24字节;
  • 对齐填充:8 -(对象头+对象体)% 8 = 8 - (20+ 24) % 8 = 4字节;
  • 对象大小 = 对象头 + 对象体 + 对齐填充 = 20字节 + 24字节 + 4字节 = 48字节。

运行时增加 JVM 参数如下:

-XX:-UseCompressedClassPointers -XX:-UseCompressedOops
public class ObjectLayOut1 {
    public static void main(String[] args) {
        Goods goods = new Goods();
        goods.setAge((short) 10);
        goods.setNo(123456);
        goods.setId(111L);
        goods.setGoodsName("方便面");
        goods.setFlag(true);
        goods.setB((byte)1);
        goods.setPrice(1.5d);
        goods.setProduceTime(LocalDateTime.now());
        goods.setType('A');
        goods.setWeight(0.065f);
        goods.setTags(new String[] {"food", "convenience", "cheap"});
        Goods.str = "test";
        Goods.temp = 222;
        Goods[] goodsArr = new Goods[3];
        goodsArr[0] = goods;
        System.out.println(ClassLayout.parseInstance(goodsArr).toPrintable());
    }
}

执行看运行结果:

[Lcom.star95.study.jvm.Goods; object internals:
OFF  SZ                         TYPE DESCRIPTION               VALUE
  0   8                              (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   8                              (object header: class)    0x0000000017e04d70
 16   4                              (array length)            3
 20   4                              (alignment/padding gap)   
 24  24   com.star95.study.jvm.Goods Goods;.<elements>         N/A
Instance size: 48 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

通过以上对象分析,我们看到在开启压缩指针的情况下,对象的大小会小很多,节省了内存空间。

六. 总结

    通过以上的分析,基本已经把 Java 对象的结构讲清楚了,另外对象占用内存空间大小也计算出来了,有助于进行 JVM 调优分析,64位的虚拟机内存在 32G 以下时默认是开启压缩指针的,超过32G 自动关闭压缩指针,主要目的都是为了提高寻址效率。

    另外,本文是通过 JOL 工具计算对象占用空间的大小,不包括引用对象实际占用的内存大小,因为计算时是按引用对象的指针占用空间大小计算的,可能跟其他工具计算的结果不一样,具体跟工具的计算逻辑有关,比如跟 JDK 自带的 jvisualvm 工具通过堆 dump 出来看到的对象大小不一样,感兴趣的可自行验证。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/309564.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何在SpringBoot中优雅地重试调用第三方API?

1.引言 在实际的应用中,我们经常需要调用第三方API来获取数据或执行某些操作。然而,由于网络不稳定、第三方服务异常等原因,API调用可能会失败。为了提高系统的稳定性和可靠性,我们通常会考虑实现重试机制。 2.重试机制的必要性 第三方API调用可能面临各种不可预测的问题…

Win10下python3和python2同时安装并解决pip共存问题

特别说明&#xff0c;本文是在Windows64位系统下进行的&#xff0c;32位系统请下载相应版本的安装包&#xff0c;安装方法类似。 使用python开发&#xff0c;环境有Python2和 python3 两种&#xff0c;有时候需要两种环境切换使用&#xff0c;下面提供详细教程一份。 1、下载…

Centos7升级openssl到openssl1.1.1

Centos7升级openssl到openssl1.1.1 1、先查看openssl版本&#xff1a;openssl version 2、Centos7升级openssl到openssl1.1.1 升级步骤 #1、更新所有现有的软件包列表并安装最新的软件包&#xff1a; $sudo yum update #2、接下来&#xff0c;我们需要从源代码编译和构建OpenS…

Vue3 子传父 暴露数据 defineExpose

defineExpose 属性&#xff1a;用于将子组件中的数据和方法&#xff0c;暴露给父组件&#xff0c;父组件再配合 ref 属性使用。 语法格式 // 子组件&#xff1a;暴露数据和方法 defineExpose({ 数据, 数据, 方法 });// 父组件&#xff1a;使用数据和方法 ref名称.value.数据 …

领域专家精心讲解AI视频生成

大家好&#xff0c;我是herosunly。985院校硕士毕业&#xff0c;现担任算法研究员一职&#xff0c;热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名&#xff0c;CCF比赛第二名&#xff0c;科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的…

PPT插件-大珩助手-快速构建自己的图形

绘图板-快速构建自己的图形 通过手绘的方式&#xff0c;快速构建自己的想法和创意&#xff0c;通过在PPT中插入绘图&#xff0c;植入背景透明的绘图&#xff0c;点击画笔可切换橡皮擦&#xff0c;可以清空画板重新绘制。 素材库-存储图形 通过素材库存储自己的图形 图形调整…

【笔记------coremark】单片机上的跑分库coremark移植

coremark的官方仓库&#xff1a;https://github.com/eembc/coremark 官方收录了很多单片机的跑分情况&#xff1a;https://www.eembc.org/coremark/scores.php 这个是我建立的一个仓库&#xff0c;用来收集自己用到的一些单片机的跑分情况&#xff1a;https://gitee.com/wild_c…

2024.1.11每日一题

LeetCode 2645.构造有效字符串的最少插入数 2645. 构造有效字符串的最少插入数 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个字符串 word &#xff0c;你可以向其中任何位置插入 “a”、“b” 或 “c” 任意次&#xff0c;返回使 word 有效 需要插入的最少字…

招投标系统是Electron的纯内网编辑Office Word,可以设置部分区域可编辑,其他的地方不能编辑吗?

问题&#xff1a; 我们是招投标系统的开发公司&#xff0c;框架是用的Electron&#xff0c;需要在纯内网的环境下编辑Office Word&#xff0c;可以设置部分区域可编辑&#xff0c;其他的地方不能编辑吗&#xff08;如下红框位置&#xff09;并且在用户忘记填写一些区域的时候做…

Elasticsearch 地理空间搜索 - 远超 OpenSearch

作者&#xff1a;来自 Elastic Nathan_Reese 2021 年&#xff0c;OpenSearch 和 OpenSearch Dashboards 开始作为 Elasticsearch 和 Kibana 的分支。 尽管 OpenSearch 和 OpenSearch Dashboards 具有相似的血统&#xff0c;但它们不提供相同的功能。 在分叉时&#xff0c;只能克…

审批流、工作流、业务流、BPM 几个概念澄清

背景 每次听各类供应商售前专家在做产品宣讲时&#xff0c;经常看到牛人在讲坛上吐沫横飞&#xff0c;大讲各种流&#xff0c;信息流、业务流、物流、商流、资金流、单据流等&#xff0c;终于在《透明数字化供应链》一书中&#xff0c;看到对各类流的解释&#xff0c;现整理如…

ffmpeg 视频分辨率修改 质量压缩

随着手机像素的提高&#xff0c;拍摄视频也越来越大&#xff0c;10秒的视频动辄 二三十兆&#xff0c;这给视频传输和播放都带来了 诸多不变。一般都需要 前端或或者后端 对视频进行压缩。由于我这边前端是 H5&#xff0c;所以只能后端进行压缩&#xff0c; 采用主流压缩库采用…

JS逆向之加密参数定位

文章目录 前言加密参数的处理步骤加密参数的定位方法搜索断点XHR断点DOM断点EVENT断点 hook 前言 当我们对网络请求进行抓包分析之后&#xff0c;需要用开发者工具对加密参数进行全局搜索。当搜索不到加密参数的时候&#xff0c;应该采取什么解决方法去定位。 还有一个应用场…

水经微图安卓版APP正式上线!

在水经微图APP&#xff08;简称“微图APP”&#xff09;安卓版已正式上线&#xff01; 在随着IOS版上线约一周之后&#xff0c;安卓版终于紧随其后发布了。 微图安卓版APP下载安装 自从IOS版发布之后&#xff0c;就有用户一直在问安卓版什么时候发布&#xff0c;这里非常感谢…

【申请SSL证书】免费申请阿里云SSL证书

注意&#xff1a;申请 SSL证书的前提是有一个域名且备案了 第一部&#xff1a;申请免费证书 免费 CA 证书购买地址&#xff08;请戳这里&#xff09; 选择合适的选项如下图 为了解决免费证书近期存在的吊销、统计等问题&#xff0c;自2021年起&#xff0c;免费证书申请申请将…

创建并美化Github主页(内含组件)

目录 1、创建仓库 2、美化 1、包含多种 2、活动统计图 3、资料奖杯 4、文字的打字特效 5、中文网站卡片 6、贪吃蛇贡献图 7、可参考的页面 最近有想要写开源的打算了&#xff0c;计划了好久好久好久&#xff0c;不知道写啥(目前仍然不知道)…… 俗话说人活一张脸&#xff0…

什么是编程思路?如何训练提升自己的编程思路?

哈喽&#xff0c;大家上午好呀&#xff01;又和大家如期见面了&#xff01; 今天给大家分享改变编程思路的9条技巧。 1.拆分项目&#xff0c;再编程 先按大类写子程序&#xff0c;例如自动&#xff0c;手动&#xff0c;报警&#xff0c;然后子程序中写FB块&#xff0c;FC程序&…

【算法每日一练]-练习篇 #Tile Pattern #Swapping Puzzle # socks

目录 今日知识点&#xff1a; 二维前缀和 逆序对 袜子配对(感觉挺难的&#xff0c;又不知道说啥) Tile Pattern Swapping Puzzle socks Tile Pattern 331 题意&#xff1a;有一个10^9*10^9的方格。W表示白色方格&#xff0c;B表示黑色方格。每个(i,j)方的颜色由(i…

Python-12-正则

当然内容不是很全&#xff0c;可以参考: 正则表达式学习资料 https://blog.csdn.net/weixin_40907382/article/details/79654372

RAG:让大语言模型拥有特定的专属知识

作为一个在Chatbot领域摸爬滚打了7年的从业者&#xff0c;笔者可以诚实地说&#xff0c;在大语言模型的推动下&#xff0c;检索增强生成&#xff08;Retrieval Augmented Generation&#xff0c;RAG&#xff09;技术正在快速崛起。 RAG的搜索请求和生成式AI技术&#xff0c;为搜…
最新文章