堆外内存和堆内内存及虚引用的应用

news2025/2/23 13:18:41

目录

内存区域划分:

元空间

程序计数器

直接内存

对象的创建

对象的访问定位

判断对象是否存活

堆外内存

堆内内存的缺点以及引入堆外内存

为什么需要堆外内存?

如何分配堆外内存?

如何回收堆外内存?

1) System.gc() 触发

在预定内存时,为什么要主动调用System.gc

2) Cleaner 对象

为什么要使用堆外内存

为什么不能大面积使用堆外内存

巨人的肩膀


在介绍堆内内存之前首先来看一下JDK内存的划分,以便于更好的理解堆内内存的作用

内存区域划分:

JDK 1.8 之前的 JVM 运行时数据区域分布图:

 JDK 1.8及之后的 JVM 运行时数据区域分布图: 

通过 JDK 1.8 之前与 JDK 1.8 之后的 JVM 运行时数据区域分布图对比,我们可以发现区别就是 1.8有一个元空间替代方法区。下文元空间章节介绍了为何替换方法区

元空间

JDK 1.8就把方法区改用元空间了。类的元信息被存储在元空间中,元空间没有使用堆内存,而是与堆不相连的本地内存区域。所以,理论上系统可以使用的内存有多大,元空间就有多大。

JDK 1.8 中 HotSpot JVM 移出 永久代(PermGen),开始时使用元空间(Metaspace)。使用元空间取代永久代的实现的主要原因如下:

  1. 避免OOM异常,字符串存在永久代中,容易出现性能问题和内存溢出;
  2. 永久代设置空间大小是很难确定,太小容易出现永久代溢出,太大则容易导致老年代溢出;
  3. 永久代进行调优非常困难;
  4. 将 HotSpot 与 JRockit 合二为一;

程序计数器

Program Counter Register:一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。由于 JVM 可以并发执行线程,所以会为每个线程分配一个程序计数器,与线程的生命周期相同。因此会存在线程之间的切换,而这个时候就程序计数器会记录下当前程序执行到的位置,以便在其他线程执行完毕后,恢复现场继续执行。

如果线程正在执行的是 Java 方法,这个计数器记录的是正在执行虚拟机字节码指令的地址;如果正在执行的是 Native 方法,计数器的值则为空(undefined)

此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。

直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致OutOfMemoryError异常出现。

JDK1.4中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel)缓存区(Buffer) 的 I/O 方式,它可以直接使用Native函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据

本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

对象的创建

下图便是 Java 对象的创建过程,我建议最好是能默写出来,并且要掌握每一步在做什么。

①类加载检查: 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

②分配内存:类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式“指针碰撞”“空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定

内存分配的两种方式:(补充内容,需要掌握)

选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的。

内存分配并发问题(补充内容,需要掌握)

在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:

  • CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
  • TLAB: 为每一个线程预先在Eden区分配一块儿内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配

③初始化零值: 内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

④设置对象头: 初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

⑤执行 init 方法: 在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

对象的访问定位

建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有①使用句柄②直接指针两种:

  1. 句柄: 如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;

  2. 直接指针: 如果使用直接指针访问,那么 Java 堆对像的布局中就必须考虑如何防止访问类型数据的相关信息,reference 中存储的直接就是对象的地址。

    这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。

判断对象是否存活

可达性分析算法

引用计数法

(六)JVM成神路之GC基础篇:对象存活判定算法、GC算法、STW、GC种类详解 - 掘金

JVM学习-GC之判断对象存活 - 掘金

gc我先说了下怎么判断垃圾,引用计数法和可达性分析法,面试官打断了我,问我如果一个场景,new了一个线程,什么也不做,那么他会被回收吗?不会回收的话是谁引用了它?如果是可达性分析,那么在springboot中,serviceA注入了serviceB,serviceB注入了serviceA,那么他们会被回收吗?如果会被回收,那么单例不是每次都要创建吗?你如果是springboot的开发者,你怎么处理它?

堆外内存

平时编程时,在 Java 中创建对象,实际上是在堆上划分了一块区域,这个区域叫堆内内存

  • 使用这 -Xms -Xmx 来指定新生代老年代空间大小的初始值和最大值,这初始值和最大值也被称为 Java的大小,即 堆内内存大小。
  • 这个堆内内存完全受 JVM 管理JVM 有垃圾回收机制,所以我们一般不必关系对象的内存如何回收。

剖开 JVM 内存模型,来看下其堆划分:

由图可知 Java8 使用元空间替代永久代且元空间放在堆外内存上,这是为啥?

  1. 类的元数据信息常用到,在 GC 时回收效率偏低。
  2. 类的元数据信息比较难以确定其大小,指定太小容易出现永久代溢出、指定太大则容易造成老年代溢出。

堆内内存的缺点以及引入堆外内存

Java中的对象都是在JVM堆中分配的,其好处在于开发者不用关心对象的回收。但有利必有弊,堆内内存主要有两个缺点:1.GC是有成本的,堆中的对象数量越多,GC的开销也会越大。2.使用堆内内存进行文件、网络的IO时,JVM会使用堆外内存做一次额外的中转,也就是会多一次内存拷贝。

和堆内内存相对应,堆外内存就是把内存对象分配在Java虚拟机堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。

Java 程序一般使用 -XX:MaxDirectMemorySize 来限制最大堆外内存

还有个问题:堆外内存属于用户空间还是内核空间? 用户空间。

为什么需要堆外内存?

使用堆外内存,有这些好处:

  1. 直接使用堆外内存可以减少一次内存拷贝: 当进行网络 I/O 操作、文件读写时,堆内内存都需要转换为堆外内存,然后再与底层设备进行交互。
  2. 降低 JVM GC 开销对应用程序影响:因为堆外内存不受 JVM 管理。
  3. 堆外内存可以实现进程之间、JVM 多实例之间的数据共享。

为什么使用堆外内存可以减少一次内存拷贝呢?

原因:当进行网络 I/O操作或文件读写时,如果使用堆内内存(HeapByteBufferJDK 会先创建一个堆外内存(DirectBuffer,再去执行真正的读写操作。

具体原因是:调用底层系统函数(writeread等),必须要求使用是连续的地址空间

  1. 操作系统并不感知 JVM 的堆内存,而且 JVM 的内存布局与操作系统所分配的是不一样的,操作系统并不会按照 JVM 的行为来读写数据。
  2. 同一个对象的内存地址随着 JVM GC 的执行可能会随时发生变化,例如 JVM GC 的过程中会通过压缩来减少内存碎片,这就涉及对象移动的问题了。

当然使用堆外内存,有这些弊端:

  1. 排查内存泄漏问题相对困难: 因为堆外内存需要手动释放,不熟悉对应框架源码,可能稍有不慎就会造成应用程序内存泄漏。
  2. 对开发人员的基础技能要求高。

由此可以看出,如果想实现高效的 I/O 操作、缓存常用的对象、降低 JVM GC 压力,堆外内存是一个非常不错的选择。

如何分配堆外内存?

Java中分配堆外内存首先通过ByteBuffer.java.allocateDirect()得到以一个DirectByteBuffer对象;其中DirectByteBuffer是用Unsafe去实现内存分配的,对堆内存的分配、读写、回收都做了封装。

首先来看下 Java NIO 包中的 ByteBuffer 类的分配方式,使用方式如下:

//分配10M堆外内存
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10 * 1024 * 1024);
//释放堆外内存
((DirectBuffer) byteBuffer).cleaner().clean();

跟进 ByteBuffer.allocateDirect 源码,发现其中直接调用的 DirectByteBuffer 构造函数:

DirectByteBuffer(int cap) {//注意它是一个构造方法 
    super(-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);//注意这里会调用 System.gc();

    long base = 0;
    try {
        //1. 真正分配堆外内存
        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 boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    //2. 用于回收堆外内存
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

DirectByteBuffer构造方法中还做了挺多事情的,总的来说分为几个步骤:

  1. 预分配内存
  2. 分配内存
  3. 将刚分配的内存空间初始化为0
  4. 创建一个cleaner对象,Cleaner对象的作用是当DirectByteBuffer对象被回收时,释放其对应的堆外内存

从DirectByteBuffer的构造方法中可以看出,堆外内存的分配的开始在 Bits.reserveMemory(size, cap);中。

进入Bits类,先看几个和堆外内存相关的成员属性:

maxMemory

用户设置的堆外内存最大分配量,由jvm参数-XX:MaxDirectMemorySize= 配置。

reservedMemory

已使用堆外内存的大小。使用AtomicLong来保证多线程下的安全性。

totalCapacity

总容量。同样使用AtomicLong。

count

记录分配堆外内存的总份数。

memoryLimitSet

一个标记变量,有volatile关键字。用来记录maxMemory字段是否已初始化。

在分配堆外内存前,jdk使用tryReserveMemory方法实现了一个乐观锁,来保证实际分配的堆外内存总数不会大于设计的上限。

在tryReserveMemory中的逻辑也比较简单,使用while循环+CAS来保证有足够的剩余空间,并更新总空间,剩余空间,和堆外内存数。可以看出,如果CAS失败,但还有足够的容量,while循环会进入下一轮CAS更新尝试,直到更新成功或容量不足。

在reserveMemory方法中,只是先将堆外内存相关的属性设值,但并没有真正的分配内存。

Java的堆外内存回收设计是这样的:当GC发现DirectByteBuffer对象变成垃圾时,会调用Cleaner.clean()回收对应的堆外内存,一定程度上防止了内存泄露。当然,也可以手动的调用该方法,对堆外内存进行提前回收。

DirectByteBuffer 对象: 存放在堆内存里,仅仅包含堆外内存的地址、大小等属性。同时还会创建对应的 Cleaner 对象,通过 ByteBuffer 分配的堆外内存不需要手动回收,它可以被 JVM 自动回收;当然,也可以手动的调用该方法,对堆外内存进行提前回收。

当堆内的 DirectByteBuffer 对象被 GC 回收时,Cleaner 就会用于回收对应的堆外内存。 

真正分配堆外内存的逻辑还是通过 unsafe.allocateMemory(size)        

Unsafe 是一个非常不安全的类,它用于执行内存访问、分配、修改等敏感操作,可以越过 JVM 限制的枷锁。Unsafe 最初并不是为开发者设计的,使用它时虽然可以获取对底层资源的控制权,但也失去了安全性的保证,所以使用 Unsafe 一定要慎重。

private static Unsafe unsafe = null;
static {
    try {
        Field getUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        getUnsafe.setAccessible(true);
        unsafe = (Unsafe) getUnsafe.get(null);
    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
    }
}

获得 Unsafe 实例后,可以通过 allocateMemory 方法分配堆外内存,allocateMemory 方法返回的是内存地址,使用方法如下所示:

//分配 10M 堆外内存
long address = unsafe.allocateMemory(10 * 1024 * 1024);
//Unsafe#allocateMemory 所分配的内存必须自己手动释放, 否则会造成内存泄漏
//这也是 Unsafe 不安全的体现。
unsafe.freeMemory(address);

如何回收堆外内存?

堆外内存回收,有两种方式:

  1. Full GC 时以及调用 System.gc() 通过 JVM 参数 -XX:MaxDirectMemorySize 指定堆外内存的上限大小,当堆外内存的大小超过该阈值时,就会触发一次 Full GC 进行清理回收,如果在 Full GC 之后还是无法满足堆外内存的分配,那么程序将会抛出 OOM 异常。

  2. 使用unsafe.freeMemory(address); 来回收: DirectByteBuffer 在初始化时会创建一个 Cleaner 对象Cleaner 内同时会创建 Deallocator,调用 Deallocator#run() 来回收。

1) System.gc() 触发

ByteBuffer.allocateDirect 分配的过程中: 如果没有足够的空间分配堆外内存,在 Bits.reserveMemory 方法中也会主动调用 System.gc() ,就会触发 Full GC(并不是马上执行)。 

// ByteBuffer.allocateDirect 直接调用 DirectByteBuffer 构造函数
DirectByteBuffer(int cap) { 
    ... 
    Bits.reserveMemory(size, cap);// 注意这个方法里会调用System.gc();
    ...
}

注意: 如果环境中设置了 -XX:+DisableExplicitGCSystem.gc() 会不起作用的。

所以依赖 System.gc() 并不是一个好办法。

在预定内存时,为什么要主动调用System.gc

既然要调用System.gc,那肯定是想通过触发一次gc操作来回收堆外内存,不过堆外内存不会对gc造成什么影响(这里的System.gc除外),但是堆外内存的回收其实依赖于gc机制,首先要知道在java层面和堆外分配的这块内存关联的只有与之关联的DirectByteBuffer对象了,它记录了这块内存的基地址以及大小,那么既然和gc也有关,那就是gc能通过操作DirectByteBuffer对象来间接操作对应的堆外内存了。DirectByteBuffer对象在创建的时候关联了一个PhantomReference,说到PhantomReference它其实主要是用来跟踪对象何时被回收的,它不能影响gc决策,但是gc过程中如果发现某个对象除了只有PhantomReference引用它之外,并没有其他的地方引用它了,那将会把这个引用放到java.lang.ref.Reference.pending队列里,在gc完毕的时候通知ReferenceHandler这个守护线程去执行一些后置处理,而DirectByteBuffer关联的PhantomReference是PhantomReference的一个子类,在最终的处理里会通过Unsafe的free接口来释放DirectByteBuffer对应的堆外内存块。

2) Cleaner 对象

通过前面堆外内存分配方式的介绍,我们知道 DirectByteBuffer 在初始化时会创建一个 Cleaner 对象,它会负责堆外内存的回收工作,那么 Cleaner 是如何与 GC 关联起来的呢?

先来看下 Cleaner 的源码:

可以看到 Cleaner 属于 PhantomReference 的子类,那 Cleaner#clean() 执行是否跟 JVM GC 或Reference 有关呢? 

TipsJava 对象有四种引用方式, 强引用 StrongReference、软引用 SoftReference、弱引用 WeakReference、虚引用 PhantomReference

这里先了解下 Reference 核心处理流程:

  1. JVM 垃圾收集器扫描到对象 O 可回收。

  2. 把对象 O 对应的 Reference 实例 R 添加到 PendingReference 链表中。

  3. 通知 ReferenceHandler 线程处理,最后完成清理逻辑。

private static class ReferenceHandler extends Thread {
        private static void ensureClassInitialized(Class<?> clazz) {
            try {
                Class.forName(clazz.getName(), true, clazz.getClassLoader());
            } catch (ClassNotFoundException e) {
                throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
            }
        }
        static {
            ensureClassInitialized(InterruptedException.class);
            ensureClassInitialized(Cleaner.class);
        }
        ReferenceHandler(ThreadGroup g, String name) {
            super(g, name);
        }
        public void run() {
            while (true) {
                tryHandlePending(true);
            }
        }
    }

    static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) {
                    r = pending; 
                    //判断是否为 Cleaner
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    if (waitForNotify) {
                        lock.wait();
                    }
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            Thread.yield();
            return true;
        } catch (InterruptedException x) {
            return true;
        }
        if (c != null) {//是Cleaner, 则调用Cleaner.clean()方法
            c.clean();
            return true;
        }
        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();
        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }

总结一下: 当 DirectByteBuffer 被回收的时候,会调用 Cleaner 的 clean() 方法来释放堆外内存。

为什么要使用堆外内存

DirectByteBuffer在创建的时候会通过Unsafe的native方法来直接使用malloc分配一块内存,这块内存是heap之外的,那么自然也不会对gc造成什么影响(System.gc除外),因为gc耗时的操作主要是操作heap之内的对象,对这块内存的操作也是直接通过Unsafe的native方法来操作的,相当于DirectByteBuffer仅仅是一个壳,还有我们通信过程中如果数据是在Heap里的,最终也还是会copy一份到堆外,然后再进行发送,所以为什么不直接使用堆外内存呢。对于需要频繁操作的内存,并且仅仅是临时存在一会的,都建议使用堆外内存,并且做成缓冲池,不断循环利用这块内存。

为什么不能大面积使用堆外内存

如果我们大面积使用堆外内存并且没有限制,那迟早会导致内存溢出,毕竟程序是跑在一台资源受限的机器上,因为这块内存的回收不是你直接能控制的,当然你可以通过别的一些途径,比如反射,直接使用Unsafe接口等,但是这些务必给你带来了一些烦恼,Java与生俱来的优势被你完全抛弃了—开发不需要关注内存的回收,由gc算法自动去实现。另外上面的gc机制与堆外内存的关系也说了,如果一直触发不了cms gc或者full gc,那么后果可能很严重。

巨人的肩膀

一文搞懂堆外内存(模拟内存泄漏) - 掘金---不错

关于JVM堆外内存的一切 - 掘金

Spring Boot引起的“堆外内存泄漏”排查及经验总结 - 掘金---未学习

彻底弄懂零拷贝、MMAP、堆外内存 - 掘金---偏向于操作系统(未学习)

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

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

相关文章

C语言函数章--第二弹(让冤种室友用你的函数,但不给他看函数源码)

前言 &#x1f496;作者&#xff1a;龟龟不断向前 ✨简介&#xff1a;宁愿做一只不停跑的慢乌龟&#xff0c;也不想当一只三分钟热度的兔子。 &#x1f47b;专栏&#xff1a;C初阶知识点 &#x1f47b;工具分享&#xff1a; 刷题&#xff1a; 牛客网 leetcode笔记软件&#xff…

Error注入攻击

&#x1f4aa;&#x1f4aa;Error注入攻击1.创建漏洞环境2.漏洞攻击2.1判断是否有注入2.2信息收集2.3注入获取数据库名2.4注入获取表名2.5注入获取列名2.6注入获取信息3.sql靶场实战1.创建漏洞环境 &#x1f4aa;&#x1f4aa;第一步创建sql环境&#xff0c;直接再mysql下运行 …

Flutter——软件安装与环境配置

Flutter入门官网Flutter SDK下载创建Flutter项目在ios上运行第一个Flutter项目效果图代码总结官网 Flutter开发手册网址如下 Flutter SDK下载 下载地址 第一步&#xff1a;进入官网&#xff0c;选择自己相对应的系统 第二步&#xff1a;选择对应版本SDK并下载到本地 创建Flu…

electron调用dll文件

Electron 对系统层能力的使用可能比较弱&#xff0c;此时需要求助 Python、C、C# 等语言&#xff0c;通过 ffi-napi 库可以让 Node.js 使用 C dll&#xff0c;通过 electron-edge-js 库可以让 Node.js 使用 C# dll 1. 先确定dll文件是用什么语言写的. 使用peid 应用查看- 这个…

【Transformers】第 2 章:主题的实践介绍

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

Node.js | 基于 MongoDB 的简易用户管理系统

&#x1f5a5;️ NodeJS专栏&#xff1a;Node.js从入门到精通 &#x1f5a5;️ 博主的前端之路&#xff08;源创征文一等奖作品&#xff09;&#xff1a;前端之行&#xff0c;任重道远&#xff08;来自大三学长的万字自述&#xff09; &#x1f5a5;️ TypeScript知识总结&…

C++秋招经验贴

文章目录一、个人背景及秋招情况1.个人背景2.秋招情况二、求职C强相关开发岗位的准备过程以及一些建议1. 八股2. 力扣刷题3. 实习4. 项目三、总结一、个人背景及秋招情况 1.个人背景 本科&#xff1a;二本&#xff0c;材料专业   硕士&#xff1a;211硕&#xff0c;光学工程…

TI IWR1642毫米波雷达使用串口原始数据采集与分析

本文编辑&#xff1a;调皮哥的小助理 1.引言 如果文章能够给你带来价值&#xff0c;希望能够关注我。 如果文章能够让你学习到知识&#xff0c;希望你能够点个赞&#xff01; 好了下面开始今天的学习内容吧。 今天给大家分享的是 《TI 的IWR1642毫米波雷达使用串口原始数据…

深度学习入门(十五)环境和分布偏移(了解)

深度学习入门&#xff08;十五&#xff09;环境和分布偏移前言环境和分布偏移教材1 分布偏移的类型1.1 协变量偏移1.2 标签偏移1.3 概念偏移2 分布偏移示例2.1医学诊断2.2 自动驾驶汽车2.3 非平稳分布2.4 更多轶事3 分布偏移纠正3.1 经验风险与实际风险3.2 协变量偏移纠正3.3 标…

MATLAB | 一起来感受数学之美叭

前两天去观摩了MATHWORKS官方举办的Mathematics is beautiful数学之美投票比赛&#xff0c;见到了很多非常惊艳的作品&#xff0c;在这里分享给大家让大家一同感受大神们的创造力&#xff0c;接下来由我来做全程解说。 虽然看起来代码都写好了&#xff0c;&#xff0c;&#x…

程序员眼中看到的网页是如何制作出来的?

一、认识网页 在学习之初&#xff0c;我们需要认识一下网页的概念&#xff0c;因为网页与我们的 html是息息相关的。 那么接下来我们来看一下&#xff0c;我们经常去通过浏览器查看的网页&#xff0c;它的本质是什么&#xff1f;在此我们需要去做一个对比。我们眼中看到的网页…

聚类算法概要及相关知识准备

聚类的概念 聚类分析是在数据中发现数据对象之间的关系&#xff0c;将数据进行分组&#xff0c;组内的相似性越大&#xff0c;组间的差别越大&#xff0c;则聚类效果越好。 将物理或抽象对象的集合分成由类似对象组成的多个类或簇&#xff08;cluster&#xff09;的过程被称为…

SpringBoot+Vue的社区疫情防控管理系统|基于Python+Django的社区物资采购系统

&#x1f496;&#x1f496;作者&#xff1a;IT跃迁谷毕设展 &#x1f499;&#x1f499;个人简介&#xff1a;曾长期从事计算机专业培训教学&#xff0c;本人也热爱上课教学&#xff0c;语言擅长Java、微信小程序、Python、Golang、安卓Android等。平常会做一些项目定制化开发…

Cookie使用详解

Cookie使用详解 目录Cookie使用详解理论知识前言创建Cookiecookie 的属性介绍name 、valuedomainpathExpires 、Max-AgeSameSiteSecure&#xff0c;HttpOnlyCookie与跨域、安全知识点小结实践相关配置修改代码实践实验过程记录其它小结理论知识 前言 HTTP Cookie&#xff08;…

使用Charles和iPhone进行微信小程序抓包详解

基于工作原因&#xff0c;需要对一款微信小程序进行测试。本次任务是纯黑盒方式&#xff0c;所以只有通过抓包的方式找到接口及参数列表&#xff0c;再逐一进行功能和性能测试。 一、使用工具 网络抓包工具&#xff1a;Charles 设备&#xff1a;iPhone6s&#xff0c;iPhone1…

数据分析 | Pandas 200道练习题,每日10道题,学完必成大神(8)

文章目录前期准备1. 将收盘价5日均线&#xff0c;20日均线与原始数据绘制在同一个图上2. 按周为采样规则&#xff0c;取一周收盘价的最大值3. 绘重制采样数据与原始数据4. 将数据往后移动5天、5. 将数据向前移动5天6. 使用expending函数计算开盘价的移动窗口的均值7. 绘制上一题…

牛客刷题系列(汽水瓶,跳台阶扩展问题,斐波那契凤尾)

牛客刷题系列一&#xff1a;汽水瓶题目链接常规写法简便写法二.跳台阶扩展问题三&#xff1a;斐波那契凤尾很多小伙伴为了刷题发愁 今天为大家推荐一款刷题神奇哦&#xff1a;刷题面试神器牛客 各大互联网大厂面试真题。从基础到入阶乃至原理刨析类面试题 应有尽有&#xff0c;…

云IDE介绍——CSDN开发云

云IDE产品介绍云IDE使用教程 免费使用地址&#xff1a;点击【云IDE】&#xff0c;即可开始创建工作空间啦~ 作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xf…

【学习笔记之数据结构】时间复杂度与空间复杂度

一、算法效率 算法在编写成可执行程序后&#xff0c;运行时需要耗费时间资源和空间&#xff08;内存&#xff09;资源。因此衡量一个算法的好坏&#xff0c;一般是从时间和空间两个维度来衡量的&#xff0c;即时间复杂度和空间复杂度。   时间复杂度主要衡量一个算法的运行快…

2022年音视频面试题 C/C++/Linux/FFmpeg/webRTC/rtmp/hls/rtsp/ffplay/srs

1&#xff09;OpenGL 是按照什么架构设计的&#xff1f; OpenGL 的渲染架构是 Client/Server 模式&#xff1a;Client&#xff08;客户端&#xff09;指的是我们在 CPU 上运行的一些代码&#xff0c;比如我们会编写 OC/C/Java 代码调用 OpenGL 的一些 API&#xff1b;而 Server…