JVM学习-内存结构(二)

一、堆

1.定义

2.堆内存溢出问题

1.演示

-Xmx设置堆大小

3.堆内存的诊断

3.1介绍

1,2都是命令行工具(可直接在ideal运行时,在底下打开终端,输入命令)

1可以拿到Java进程的进程ID,2 jmap只能查询某一个时刻的堆内存

3.2 堆内存jmap演示

public class Demo1_4 {
​public static void main(String[] args) throws InterruptedException {System.out.println("1...");Thread.sleep(30000);byte[] array = new byte[1024 * 1024 * 10]; // 10 MbSystem.out.println("2...");Thread.sleep(20000);array = null;System.gc();System.out.println("3...");Thread.sleep(1000000L);}
}

第一个:

第二个(Eden多了10M):

第三个:垃圾回收后

3.3jconsole演示

运行程序 在终端输入jconsole,弹出界面

选择对应的程序,点击不安全连接

3.4.案例二

现象垃圾回收后,内存仍然占用很高

工具:jvisualvm

代码

/*** 演示查看对象个数 堆转储 dump*/
public class Demo1_13 {
​public static void main(String[] args) throws InterruptedException {List<Student> students = new ArrayList<>();for (int i = 0; i < 200; i++) {students.add(new Student());
//            Student student = new Student();}Thread.sleep(1000000000L);}
}
class Student {private byte[] big = new byte[1024*1024];
}

点击堆Dump,进行查找

二、方法区

1.定义

        Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的方法区域。方法区域类似于用于传统语言的编译代码的存储区域,或者类似于操作系统进程中的“文本”段。它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括特殊方法,用于类和实例初始化以及接口初始化方法区域是在虚拟机启动时创建的。尽管方法区域在逻辑上是堆的一部分,但简单的实现可能不会选择垃圾收集或压缩它。此规范不强制指定方法区的位置或用于管理已编译代码的策略。方法区域可以具有固定的大小,或者可以根据计算的需要进行扩展,并且如果不需要更大的方法区域,则可以收缩。方法区域的内存不需要是连续的!

        存的是跟类相关的信息,包括方法,构造器,成员方法等。方法区在虚拟机启动时被创建是在概念上定义的方法区,逻辑上属于堆的组成部分(厂家实现不同)方法区也会导致内存溢出的错误,抛出OutOfMemoryEror

2.组成

以Hotspot 虚拟机为例,jdk1.6 1.7 1.8 内存结构图,1.8使用的是本地内存不在占用堆内存 。

3.方法区内存溢出

  • 1.8 之前会导致永久代内存溢出

    使用 -XX:MaxPermSize=8m 指定永久代内存大小
  • 1.8 之后会导致元空间内存溢出

    • 使用 -XX:MaxMetaspaceSize=8m 指定元空间大小

  • 设置元空间大小为8m

    public class Demo1_8 extends ClassLoader { // 类加载器 可以用来加载类的二进制字节码public static void main(String[] args) {int j = 0;try {Demo1_8 test = new Demo1_8();for (int i = 0; i < 10000; i++, j++) {// ClassWriter 作用是生成类的二进制字节码ClassWriter cw = new ClassWriter(0);// 版本号, public, 类名, 包名, 父类, 接口cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);// 返回 byte[]byte[] code = cw.toByteArray();// 执行了类的加载test.defineClass("Class" + i, code, 0, code.length); // Class 对象}} finally {System.out.println(j);}}
    }

4.运行时常量池

4.1.二进制字节码文件的构成

主要分为(类的基本信息,常量池,类方法定义,包含的虚拟机指令)

可将程序运行产生的.class文件通过 javap 命令反编译

源程序

package jvm;
​
​
public class HelloWorld {public static void main(String[] args) {System.out.println("hello world");}
}

切换到out输出目录下,使用javap命令

得到反编译后的

 Last modified 2024-12-27; size 541 bytesMD5 checksum 1705415cdaac31d861d20edc1e472d95Compiled from "HelloWorld.java"
public class jvm.HelloWorldminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #6.#20         // java/lang/Object."<init>":()V#2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;#3 = String             #23            // hello world#4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V#5 = Class              #26            // jvm/HelloWorld#6 = Class              #27            // java/lang/Object#7 = Utf8               <init>#8 = Utf8               ()V#9 = Utf8               Code#10 = Utf8               LineNumberTable#11 = Utf8               LocalVariableTable#12 = Utf8               this#13 = Utf8               Ljvm/HelloWorld;#14 = Utf8               main#15 = Utf8               ([Ljava/lang/String;)V#16 = Utf8               args#17 = Utf8               [Ljava/lang/String;#18 = Utf8               SourceFile#19 = Utf8               HelloWorld.java#20 = NameAndType        #7:#8          // "<init>":()V#21 = Class              #28            // java/lang/System#22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;#23 = Utf8               hello world#24 = Class              #31            // java/io/PrintStream#25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V#26 = Utf8               jvm/HelloWorld#27 = Utf8               java/lang/Object#28 = Utf8               java/lang/System#29 = Utf8               out#30 = Utf8               Ljava/io/PrintStream;#31 = Utf8               java/io/PrintStream#32 = Utf8               println#33 = Utf8               (Ljava/lang/String;)V
{public jvm.HelloWorld();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 4: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Ljvm/HelloWorld;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #3                  // String hello world5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 6: 0line 7: 8LocalVariableTable:Start  Length  Slot  Name   Signature0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

在代码区域,每条指令都会对应常量池表中一个地址,常量池表中的地址可能对应着一个类名、方法名、参数类型等信息。

ldc #3 在常量池中找一个编号为3的符号

4.2定义

常量池: 就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量信息 运行时常量池: 常量池是 *.class 文件中的,当该类被加载以后,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

4.3 StringTable

StringTable底层是一个哈希表

下面这些题你能作对吗:

答案:
public class Demo1 {
​public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = "a" + "b"; // ab 常量池中String s4 = s1 + s2;   // new String("ab") 堆中String s5 = "ab";String s6 = s4.intern();
​
// 问System.out.println(s3 == s4); // falseSystem.out.println(s3 == s5); // trueSystem.out.println(s3 == s6); // true
​String x2 = new String("c") + new String("d"); // new String("cd") 堆中x2.intern();String x1 = "cd";//现在是true     上放一行 x1 != x2(false)
​
// 如果是jdk1.6呢(1.6是将x2拷贝一份入池,1.8是本身入池)System.out.println(x1 == x2);//现在是false     上放一行 x1 != x2(false)}
}
4.3.1StringTable的特性
  1. 常量池中的字符串仅是符号,只有在被用到时才会转化为对象

  2. 利用串池的机制,来避免重复创建字符串对象

  3. 字符串变量拼接的原理是StringBuilder(1.8)

  4. 字符串常量拼接的原理是编译器优化

  5. 可以使用intern方法,主动将串池中还没有的字符串对象放入串池中

    package jvm;
    ​
    public class Demo {
    ​//  ["ab", "a", "b"]public static void main(String[] args) {
    ​String x = "ab";String s = new String("a") + new String("b");
    ​// 堆  new String("a")   new String("b") new String("ab")这个ab是动态拼接的不在串池中,在堆中String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
    ​System.out.println( s2 == "ab");//trueSystem.out.println( s == "ab");//trueSystem.out.println( s2 == x);//trueSystem.out.println( s == x );//false}
    ​
    }

intern方法 1.8 调用字符串对象的 intern 方法,会将该字符串对象尝试放入到串池中

  1. 如果串池中没有该字符串对象,则放入成功

  2. 如果有该字符串对象,则放入失败 无论放入是否成功,都会返回串池中的字符串对象

  3. 注意:此时如果调用 intern 方法成功,堆内存与串池中的字符串对象是同一个对象;如果失败,则不是同一个对象

4.4 StringTable的位置

jdk1.6 StringTable 位置是在永久代中,1.7,1.8 StringTable 位置是在堆中。

4.5 串池 、常量池 、 运行时常量池 的关系:

  1. 常量池(静态常量池):

    • 常量池通常指的是.class文件中的常量池,它包含了类、方法、字段的符号引用,以及字面量等信息。这些信息在类加载到JVM之前就已经确定,并且存储在.class文件中。

  2. 运行时常量池

    • 当类被加载到JVM时,其常量池的信息会被复制到运行时常量池中。运行时常量池是方法区的一部分,它包含了从.class文件中复制来的常量,以及在运行时动态生成的常量。

    • 运行时常量池相对于.class文件中的常量池具有动态性,可以在运行时添加新的常量。

  3. StringTable(串池)

    • StringTable是运行时常量池的一部分,专门用于存储字符串常量。它通过一个哈希表(数组+链表)来实现,确保存储的字符串常量唯一且不重复。

    • 在JDK 1.7及之前版本中,StringTable位于方法区(Perm Gen),而在JDK 1.8及之后版本中,StringTable被移到了堆中。

    • StringTable中存储的并不是String对象本身,而是指向堆中String对象的引用。

    • StringTable的创建是懒加载的,即只有当字符串常量第一次被使用时,才会在堆中创建String对象,并将其引用放入StringTable中。

        总结来说,StringTable是运行时常量池中专门用于管理字符串常量的部分,它通过优化存储机制来确保相同内容的字符串对象在JVM中只存在一份,从而节省内存。而常量池和运行时常量池则包含了更广泛的信息,包括类、方法、字段的引用和字面量等。StringTable与常量池的关系在于,常量池中的字符串常量在类加载时会被复制到运行时常量池中的StringTable里,而运行时常量池则包含了常量池的所有内容,并支持动态添加新的常

4.6 StringTable 垃圾回收

StringTable底层是一个哈希表

先设置虚拟机参数(便于输出观察):

-Xmx10m 指定堆内存大小 -XX:+PrintStringTableStatistics 打印字符串常量池信息 -XX:+PrintGCDetails -verbose:gc 打印 gc 的次数,耗费时间等信息

public class StringTable {public static void main(String[] args) throws InterruptedException {int i = 0;try {for (int j = 0; j < 100000; j++) { // j=100, j=10000String.valueOf(j).intern();i++;}} catch (Throwable e) {e.printStackTrace();} finally {System.out.println(i);}
​}
}

4.7StringTable的性能调优

因为StringTable是由HashTable实现的,所以可以适当增加HashTable桶的个数,来减少字符串放入串池所需要的时间

使用时加入配置:-Xms500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=1009 (字符串很多时考虑)

-XX:StringTableSize=桶个数(最少设置为 1009 以上)

考虑是否需要将字符串对象入池 可以通过 intern 方法减少重复入池

三、直接内存

1.定义

操作系统的内存 Direct Memory

  • 常见于 NIO 操作时,用于数据缓冲区

  • 分配回收成本较高,但读写性能高

  • 不受 JVM 内存回收管理

2.使用直接内存的好处

2.1文件读写流程:

        因为 java 不能直接操作文件管理,需要切换到内核态,使用本地方法进行操作,然后读取磁盘文件,会在系统内存中创建一个缓冲区,将数据读到系统缓冲区, 然后在将系统缓冲区数据,复制到 java 堆内存中。缺点是数据存储了两份,在系统内存中有一份,java 堆中有一份,造成了不必要的复制。

2.2使用了 DirectBuffer 文件读取流程

直接内存是操作系统和 Java 代码都可以访问的一块区域,无需将代码从系统内存复制到 Java 堆内存,从而提高了效率。

3.直接内存回收原理

public class Code_06_DirectMemoryTest {public static int _1GB = 1024 * 1024 * 1024;public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {
//        method();method1();}// 演示 直接内存 是被 unsafe 创建与回收private static void method1() throws IOException, NoSuchFieldException, IllegalAccessException {Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);Unsafe unsafe = (Unsafe)field.get(Unsafe.class);long base = unsafe.allocateMemory(_1GB);unsafe.setMemory(base,_1GB, (byte)0);System.in.read();unsafe.freeMemory(base);System.in.read();}// 演示 直接内存被 释放private static void method() throws IOException {ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1GB);System.out.println("分配完毕");System.in.read();System.out.println("开始释放");byteBuffer = null;System.gc(); // 手动 gcSystem.in.read();}}

        直接内存的回收不是通过 JVM 的垃圾回收来释放的,而是通过unsafe.freeMemory 来手动释放。 第一步:allocateDirect 的实现

public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity);
}

 底层是创建了一个 DirectByteBuffer 对象。

第二步:DirectByteBuffer 类

DirectByteBuffer(int cap) {   // package-privatesuper(-1, 0, cap, cap);boolean pa = VM.isDirectMemoryPageAligned();int ps = Bits.pageSize();long size = Math.max(1L, (long)cap + (pa ? ps : 0));Bits.reserveMemory(size, cap);long base = 0;try {base = unsafe.allocateMemory(size); // 申请内存} catch (OutOfMemoryError x) {Bits.unreserveMemory(size, cap);throw x;}unsafe.setMemory(base, size, (byte) 0);if (pa && (base % ps != 0)) {// Round up to page boundaryaddress = base + ps - (base & (ps - 1));} else {address = base;}cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); // 通过虚引用,来实现直接内存的释放,this为虚引用的实际对象, 第二个参数是一个回调,实现了 runnable 接口,run 方法中通过 unsafe 释放内存。att = null;
}

        这里调用了一个 Cleaner 的 create 方法,且后台线程还会对虚引用的对象监测,如果虚引用的实际对象(这里是 DirectByteBuffer )被回收以后,就会调用 Cleaner 的 clean 方法,来清除直接内存中占用的内存。

 public void clean() {if (remove(this)) {try {// 都用函数的 run 方法, 释放内存this.thunk.run();} catch (final Throwable var2) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {if (System.err != null) {(new Error("Cleaner terminated abnormally", var2)).printStackTrace();}System.exit(1);return null;}});}}}

可以看到关键的一行代码, this.thunk.run(),thunk 是 Runnable 对象。run 方法就是回调 Deallocator 中的 run 方法,

		public void run() {if (address == 0) {// Paranoiareturn;}// 释放内存unsafe.freeMemory(address);address = 0;Bits.unreserveMemory(size, capacity);}

直接内存的回收机制总结

   使用了 Unsafe 类来完成直接内存的分配回收,回收需要主动调用freeMemory 方法

   ByteBuffer 的实现内部使用了 Cleaner(虚引用)来检测 ByteBuffer 。一旦ByteBuffer 被垃圾回收,那么会由 ReferenceHandler(守护线程) 来调用 Cleaner 的 clean 方法调用 freeMemory 来释放内存

注意:

/*** -XX:+DisableExplicitGC 显示的*/private static void method() throws IOException {ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1GB);System.out.println("分配完毕");System.in.read();System.out.println("开始释放");byteBuffer = null;System.gc(); // 手动 gc 失效System.in.read();}

一般用 jvm 调优时,会加上下面的参数:

-XX:+DisableExplicitGC  // 静止显示的 GC

        意思就是禁止我们手动的 GC,比如手动 System.gc() 无效,它是一种 full gc,会回收新生代、老年代,会造成程序执行的时间比较长。所以我们就通过 unsafe 对象调用 freeMemory 的方式释放内存。

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

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

相关文章

在线学习平台-项目技术点-后台

目录 1、主键生成策略 1.1自动增长-AUTO INCREMENT 1.2UUID 1.3Redis生成ID 2、MyBatis-plus 2.1自动填充 2.2悲观锁、乐观锁 2.3性能分析插件 3.ResponseBody和RequestBody 4.es6语法 4.1let变量和const变量 4.2解构赋值&#xff08;数组和对象解构 4.3模板字符串…

Redis 实战篇 ——《黑马点评》(上)

《引言》 在进行了前面关于 Redis 基础篇及其客户端的学习之后&#xff0c;开始着手进行实战篇的学习。因内容很多&#xff0c;所以将会分为【 上 中 下 】三篇记录学习的内容与在学习的过程中解决问题的方法。Redis 实战篇的内容我写的很详细&#xff0c;为了能写的更好也付出…

MySQL数据库——常见的几种锁分类

详细介绍MySQL的几种常见锁分类&#xff0c;如&#xff1a;表级锁、行级锁、页面锁、悲观锁、乐观锁、共享锁、排他锁、Gap-锁等。 文章目录 按锁粒度分表级锁行级锁页面锁锁与索引关系 按加锁机制分【逻辑上的锁】悲观锁乐观锁版本号机制CAS&#xff08;Compare and Swap&…

数据库sql语句单表查询

简单的增删改查操作 select count(*) from user where accountadmin and password123456 select count(*) from user where account"admin" insert into user(account,password) values ("admin","777") update user set password "666&…

OpenCV和PyQt的应用

1.创建一个 PyQt 应用程序&#xff0c;该应用程序能够&#xff1a; 使用 OpenCV 加载一张图像。在 PyQt 的窗口中显示这张图像。提供四个按钮&#xff08;QPushButton&#xff09;&#xff1a; 一个用于将图像转换为灰度图一个用于将图像恢复为原始彩色图一个用于将图像进行翻…

电路元件与电路基本定理

电流、电压和电功率 电流 1 定义&#xff1a; 带电质点的有序运动形成电流 。 单位时间内通过导体横截面的电量定义为电流强度&#xff0c; 简称电流&#xff0c;用符号 i 表示&#xff0c;其数学表达式为&#xff1a;&#xff08;i单位&#xff1a;安培&#xff08;A&#x…

win11中win加方向键失效的原因

1、可能是你把win键锁了&#xff1a; 解决办法&#xff1a;先按Fn键&#xff0c;再按win键 2、可能是可能是 贴靠窗口设置 中将贴靠窗口关闭了&#xff0c;只需要将其打开就好了

十二月第五周python

第一个程序&#xff0c;熟悉转换器&#xff0c;把加法计算器变成exe# // 1,制作加法计算器&#xff0c; # 输入两个数字得到相加结果并输出aint(input("输入数字&#xff1a;"))#int()是把输入的内容转换成整数&#xff0c; bint(input("输入数字&#xff1a;&…

pyqt和pycharm环境搭建

安装 python安装&#xff1a; https://www.python.org/downloads/release/python-3913/ python3.9.13 64位(记得勾选Path环境变量) pycharm安装&#xff1a; https://www.jetbrains.com/pycharm/download/?sectionwindows community免费版 换源&#xff1a; pip config se…

Lottie动画源码解析

Lottie是一个很成熟的开源动画框架&#xff0c;它支持直接使用从AE导出的动画文件&#xff0c;在不同平台均可快速使用&#xff0c;大大减轻了程序员的工作量&#xff0c;也让复杂的动画成为可能。该动画文件使用Json格式来描述内容&#xff0c;可以大大缩减文件的体积。在Andr…

Cadence学习笔记 16 HDMI接口布局

基于Cadence 17.4&#xff0c;四层板4路HDMI电路 更多Cadence学习笔记&#xff1a;Cadence学习笔记 1 原理图库绘制Cadence学习笔记 2 PCB封装绘制Cadence学习笔记 3 MCU主控原理图绘制Cadence学习笔记 4 单片机原理图绘制Cadence学习笔记 5 四路HDMI原理图绘制Cadence学习笔记…

微服务篇-深入了解 MinIO 文件服务器(你还在使用阿里云 0SS 对象存储图片服务?教你使用 MinIO 文件服务器:实现从部署到具体使用)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 MinIO 文件服务器概述 1.1 MinIO 使用 Docker 部署 1.2 MinIO 控制台的使用 2.0 使用 Java 操作 MinIO 3.0 使用 minioClient 对象的方法 3.1 判断桶是否存在 3.2…

logback之pattern详解以及源码分析

目录 &#xff08;一&#xff09;pattern关键字介绍 &#xff08;二&#xff09;源码分析 &#xff08;一&#xff09;pattern关键字介绍 %d或%date&#xff1a;表示日期&#xff0c;可配置格式化%d{yyyy-MM-dd HH:mm:ss} %r或%relative&#xff1a;也是日期&#xff0c;不过…

【期末复习】JavaEE(下)

1. MVC开发模式 1.1. 运行流程 1.2. SpringMVC 核心组件 1.3. 注解解释 2. ORM与MyBatis 2.1. ORM—对象关系映射 2.2. MyBatis 2.2.1. 创建步骤 会话是单例的&#xff0c;不能跨方法。&#xff08;单例的原因主要是从数据安全角度出发&#xff09; import org.apache.ibatis…

作业帮基于 Apache DolphinScheduler 3_0_0 的缺陷修复与优化

文|作业帮大数据团队&#xff08;阮文俊、孙建业&#xff09; 背 景 基于 Apache DolphinScheduler &#xff08;以下简称DolphinScheduler&#xff09;搭建的 UDA 任务调度平台有效支撑了公司的业务数据开发需求&#xff0c;处理着日均百万级别的任务量。 整个 UDA 的架构如…

电脑缺失sxs.dll文件要怎么解决?

一、文件丢失问题&#xff1a;以sxs.dll文件缺失为例 当你在运行某个程序时&#xff0c;如果系统提示“找不到sxs.dll文件”&#xff0c;这意味着你的系统中缺少了一个名为sxs.dll的动态链接库文件。sxs.dll文件通常与Microsoft的.NET Framework相关&#xff0c;是许多应用程序…

Web开发:ORM框架之使用Freesql的分表分页写法

一、自动分表&#xff08;高版本可用&#xff09; 特性写法 //假如是按月分表&#xff1a;[Table(Name "log_{yyyyMM}", AsTable "createtime2022-1-1(1 month)")]注意&#xff1a;①需包含log_202201这张表 ②递增规律是一个月一次&#xff0c;确保他们…

【数据结构与算法】单向链表

一、什么是链表 链表由一系列节点组成&#xff0c;每个节点都包含一个 data 域&#xff08;存放数据&#xff09;和一个 next 域&#xff08;指向下一节点&#xff09;。链表中的节点可以按照任意顺序存放在内存中&#xff0c;它们之间并不连续。每个节点都记录了下一个节点的地…

【ACCSS】2024年亚信安全云认证专家题库

文件包含&#xff1a; 亚信安全ACCSS认证2019年真题&#xff08;1&#xff09; 亚信安全ACCSS认证2019年真题&#xff08;2&#xff09; 亚信安全ACCSS认证2019年真题&#xff08;3&#xff09; 亚信安全ACCSS认证2020年真题&#xff08;1&#xff09; 亚信安全ACCSS认证2020年…

OpenCV-Python实战(10)——形态学

1、腐蚀 cv2.erode() 可以删除图像中的噪音点。 可以删除毛边。 分割图像&#xff08;当图像连接的不够紧密时&#xff09; 。 img cv2.erode(src*,kernel*,anchor*,iterations*,borderType*,borderValue*)img&#xff1a;目标图像。 src&#xff1a;原始图像。 kernel&…