【JVM系列】- 穿插·对象的实例化与直接内存

对象的实例化与直接内存

😄生命不息,写作不止
🔥 继续踏上学习之路,学之分享笔记
👊 总有一天我也能像各位大佬一样
🌝分享学习心得,欢迎指正,大家一起学习成长!

在这里插入图片描述

文章目录

  • 对象的实例化与直接内存
    • 创建对象的方式
      • ① 使用new关键字
      • ② 通过反射机制
      • ③ 使用克隆的方式
      • ④ 反序列化
    • 创建对象的步骤
      • ① 判断对象对应的类是否类加载
      • ② 为对象分配内存
      • ③ 处理并发安全问题
      • ④ 初始化
      • ⑤ 设置对象的对象头
      • ⑥ 执行init方法进行初始化
    • *对象的布局
      • 1). 对象头(Object Header)
      • 2). 实例数据(Instance Data)
      • 3). 对齐填充(Padding)
      • 案例理解
    • 对象的访问定位
      • 对象访问的两种方式
        • 1). 句柄访问
        • 2). 指针访问(Hotspot采用此方法)
    • 直接内存(Direct Memory)
      • 直接内存概述
      • 直接内存的性能
      • 直接内存的OOM
    • 总结

在Java中,对象实例化是创建一个类的实例的过程。

实例化(instantiate)是指在面向对象的编程中,把用类创建对象的过程称为实例化。是将一个抽象的概念类,具体到该类实物的过程。实例化过程中一般由**类名 对象名 = new 类名(参数1,参数2…参数n)**构成。

创建对象的方式

在Java中,创建对象的方式有许多种,简单的概括都有哪些方法。

① 使用new关键字

使用new 来创建方法是最常见的一种创建方式,这种方法会调用构造方法(默认是无参构造方法),如果有自定义构造方法,就会使用自定义构造方法来创建对象。

MyClass myObject = new MyClass();

还有一种常见的方式,就是使用类的静态方法,这种方式,实际上在静态方法内部也是使用了new的方式来创建,这里可以是无参构造也可以是自定义的含参构造器。
还有一种也是new方式的变形,就是通过一个专门的工厂类或者静态方法来创建对象。

MyClass myObject = MyClassFactory.createMyClass();

② 通过反射机制

使用 Java 的反射机制,可以在运行时获取类的信息并创建对象。但是这种方式只能是使用空参的构造器,权限还必须是public。

Class<?> myClass = Class.forName("com.example.MyClass");
MyClass myObject = (MyClass) myClass.newInstance();

在Java9之后就不推荐使用了,可以选择使用Constructor的newInstance(xx)。clazz.getDeclaredConstructor().newInstance(),这个也是反射的方式,可以调用空参、带参的构造器,对于权限没有要求。

③ 使用克隆的方式

通过实现 Cloneable 接口并覆盖 clone 方法,可以创建对象的副本。这种方式不调用任何构造器。

MyClass originalObject = new MyClass();
MyClass clonedObject = (MyClass) originalObject.clone();

④ 反序列化

反序列化是将对象从其序列化形式转换回原始对象的过程。在Java中,对象可以通过序列化将其状态保存到文件或通过网络传输。反序列化则是从这些序列化的数据中重新构建对象。

除了以上方法,还有其他的创建方式,比如三方库等。

创建对象的步骤

我们通过字节码来看一下实例化对象的过程。
在这里插入图片描述

对于上图中的Object s = new Object();,首先我们可以将其分成三个部分来看,第一个部分Object,这个是存放在方法区中的,也就是存储着类的信息、常量池、静态变量等等;第二部分就是s,这个是存在Java栈中,这是个引用对象,在栈中的本地变量表中存放对象的引用地址,指向Java的对象实例数据;而第三部分new Object(),这是对象的实例数据,存在Java堆中。
接下来分析一下字节码

// new创建一个对象,#2是常量池中对应 java/lang/Object 类的索引
0 new #2 <java/lang/Object>
// dup是复制栈顶元素。在这里,复制了刚刚创建的新对象的引用。
3 dup
// 调用对象的构造方法。#1 是常量池中对应 <init> 构造方法的索引,V 表示无返回值。这里实际上调用了 java/lang/Object 类的构造方法,对新创建的对象进行初始化。
4 invokespecial #1 <java/lang/Object.<init> : ()V>
// 将栈顶元素(即新创建的对象的引用)存储到本地变量1中。
7 astore_1
// 返回。这里返回的是 void 类型,因为在 Java 中构造方法没有显式的返回值。
8 return

以上是从字节码的过程,接下来用执行过程来介绍,以下分为6个步骤来。

① 判断对象对应的类是否类加载

首先,要实例化一个对象,必须加载其对应的类。类加载是Java程序启动的一部分。当程序首次引用一个类时,类加载器负责加载这个类的字节码到内存中。这就是加载类。类加载的下一个阶段是链接。在链接过程中,将为类的静态变量分配存储空间,并且如果存在父类,还会链接到父类。链接阶段将符号引用转化为直接引用。在链接中还有验证、准备、解析。 在类的初始化阶段,执行类构造器 方法。这是一个特殊的静态方法,由编译器生成,包含类的静态字段的初始化和静态代码块的执行。初始化是按需进行的,即只有在首次实例化类的对象或者首次访问类的静态成员时才会触发。如果一个类有父类,那么会首先初始化父类。

这个部分在之前的文章《类加载子系统与加载过程》就有描述过,这里就是简单复述一下。回归正传,当虚拟机遇到一条new指令的时候,首先就会先去检查这条指令的参数能否在Metaspace的常量池中,定位到一个类的符号引用(就如以上字节码中的0 new #2 <java/lang/Object>这行字节码,#2就是这个Object对象的符号引用),并且会检查符号引用对应的类是否已经被加载、解析和初始化。如果没有,将会重新类加载,会在双亲委派模式下,使用类加载器去查找对应的.class文件。如果没有找到就会抛出ClassNotFoundException异常。

② 为对象分配内存

一旦类初始化完成,JVM 就会为对象分配内存。内存分配的方式可以是在堆上分配,也可能是栈上分配,具体取决于对象的生命周期和大小。
再分配内存,首先需要计算对象占用的空间大小,接着就是在堆中划分一块内存给新对象,如果实例化成员变量是引用变量,仅分配引用变量空间即可,即4/8个字节大小。

关于字节大小,如果操作系统是64位
boolean:占1个字节、byte:占1个字节
char:占2个字节、short:占2个字节
int:占4个字节、float:占4个字节
long:占8个字节、double:占8个字节
引用变量:占8个字节

对于对象内存的分配,还需要看内存是否规整。
如果内存是规整的,那么JVM将采用的是指针碰撞(Pointer Bumping)[1]来为对象分配内存。

什么是指针碰撞?
指针碰撞(Pointer Bumping)是一种内存分配和垃圾回收的实现方式,通常用于实现可移植的、线程安全的垃圾回收器。它的工作原理是通过移动指针来分配和回收内存。在使用指针碰撞时,整个可用的内存被看作一个大的连续块。实际上意思就是将所有使用过的内存放在一边,空闲的内存存在另一边,中间放着一个指针作为分界点的指示器,分配内存的话,就是把指针向空闲内存挪动一段与对象大小相同的距离。如果垃圾收集器是选择Serial、ParNew这种基于压缩算法,虚拟机采用这种分配方式,一般使用带有compact(整理)过程的收集器是,使用指针碰撞。

然而,如果内存是不规整的,也就是说用过的和没用过的内存相互交错,虚拟机就会采用空闲列表法来为对象分配内存,也就是虚拟机就需要维护一个列表,记录了哪些内存块是可用的,再分配的时候会从列表中去找到一块足够大的空间去划分对象实例,并更新列表上的内容,这种的分配方式就是空闲列表(Free List)。

选择哪种分配方式是由Java堆是否规整决定的,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能来决定。

③ 处理并发安全问题

在JVM创建对象的过程中,是会涉及一些并发安全问题,并且也有一些解决策略。

  • 采用CAS失败重试、区域加锁保证原子性
    • 类加载是多线程环境下的一个关键问题。JVM使用类加载锁(Class Loading Lock)来保证在同一时刻只有一个线程能够加载一个类。这个锁是全局锁,确保每个类在同一时刻只能被一个线程加载。
  • 为每个线程都预分配一块TLAB
    • 通过-XX:+/-UserTLAB参数设定

④ 初始化

初始化分配到的空间就是最开始的默认初始化进行默认值设置,这能保证对象实例字段在不赋值时就可以直接使用。

属性赋值的操作有几部分,首先是属性的默认初始化,接着就是显示初始化、代码块中的初始化,然后是由构造器进行初始化。

⑤ 设置对象的对象头

在这一步就会将对象的所属类(元数据信息)、对象的HashCode和对象的GC信息、锁信息等数据存储到对象的对象头中,这个过程的具体设置方式取决于JVM实现。

⑥ 执行init方法进行初始化

在Java程序的视角来看,初始化才是正式开始,初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。这一部分就是将对象属性进行显示初始化、代码块中的初始化、构造器的初始化。

那么怎样才算对象创建完成呢?实际上,对象的创建从经历了加载类元信息,为对象分配内存,处理并发问题,属性的默认初始化,设置对象头的信息,数据的显示初始化、代码中初始化以及构造器初始化的几个过程之后才算对象已经创建完成,当然,如果说对象在默认初始化完毕就算创建完成也是可以的。

*对象的布局

在Hotspot 虚拟机中,对象在内存中的存储布局,可以分为三个区域:对象头(Object Header)、实例数据(Instance Data)、对齐填充(Padding)。在对象头中包含了标记字(mark word)、类指针(klass word)和 数组长度(array length)。synchronized主要是跟对象头有关系,也就是通过mark word的字节位数来表示各种锁状态。
未命名文件 (17).png

关于锁的知识点之前在《【多线程与高并发】- synchronized锁的认知》就已经介绍过了。

1). 对象头(Object Header)

在HotSpot虚拟机中,Java对象的头部包含了一些用于管理对象的元数据信息。其中主要是包含以下两个部分。

  • Mark Word(标记字): 占用 8 字节,包含了对象的一些状态信息,比如哈希值、锁状态标志、线程持有的锁、线程ID、偏向锁时间戳、GC 分代年龄等。Mark Word 的内容在对象的生命周期中可能会发生变化。
  • klass word(类型指针):Class对象的类型指针, 占用 4 字节或 8 字节,Jdk1.8默认开启指针压缩后为4字节,关闭指针压缩(-XX:-UseCompressedOops)后,长度为8字节1。其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址1。

如果是数组的话,还需要记录数组的长度。

2). 实例数据(Instance Data)

这是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(也包括了继承父类的和自身的字段)。如果存储的数组对象,会包含一个用于记录数组长度的字段以及存储了实际数组元素的部分。JVM会根据对象的布局灵活地调整内存布局,减小对象头和实例数据的总体大小,从而降低内存占用。对于相同宽度的字段会被分配到一起,父类定义的变量会出现在子类之前,如果CompactFields参数为true(也是默认值),子类的窄变量可能插入父类的变量空隙。

实例数据主要包括对象的各种成员变量,包括基本类型和引用类型。基本类型直接存储内容,引用类型则是存储的指针,static类型的变量会放到类中,而不是放到实例数据里。

3). 对齐填充(Padding)

对齐填充并不是必须的,这是一种优化的概念,是作为一种占位符的作用。主要是为了满足硬件对齐的要求,提高访问效率和性能。通过插入一些额外的字节,使得对象的起始地址符合特定的对齐要求。这样可以确保对象的实例数据按照硬件对齐规则排列,从而提高内存访问的效率。

案例理解

这里我准备一个案例,在Customer类中设置了几个变量,并继承父类User,其中有一个引用变量Account类。通过Start#main()来实现本次的案例。

// 父类
public class User {String username;
}
// 子类
public class Customer extends User {int id = 1;String name;Account account;{name = "默认客户";}public Customer() {account = new Account();}
}
// 引用的类
public class Account {int ids;double money;String tab;
}
public class Start {public static void main(String[] args) {Customer customer = new Customer();/*java对象的内存布局以及使用ClassLayout查看布局*/System.out.println("Customer:" + ClassLayout.parseInstance(customer).toPrintable());    }
}

这里我引入了ClassLayout来查看Java对象内存布局的,需要引入以下坐标。

<!--  java对象的内存布局以及使用ClassLayout查看布局  -->
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.17</version>
</dependency>

通过运行Start#main()可以清晰看到所占用的字节信息,对于Customer对象,继承了User类,在创建实例的时候会把父类的属性也一并加载过来。首先,对象头是固定的8(标记字)+4(类型指针)字节,实例数据由于会加载父类属性,一共是16字节(对于引用类型,JVM是64位应该是占8字节,但是采用了指针压缩,所以只占了4字节),8+4+16=28字节,不满足对齐要求,因此还需要引入对齐填充占4个字节,一共就是32字节。

Customercom.lyd.testboot.jvm.objectdemo.Customer object internals:
OFF  SZ                                      TYPE DESCRIPTION               VALUE0   8                                           (object header: mark)     0x0000000000000001 (non-biasable; age: 0)8   4                                           (object header: class)    0xf800c18212   4                          java.lang.String User.username             null16   4                                       int Customer.id               120   4                          java.lang.String Customer.name             (object)24   4   com.lyd.testboot.jvm.objectdemo.Account Customer.account          (object)28   4                                           (object alignment gap)    
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

从以上打印出来的日志能够看到实例中包含了父类与本类的属性以及所占字节数。下图就来介绍整个对象的实例过程。
在这里插入图片描述

每个方法就是一个栈帧,当我们运行Start#main(),因为是main方法,在栈帧中的局部变量表中会先加载args,里面Customer customer = new Customer();会将customer记录到栈帧的局部变量表中,通过地址指针指向了堆中的整个实例对象。new Customer()这个实例的内部结构在堆空间记录了对象头、实例数据、填充字节。在该实例的对象头部中,记录了标记字(主要用与GC、线程安全等),通过类型指针指向方法区中对应Customer的klass类元信息。在实例数据中,会加载父类的属性以及本身属性,Customer类中引用了String对象,这是个引用类型,通过静态代码块赋值,这个字符串存在自负床变量池中。其中也引用了Account对象,这也是个引用类型,并不会将此对象属性都加载进来,只是记录了这个对象的引用地址,指向堆空间中的new Account()实例,在Account实例中,也是与Customer实例一样,包含了对象头等信息,也是通过类型指针指向方法区Account的klass类元信息。

对象的访问定位

从上文介绍对象的布局就已经能够知道对象是如何获取对象实例的。对于一个Customer customer = new Customer();我们知道其存储为三个部分,在栈帧中存储了堆区中的引用地址(reference),在堆区有个元数据指针(InstanceOopDesc)指向方法区中的InstanceKlass。总的来说,就是栈帧中记录着堆区实例化的对象地址,通过这个地址来方法对象实例。

对象访问的两种方式

对象的访问方式主要有两种方式,句柄访问和指针访问,在hotspot虚拟机中采用的是指针访问的方式。

1). 句柄访问

有些 JVM 实现可能使用了句柄访问的概念,其中对象的引用由一个句柄对象来管理,而句柄包含了对象的地址以及其他元信息。在Java堆中会开辟一个句柄池与实例池,句柄池中会通过指针指向实例对象和对象类型。
对象访问-句柄访问.png

因为采用的是句柄池指向对象实例数据,在reference中存储的是稳定的句柄地址,对象如果被移动(垃圾收集时候会移动对象)只会改变句柄的实例数据指针就行,reference本身是不用做修改。但是需要开辟一个空间来充当句柄池,这就会增加堆内存的空间占用。

2). 指针访问(Hotspot采用此方法)

在栈帧中的本地变量表中记录了堆中对象实例数据的地址,通过地址引用到堆中的对象实例数据,实例对象在通过类型指针指向方法区中的类元信息。
对象访问-指针引用.png

指针方式不用额外占用堆的空间,但是如果遇到对象移动,就需要去修改reference存储的地址。

直接内存(Direct Memory)

直接内存不是JVM运行时数据区的一个部分,也不是《Java虚拟机规范》中定义的内存区域,它是Java堆外的、直接向系统获取的内存空间。

直接内存概述

在Java虚拟机(JVM)中,“直接内存” 通常指的是使用 NIO(New I/O | Non-Blocking I/O)包中的 ByteBuffer 类,以及其中的 allocateDirect 方法所分配的直接字节缓冲区(Direct ByteBuffer)。通过DirectByteBuffer操作Native内存。

public class BufferTest {private static final int BUFFER_SIZE = 1024 * 1024 * 1024;public static void main(String[] args) {// byteBuffer 将持有一个大小为 1 GB 的直接内存缓冲区ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);System.out.println("内存分配:" + BUFFER_SIZE + "byte");Scanner sc = new Scanner(System.in);sc.next();System.out.println("释放内存");byteBuffer = null;System.gc();}
}

以上代码,我们可以创建出一个内存为1G的直接内存,这里通过Scanner输入进行阻塞,我们可以通过进程查看所占用的内存大小。可见使用ByteBuffer#allocateDirect()会直接分配本地内存。
在这里插入图片描述

直接内存的性能

通常,直接内存的速度会比Java堆更快,读写性能高。在一些频繁使用IO的场景,可能会考虑使用直接内存。Java的NIO包是允许程序使用直接内存,用来作为数据缓冲区。
对于非直接缓冲区,读写文件需要与磁盘交互,这时就需要由用户态切换到内核态,在由内核态去对物理磁盘进行交互。这样就造成了需要进行两份内存的存储重复数据,效率低。
在这里插入图片描述

对于直接缓冲区,使用NIO就能够直接的使用操作系统给出的缓存区,只会存储一份。
在这里插入图片描述

直接内存的OOM

直接内存也有可能导致OutOfMemoryError异常。因为直接内存是存在Java堆外的,他的大小不会受限于-Xmx设置的最大堆空间,系统内存是有限的,Java堆和直接内存加起来不能超过操作系统给的最大内存。它是受限于操作系统对进程的可用虚拟内存空间。
如果是超过内存的限制,就会抛出java.lang.OutOfMemoryError: Direct buffer memory
缺点:

  • 分配回收成本比较高
  • 不受JVM的内存回收管理

直接内存大小可以通过MaxDirectMemorySize设置,如果不指定,默认是与堆的最大值-Xmx参数值一致。

总结

本此学习穿插了Java对象的内存布局,更加清楚了解到对象的创建方式以及过程,最为重要的是了解对象的布局结构,包括实例对象数据存放在堆中,类元信息在方法区,栈帧通过引用去指向对应的数据信息。对比了句柄方式和指针方式。最后学习了直接内存的内容,了解了直接内存也是会出现OOM异常。

👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

域名邮箱与企业邮箱的区别:功能、应用与优势

根据使用者的不同需求&#xff0c;电子邮件分为域名邮箱和企业邮箱两种类型。那么这两种邮箱之间究竟存在哪些区别呢&#xff1f;本文将从定义、优势和劣势三个方面进行详细解析。 什么是域名邮箱&#xff1f; 域名邮箱&#xff0c;顾名思义是以域名作为后缀的电子邮箱。域名邮…

【UGUI】制作用户注册UI界面

这里面主要的操作思想就是 1.打组 同一个事情里面包含两个UI元素都应该打组便于管理和查找 2.设置锚点位置 每次创建一个UI都应该设置他的锚点以便于跟随画布控制自己的&#xff1a;相对位置 3. 设置尺寸&#xff08;像素大小&#xff09; 每一次UI元素哪怕是作为父物体的…

我叫:归并排序【JAVA】

1.认识我一下 1.归并排序(MERGE-SORT)利用归并的思想实现的排序方法,该算法采用经典的分治策略2.分治法将问题分成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的各答案"修补"在一起&#xff0c;即分而治之。 2.分合思想 3 分久必合 /*** 合并** param arr …

(数据结构)顺序表的定义

#include<stdio.h> //顺序表的实现——静态分配 #define MAX 10 //定义最大长度 typedef struct List {int data[MAX]; //用静态的数组存放数据int lenth; //顺序表现在的长度 }List; //顺序表的初始化 void ChuShiHua(List L) {L.lenth 0; //将顺序表的长度初始化…

YOLOv8优化策略:自适应改变核大小卷积AKConv,效果优于标准卷积核和DSConv |2023.11月最新成果

🚀🚀🚀本文改进: AKConv 中,通过新的坐标生成算法定义任意大小的卷积核的初始位置。 为了适应目标的变化,引入了偏移量来调整每个位置的样本形状。 此外,我们通过使用具有相同大小和不同初始采样形状的 AKConv 来探索神经网络的效果。 AKConv 通过不规则卷积运算完成…

【备忘录】软件记录

Anaconda 虚拟环境 创建Python环境 Spyder Python程序编辑 Jupyter Notebook 交互式开发环境

Spring第二课响应的完全,如何理解前后端互联

目录 一、响应 Control,RestController 1.Controller的源码&#xff0c;代表什么意思 2.返回数据 Responsebody 3.返回HTML片段 4.返回JSON 5.那么假如我们使用集合会怎么样呢 设置状态码&#xff0c;虽然不影响展示&#xff0c;但是确实显示起来也就是401的情况。 2.我…

[设计模式] 常见的设计模式

文章目录 设计模式的 6 大设计原则设计模式的三大分类常见的设计模式有哪几种1. 单例模式&#xff1a;保证一个类仅有一个实例&#xff0c;并提供一个访问它的全局访问点。&#xff08;连接池&#xff09;1. 饿汉式2. 懒汉式3. 双重检测 2. 工厂模式3. 观察者模式● 推模型● 拉…

自动化横行时代,手工测试如何突破重围?测试之路...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 自动化测试是每个…

WiseAlign 软件运行中存图功能使用方法

WiseAlign 软件运行中存图功能使用方法 在需要存图的相机图像通道点击鼠标右键 在弹出的菜单中选择“图像操作——保存图像” 选择想要存放图片的文件夹&#xff08;如下图所示&#xff09; 修改文件名称 如果文件夹中已有同名文件会提示xxx.bmp文件已存在&#xff0c;是否需要…

【【Linux开发环境搭建与软件的安装】】

Linux开发环境搭建与软件的安装 下面我们来讲述 Ubuntu 系统搭建 tftp 服务器 TFTP 需要一个文件夹来存放文件&#xff0c;我们在根目录下新建一个/tftpboot 目录做为 TFTP 文件存储目录&#xff0c;之所以使用该目录是因为后面使用的 Petalinux 工具默认使用该目录&#xff0…

Spring Cloud,注册中心,配置中心,原理详解

文章目录 Spring Cloud&#xff0c;注册中心&#xff0c;配置中心&#xff0c;原理详解谈谈我个人对 spring Cloud 的理解 注册中心Eureka&#xff1a;服务搭建小结 Ribbo - 负载均衡1. 负载均衡流程2. 负载均衡策略 nacos注册中心1. 配置集群1. 创建 namespace2. 配置命名空间…

核密度估计法(KDE)的概念,应用,优点,缺点,以及与正态分布(高斯分布)的区别,以及与概率分布的区别联系。看完你就真正捋清这些概念了

文章目录 前言一、核密度估计法&#xff08;KDE&#xff09;是什么&#xff1f;二、核密度估计法的步骤如下&#xff1a;三、核密度的应用&#xff1a;四、核密度估计法的优点&#xff1a;五、核密度估计法的缺点&#xff1a;六、核密度估计法和正态分布的区别在于&#xff1a;…

注解方式优雅的实现Redisson分布式锁

1.前言 随着微服务的快速推进&#xff0c;分布式架构也得到蓬勃的发展&#xff0c;那么如何保证多进程之间的并发则成为需要考虑的问题。因为服务是分布式部署模式&#xff0c;本地锁Reentrantlock和Synchnorized就无法使用了&#xff0c;当然很多同学脱口而出的基于Redis的se…

【机器学习】平滑滤波

平滑滤波技术 平滑滤波&#xff0c;顾名思义就是对信号进行处理使之整体显得更加平滑&#xff0c;降低噪声影响&#xff0c;提高信号质量&#xff0c;它常见于数字信号处理和图像处理&#xff0c;一般意义上的数字信号多体现于一维数据&#xff0c;图像信号多体现于二维数据。…

OCR常用数据集_看数据集区分可识别语言

这里写目录标题 COCO-TEXT 英文Total-Text 英文少量中文IIIT5K[50]、IC03[44]、IC13[34]、IC15[33]、CT80[56]MJSynth 英文SynthText分层文本数据集 (HierText) 英文TextOCR和IntelOCR &#xff1f;&#xff1f;&#xff1f;Multi-language dataset (IC19)RCTW17 主要中文MSRA-…

C语言键盘输入字符串小写转大写输出及scanf的小问题解决

1.博主在学习C语言时&#xff0c;也没太关注C语言的一些细节问题&#xff0c;导致后面有人问问题的时候一时没回答出来&#xff0c;也就是所谓的基础不牢地动山摇&#xff0c;比如这一次有同学问的scanf键盘输入的小问题&#xff0c;折腾了一阵子还是想出来问题所在。 2.废话不…

Docker | Docker部署MySQL

Docker | Docker部署MySQL ✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a;Docker系列…

软件工程--面向对象分析用通俗语言20小时爆肝总结!(包含用例图、活动图、类图、时序图......)

面向对象方法分为面向对象分析&#xff08;OOA&#xff09;、面向对象设计&#xff08;OOD&#xff09;、面向对象编程&#xff08;OOP&#xff09;&#xff0c;本文详细介绍面向对象分析 本文参考教材&#xff1a;沈备军老师的《软件工程原理》大多图片来源其中 目录 面向对…

JMeter多脚本间的启动延时

JMeter做压测时&#xff0c;当需要多个jmx脚本依次执行时&#xff0c;需要用到“启动延时”&#xff0c;即间隔可设置的时间后启动运行下一个jmx脚本。 实现“启动延时”的方法有2个。 方法一、利用JMeter线程组中的"Startup delay"参数 如上图&#xff0c;调度器&…