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

news2024/11/18 10:34:22

对象的实例化与直接内存

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

在这里插入图片描述

文章目录

  • 对象的实例化与直接内存
    • 创建对象的方式
      • ① 使用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               VALUE
  0   8                                           (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                                           (object header: class)    0xf800c182
 12   4                          java.lang.String User.username             null
 16   4                                       int Customer.id               1
 20   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.coloradmin.cn/o/1252938.html

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

相关文章

laravel实现发送邮件功能

Laravel提供了简单易用的邮件发送功能&#xff0c;使用SMTP、Mailgun、Sendmail等多种驱动程序&#xff0c;以及模板引擎将邮件内容进行渲染。 1.在项目目录.env配置email信息 MAIL_MAILERsmtp MAIL_HOSTsmtp.qq.com MAIL_PORT465 MAIL_FROM_ADDRESSuserqq.com MAIL_USERNAME…

二阶常系数非齐次线性方程

,是一个n次多项式。 &#xff08;1&#xff09; 设 是(1)的特解。 是一个待定多项式 求的一阶导数 (求导&#xff1a;一项不变&#xff0c;二项求导二项不变&#xff0c;一项求导) 求的二阶导数 将其代入方程一 不可能是零 公式(2) 第一种情况&#xff1a; 即不是特征方程的…

c语言:有关内存函数的模拟实现

memcpy函数&#xff1a; 功能&#xff1a; 复制任意类型的数据&#xff0c;存储到某一数组中。 代码模拟实现功能&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include <stdio.h> #include<assert.h> memcpy…

机器学习第14天:KNN近邻算法

☁️主页 Nowl &#x1f525;专栏《机器学习实战》 《机器学习》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 文章目录 介绍 实例 回归任务 缺点 实例 分类任务 如何选择最佳参数 结语 介绍 KNN算法的核心思想是&#xff1a;当我们要判断一个数据为哪一类时…

ZKP中的哈希函数

1. 引言 后续博客&#xff1a; 如何选择ZK-friendly 哈希函数&#xff1f; 当暴露在ZK空间中时&#xff0c;对于普通crypto工程师来说&#xff0c;通常只需很少的时间就可以偶然发现一些外来的哈希函数——很可能是Poseidon。本文&#xff0c;将介绍ZK中高效哈希函数的历史&…

怎么给数据库某个字段建立一个前缀索引

说明&#xff1a;SQL调优中重要的一个环节是建立索引&#xff0c;其中有一条是字段值过长字段应该建立前缀索引&#xff0c;即根据字段值的前几位建立索引&#xff0c;像数据库中的密码字段、UUID字段。 因为其随机性&#xff0c;其实根据前几位就可以锁定某一条记录了。前缀索…

css实现图片绕中心旋转,鼠标悬浮按钮炫酷展示

vue模板中代码 <div class"contentBox clearfix home"><div class"circle"><img class"in-circle" src"../../assets/img/in-circle.png" alt""><img class"out-circle" src"../../as…

python基于DETR(DEtection TRansformer)开发构建钢铁产业产品智能自动化检测识别系统

在前文中我们基于经典的YOLOv5开发构建了钢铁产业产品智能自动化检测识别系统&#xff0c;这里本文的主要目的是想要实践应用DETR这一端到端的检测模型来开发构建钢铁产业产品智能自动化检测识别系统。 DETR (DEtection TRansformer) 是一种基于Transformer架构的端到端目标检…

字符串转换成十进制整数

编程要求 输入一个以#结束的字符串&#xff0c;本题要求滤去所有的非十六进制字符&#xff08;不分大小写&#xff09;&#xff0c;组成一个新的表示十六进制数字的字符串&#xff0c;然后将其转换为十进制数后输出。如果在第一个十六进制字符之前存在字符“-”&#xff0c;则…

vue2:组件中extends的使用

上一篇文章中我对mixin的使用进行了一个使用和测试,这里对extend进行一个使用,其实extend和mixin还是有区别的。 上一篇文章:vue2:mixin混入的使用-CSDN博客 不过也是看实际的业务场景,我们也可以使用extend完成和mixin几乎一摸一样的操作。 不废话,上代码 创建extendTest.…

MySQL表连接

文章目录 MySQL内外连接1.内连接2.外连接&#xff08;1&#xff09;左外连接&#xff08;2)右外连接 3.简单案例 MySQL内外连接 1.内连接 内连接的SQL如下&#xff1a; SELECT ... FROM t1 INNER JOIN t2 ON 连接条件 [INNER JOIN t3 ON 连接条件] ... AND 其他条件;说明一下…

vulnhub靶机Aragog-1.0.2

靶机下载&#xff1a;https://download.vulnhub.com/harrypotter/Aragog-1.0.2.ova 主机发现 目标133 端口扫描 端口服务扫描 漏洞扫描 去看web 就一个图片 直接目录扫描 有点东西一个一个看&#xff08;blog里面有东西&#xff09; 这里面直接能看到wordpress 直接用wpscan…

Go 语言之 Maps 详解:创建、遍历、操作和注意事项

Maps用于以键值对的形式存储数据值。Maps中的每个元素都是一个键值对。Maps是一个无序且可更改的集合&#xff0c;不允许重复。Maps的长度是其元素的数量。您可以使用 len() 函数来查找长度。Maps的默认值是 nil。Maps保存对底层哈希表的引用。 Go语言有多种方法来创建Maps。 …

VS中如何使用Halcon

使用Halcon的本质就是调用Halcon的库&#xff0c;其主要步骤有&#xff1a; 1、将Halcon代码导出为C的.cpp文件 2、获取.cpp文件中的action函数的函数体 3、添加Halcon的动态库和静态库 4、添加action函数需要的头文件 导出halcon中的代码 a&#xff09;导出代码 b&#x…

运行软件报错找不到vcruntime140_1.dll无法继续执行代码如何解决?-常见问题

关于vcruntime140_1.dll丢失的6个解决方法。在我们使用电脑的过程中&#xff0c;有时候会遇到一些错误提示&#xff0c;其中之一就是“vcruntime140_1.dll丢失”。那么&#xff0c;究竟什么是vcruntime140_1.dll文件呢&#xff1f;又是什么原因导致了它的丢失&#xff1f;接下来…

基于uniapp+vue微信小程序的健康饮食管理系统 907m6

设计这个微信小程序系统能使用户实现不需出门就可以在手机或电脑前进行网上查询美食信息、 运动视频等功能。 本系统由用户和管理员两大模块组成。用户界面显示在应用程序中&#xff0c;管理员界面显示在后台服务中&#xff0c;通过小程序端与服务端间进行数据交互与数据传输实…

微服务实战系列之Nginx(技巧篇)

前言 今天北京早晨竟然飘了一些“雪花”&#xff0c;定睛一看&#xff0c;似雪非雪&#xff0c;像泡沫球一样&#xff0c;原来那叫“霰”。 自然中&#xff0c;雨雪霜露雾&#xff0c;因为出场太频繁&#xff0c;认识门槛较低&#xff0c;自然不费吹灰之力&#xff0c;即可享受…

电脑技巧:电脑常见蓝屏、上不了网等故障及解决办法

目录 一、电脑蓝屏 常见原因1: 病毒木马 常见原因2: 安装了不兼容的软件 二、电脑不能上网 常见原因1: 新装系统无驱动 常见原因2: DNS服务器异常 常见原因3: 硬件问题 三、电脑没声音 常见原因1: 未安装驱动 常见原因2: 硬件故障 四、电脑屏幕不显示 常见原因1: 显…

【限流配电开关】TPS2001C

&#x1f6a9; WRITE IN FRONT &#x1f6a9; &#x1f50e; 介绍&#xff1a;"謓泽"正在路上朝着"攻城狮"方向"前进四" &#x1f50e;&#x1f3c5; 荣誉&#xff1a;2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评百大…

物联网AI 无线连接学习之蓝牙基础篇 协议的发展

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; 蓝牙由来 “蓝牙”&#xff08;Bluetooth&#xff09;原是一位在10世纪统一丹麦的国王哈拉尔 (HaralBluetooth)&#xff0c;他将当时的瑞典、芬兰与丹麦统一起来。而将“蓝牙”与后来的无线通讯技术标准关联…