GC 基础入门

什么是GC(Garbage Collection)?

内存管理方式通常分为两种:

  • 手动内存管理(Manual Memory Management)
  • 自动内存管理(Garbage Collection, GC)
手动内存管理

手动内存管理是指开发者直接管理内存的分配和释放。典型的语言如C、C++使用malloc/freenew/delete等来手动分配和释放内存。

优点:

  • 精确控制内存的分配和释放。
  • 可预测的性能,没有GC带来的额外开销。
  • 不需要的内存可以立即释放,从而节省资源。

缺点:

  • 存在**内存泄漏(Memory Leak)**问题。
  • 存在**悬挂指针(Dangling Pointer)**问题。
  • 开发复杂度增加。

内存泄漏: 分配的内存未被释放,导致程序运行期间内存占用持续增加,可能导致系统内存耗尽而崩溃。 悬挂指针: 已释放的内存被引用时,可能会引发不可预测的行为。

近年来,Rust通过所有权系统(Ownership System)提供了一种安全的内存管理方式,这并不是完全的手动内存管理,而是手动内存管理的一种新替代方案。

GC(垃圾回收)

GC是编程语言中自动回收不再使用的内存的功能。接下来我们将详细解释这一机制。

自由存储列表(Free-Storage List)

在任何时刻,只有一部分为列表结构预留的内存实际上用于存储S表达式(S-expressions)。其余的寄存器(在我们的系统中大约有15,000个)组成了一个称为**自由存储列表(free-storage list)**的单一列表。程序中的某个特定寄存器FREE包含该列表的第一个寄存器位置。

当需要额外的列表结构时,自由存储列表的第一个单词将被使用,并且寄存器FREE的值会更新为自由存储列表的第二个单词位置。用户不需要编程将寄存器返回到自由存储列表中。

——《Recursive Functions of Symbolic Expressions Their Computation by Machine, Part I》John McCarthy

GC的概念始于1960年,约翰·麦卡锡(John McCarthy)在其论文《Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I》中介绍了自由存储列表(Free-Storage List)的概念,这是后来GC(垃圾回收)的基础思想。

为什么需要这个概念?

LISP(LISt Processor)是一种高级语言,支持符号处理(Symbolic Processing)和递归调用(Recursion)。然而,当时的计算机技术难以应对LISP复杂的内存管理问题。LISP在运行过程中不断生成对象,未使用的对象(垃圾)堆积会导致严重的内存泄漏问题。由于程序员很难完全追踪所有的内存分配和释放,因此引入了自动内存管理技术。

自由存储列表(Free-Storage List)是早期LISP系统中实现自动内存管理的基础概念。当时LISP采用动态生成列表结构的方式,程序员显式释放内存非常困难。为了解决这个问题,引入了跟踪未使用内存块的Free-Storage List概念,这最终发展为自动内存管理的思想。其基本原理是使用特定寄存器(FREE)存储可用内存列表的起始位置,按需获取内存并在不再使用时将其返回。这种思想成为了后来Mark-and-Sweep方式GC的基础。

Newell-Shaw-Simon 的列表存储方式的一个重要特点是,即使相同的数据多次出现,也可以只在计算机内存中存储一次。也就是说,列表可以“重叠(overlapped)”。
然而,这种重叠会在删除(erasure)过程中引发问题。当不再需要某个列表时,必须选择性地删除那些没有与其他列表重叠的部分。在LISP中,麦卡锡(McCarthy)提出了一个优雅但低效的解决方案。
本文则描述了一种能够实现高效删除的通用方法。
——《A Method for Overlapping and Erasure of Lists》 (1963),George E. Collins

尽管约翰·麦卡锡的论文奠定了Mark-and-Sweep方法的基础,但在执行GC时会导致程序暂停(Stop-the-World)现象。这使得实现实时GC变得困难。因此,Collins提出了引用计数(Reference Counting)的概念作为GC方法。
该方法通过为每个对象保存一个引用计数(Reference Count),并在引用计数变为0时立即回收内存。

Collins(1960年)提出的引用计数GC虽然实现了即时内存回收,但由于循环引用问题,并不是一个完美的解决方案。
尽管如此,现代语言如Python和Swift仍然使用这种方法,但加入了一些补充技术来解决其缺陷。

(顺便提一句,“优雅但低效”这样的评价让我觉得有趣。程序员这个职业似乎总有一种特点,他们喜欢用“优雅”的方式讽刺对方。也许是因为讨论的是抽象概念,缺乏实体暴力的存在感?在韩国,如果有人这样说话,可能会被拳头教训吧。)

《A LISP Garbage Collector for Virtual-Memory Computer Systems》论文摘要

本文提出了一种适用于非常大的虚拟内存环境的列表处理系统的垃圾回收算法。
该算法的主要目的不是“释放空闲内存”,而是压缩活动内存(compaction)
在虚拟内存系统中,由于空闲内存实际上并不会耗尽,因此很难决定何时触发垃圾回收。因此,本文讨论了触发垃圾回收的各种条件。
——《A LISP Garbage Collector for Virtual-Memory Computer Systems》 (Fenichel & Yochelson, 1969)

(虚拟内存(Virtual Memory):一种逻辑上管理超出物理内存大小的大容量内存的技术。)

这篇论文改进了Minsky的复制式GC(Copying GC),使其能够在虚拟内存环境中高效运行。
本文实际实现了Minsky的垃圾回收器,它利用深度优先搜索(DFS, Depth-First Search)将可达数据复制到辅助存储器中,分配到新的连续地址后重新加载回内存。尽管有评论认为其实现完成度较低(因为稍后会详细解释,所以这里不需要完全理解),这篇论文是首次将Minsky的方法应用于现代虚拟内存环境中的“Copying GC”,如果没有这篇论文,Java和C#中的分代GC(Generational GC)概念可能就不会出现。

“提出了一种简单的非递归(nonrecursive)列表结构压缩方法或垃圾收集器。该算法适用于紧凑(compact)结构和LISP风格的列表结构。通过逐步利用部分结构来追踪复制的列表,从而消除了对递归的依赖。” — (C. J. Cheney, 1970)

随后,使用广度优先搜索(BFS, Breadth-First Search)而非深度优先搜索(DFS),设计了一种“非递归(Nonrecursive)”的GC,使其能够在没有栈的情况下运行。
这项研究对Java、C#、Python的Copying GC方式产生了重要影响,基于BFS的设计避免了栈溢出问题,并具有缓存友好的结构。

为什么BFS可以以非递归的方式实现,而DFS不能?

首先,大多数读者应该知道,栈是一种**后进先出(LIFO, Last In, First Out)的数据结构。最后添加的元素会最先被移除。
队列则是一种
先进先出(FIFO, First In, First Out)**的数据结构。也就是说,最先添加的元素会最先被移除。

DFS的工作原理

DFS采用一种沿着某条路径深入探索到底,然后回溯(backtracking)的方式。为了实现这一点,DFS需要使用栈(Stack)或递归函数(Recursive Call)。
DFS基于栈的原理如下:

  1. 将当前节点存储在栈中(或通过递归调用)。
  2. 如果有下一个要访问的节点,则继续通过栈调用(或递归调用)进行处理。
  3. 当没有更多可前进的地方时,执行回溯(返回上一步)。
BFS的工作原理

BFS则是基于队列的原理:

  1. 将起始节点添加到队列中(Enqueue)。
  2. 从队列中取出一个节点(Dequeue),访问它,并将其相邻节点重新添加到队列中。
  3. 重复上述过程,直到访问完所有节点。

由于BFS使用队列,因此不需要额外的递归调用。


以洞穴探险为例

DFS

在洞穴探险时,DFS会选择一条路一直走到尽头,如果路被堵住,就会回溯并尝试新的路径。
当路被堵住时,必须记住(存储)返回的路径,否则无法回到原点。

BFS

BFS会在洞口布置一支队伍,同时探索所有的岔路。先确认第一层的所有路径,然后再进入下一层。
为了合理分配队伍,必须优先安排最早到达的岔路。

GC的发展历程

年代

研究者

GC算法

改进点

问题

1960

John McCarthy

Mark-and-Sweep

首次引入GC概念

Stop-the-World,碎片化问题

1963

Marvin L. Minsky

Copying GC(基于DFS)

解决内存碎片化,优化缓存

循环引用问题,磁盘使用

1969

Fenichel & Yochelson

Copying GC(虚拟内存应用)

使用两个半空间(Semispaces)

内存不足时程序变慢

1970

C. J. Cheney

非递归Copying GC(基于BFS)

无需栈即可进行GC

BFS基础,内存重定位优化较少

GC算法发展的意义是什么?

通过减少“Stop-the-World”问题,并逐步向实时(Concurrent)GC发展,我们可以看到GC的进步过程。

那么现在让我们总结一下垃圾回收器有哪些算法,以及这些算法的概述。

在进入主题之前,先简单介绍一些基础知识:

  • 我们通常将内存管理分为两个池:堆内存和栈内存。
  • 栈内存主要用于存储小型(短生命周期)的数据(主要是值类型)。
  • 堆内存则用于存储更大、更持久的数据(主要是引用类型)。

GC算法大致可以分为两类:

1. Tracing GC(追踪型GC)

这是最常见的GC类型。
垃圾回收通过可达性(Reachability)来判断某个对象是否为垃圾。
从根(Root,如全局变量、栈变量等)开始查找可达对象(Reachable),然后销毁不可达对象(Unreachable)。
如果没有有效的引用,对象会被标记为unreachable并被回收。
常用的算法主要有以下三种(附带代码仅为作者学习核心概念实现的伪代码,不保证实际运行效果)。

1-1. Mark-and-Sweep(标记-清除)

(ref: Demystifying memory management in modern programming languages | Technorage)

#include <iostream>
#include <vector>
#include <algorithm>

// Mark-and-Sweep GC 实现(使用原始指针)
class Object {
public:
    bool marked;                       // GC 标记状态
    std::vector<Object*> children;     // 子对象列表
    Object() : marked(false) {}
    
    // 添加子对象的方法
    void addChild(Object* child) {
        children.push_back(child);
    }
    
    // 析构函数:对象被删除时打印日志(用于调试)
    ~Object() {
        std::cout << "Deleting Object at " << this << std::endl;
    }
};

// 全局堆和根对象容器
std::vector<Object*> heap;
std::vector<Object*> roots;

// 递归标记对象及其子对象
void mark(Object* obj) {
    if (obj == nullptr || obj->marked) {
        return;
    }
    obj->marked = true;
    for (Object* child : obj->children) {
        mark(child);
    }
}

// 清除阶段:删除未标记的对象并从堆中移除
void sweep() {
    auto it = heap.begin();
    while (it != heap.end()) {
        Object* obj = *it;
        if (!obj->marked) {
            // 释放无法到达的对象
            delete obj;
            it = heap.erase(it);
        } else {
            // 复位标记以便下次 GC 运行
            obj->marked = false;
            ++it;
        }
    }
    
    // 更新根对象列表,仅保留仍在堆中的对象
    std::vector<Object*> newRoots;
    for (Object* root : roots) {
        if (std::find(heap.begin(), heap.end(), root) != heap.end()) {
            newRoots.push_back(root);
        }
    }
    roots = newRoots;
}

// 执行完整的 GC 过程:标记并清除
void gc() {
    for (Object* root : roots) {
        mark(root);
    }
    sweep();
}

int main() {
    // 创建对象并加入堆中
    Object* a = new Object();
    Object* b = new Object();
    Object* c = new Object();
    
    // 设定对象间的引用关系
    a->addChild(b);
    b->addChild(c);
    
    // 将所有对象加入堆
    heap.push_back(a);
    heap.push_back(b);
    heap.push_back(c);
    
    // 设定根对象(此处 a 作为根对象)
    roots.push_back(a);
    
    std::cout << "Heap size before GC: " << heap.size() << std::endl;
    
    // 执行垃圾回收
    gc();
    
    std::cout << "Heap size after GC: " << heap.size() << std::endl;
    
    // 释放剩余的对象(实际系统中最终 GC 运行时会处理)
    for (Object* obj : heap) {
        delete obj;
    }
    heap.clear();
    roots.clear();
    
    return 0;
}

(如果在代码中使用C++的RAII特性并通过shared_ptr管理内存,则很难观察到GC的实际操作,因此需要使用原始指针。)

  1. Mark阶段 :识别正在使用的对象。
  2. Sweep阶段 :删除不可达对象。
  3. 出现内存碎片化(Fragmentation)问题。

简而言之,从Root Space找到连接的对象,并删除未连接的对象。
优点是实现相对简单,且能有效保留必要的对象,避免浪费内存。
缺点是会出现“Stop-the-World”现象,并产生内存碎片化问题。

内存碎片化 :当内存单元在堆中分配时,其大小取决于存储变量的大小。回收内存时,堆内存会分裂成碎片。

  • 内部碎片化 :已分配内存块中未使用的空间(例如分配4KB但只使用1KB)。
  • 外部碎片化 :可用内存分散,无法分配大块内存。

(img ref: Visualizing memory management in V8 Engine (JavaScript, NodeJS, Deno, WebAssembly) | Technorage )

1-2. Mark-Compact(标记-整理)

在Mark阶段之后,将存活对象移动到内存的一侧以进行整理(Compaction),从而解决碎片化问题。
缺点是整理过程中会产生额外的开销,GC时间较长。

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>

constexpr std::size_t HEAP_SIZE = 1024;

// 对象类 (简单链表结构)
class Object {
public:
    int value;
    Object* next;
    explicit Object(int val) : value(val), next(nullptr) {}

    // 析构函数 (调试用途)
    ~Object() {
        std::cout << "Deleting Object with value " << value << " at " << this << std::endl;
    }
};

// 使用两个半空间 (Semispace) 进行内存管理
std::vector<char> fromSpace(HEAP_SIZE);
std::vector<char> toSpace(HEAP_SIZE);

std::size_t fromIndex = 0;
std::size_t toIndex = 0;

// 在给定的空间中分配对象 (使用 placement new)
Object* allocate(int val, std::vector<char>& space, std::size_t& idx) {
    if (idx + sizeof(Object) > space.size()) {
        return nullptr; // 空间不足
    }
    Object* obj = new (&space[idx]) Object(val);
    idx += sizeof(Object);
    return obj;
}

// 递归复制对象及其链接
Object* copy(Object* obj, std::vector<char>& targetSpace, std::size_t& targetIdx) {
    if (obj == nullptr) {
        return nullptr;
    }
    Object* newObj = allocate(obj->value, targetSpace, targetIdx);
    if (newObj == nullptr) {
        return nullptr;
    }
    newObj->next = copy(obj->next, targetSpace, targetIdx);
    return newObj;
}

// 复制垃圾回收 (Copying GC) - 复制所有可达对象到新的空间
void gc(Object*& root) {
    toIndex = 0;
    Object* newRoot = copy(root, toSpace, toIndex);

    // 交换空间,使新分配的空间成为新的堆
    std::swap(fromSpace, toSpace);
    fromIndex = toIndex;
    root = newRoot;
}

int main() {
    // 在 fromSpace 中分配对象
    Object* a = allocate(10, fromSpace, fromIndex);
    if (a == nullptr) {
        std::cerr << "Allocation failed for object a." << std::endl;
        return 1;
    }
    Object* b = allocate(20, fromSpace, fromIndex);
    if (b == nullptr) {
        std::cerr << "Allocation failed for object b." << std::endl;
        return 1;
    }
    a->next = b;

    std::cout << "Before GC, root object value: " << a->value << std::endl;

    // 运行 GC
    gc(a);

    std::cout << "After GC, copied root object value: " << a->value << std::endl;

    return 0;
}
1-3. Copying GC(复制型GC)

将内存分为两个半空间(Semi-Space),分别称为From-Space和To-Space。
仅将可达对象复制到新空间(To-Space),然后销毁旧空间。
优点是没有内存碎片化,分配速度快;缺点是内存使用效率低(仅使用一半的内存)。

2. Reference Counting(引用计数)
#include <iostream>
#include <unordered_map>

// 对象类:存储简单整数值
class Object {
public:
    int value;
    explicit Object(int val) : value(val) {}

    // 析构函数: 当对象被释放时打印日志
    ~Object() {
        std::cout << "Deleting Object with value " << value << " at " << this << std::endl;
    }
};

// 全局引用计数表
std::unordered_map<Object*, int> refTable;

// 创建对象并初始化其引用计数
Object* createObject(int val) {
    Object* obj = new Object(val);
    refTable[obj] = 1;
    return obj;
}

// 增加对象的引用计数
void addRef(Object* obj) {
    if (obj != nullptr) {
        auto it = refTable.find(obj);
        if (it != refTable.end()) {
            ++(it->second);
        }
    }
}

// 释放对象的引用:当引用计数为0时删除对象
void releaseRef(Object* obj) {
    if (obj != nullptr) {
        auto it = refTable.find(obj);
        if (it != refTable.end()) {
            if (--(it->second) == 0) {
                refTable.erase(it);
                delete obj;
            }
        }
    }
}

int main() {
    // 创建对象
    Object* a = createObject(10);
    Object* b = createObject(20);

    // 增加 b 的引用计数 (模拟多个地方使用)
    addRef(b);

    // 释放 b 的引用:两次调用后计数变为0,对象被释放
    releaseRef(b);
    releaseRef(b);

    // 释放 a
    releaseRef(a);

    std::cout << "Remaining objects in refTable: " << refTable.size() << std::endl;
    return 0;
}

(使用全局引用表unordered_map,在创建对象时将引用计数初始化为1,并通过addRefreleaseRef调整引用计数。当引用计数为0时调用delete。)

每个对象存储一个引用计数(Reference Count),当引用计数为0时立即释放内存。
由于可以立即释放内存,因此没有“Stop-the-World”现象,且对象的使用量清晰可见。
缺点是存在循环引用问题,并且在处理大型对象时性能下降。
Swift中使用了改进版的ARC(Automatic Reference Counting)。

核心总结

分类

Tracing GC (Mark & Sweep)

Reference Counting GC

基本概念

从根开始追踪并清理对象

根据对象的引用计数释放内存

内存泄漏可能性

可能存在循环引用(GC自动解决)

循环引用导致内存泄漏

GC执行成本

与对象数量和引用图大小成正比

对象越多计算负担越大

性能

对象越多性能越可能下降

引用计数立即归零时快速释放

Stop-the-World(STW)

GC执行时暂停

立即释放,无需暂停

典型应用场景

C#, Java, Python

Objective-C (ARC), Swift

此外,除了基本的GC算法外,还有一些优化策略。

GC优化策略有哪些?

最常见的是Generational GC(分代GC)

1. Generational GC(分代GC)

根据对象的生存周期将其分为Young Generation和Old Generation,并对新生代(Young)快速GC,对老年代(Old)缓慢GC。
接下来我们看看Young和Old是如何划分的。

1-1. Young Generation(Eden + Survivor Space)
  • Eden(伊甸园)区域
    新创建的对象首先分配到这里。大多数对象在这里生成后很快就会被销毁,因此GC频繁发生。
  • Survivor(幸存者)区域
    Eden区域中经过GC后存活的对象会移动到这里。通常有两个Survivor区域。对象多次经历GC后最终晋升到Old Generation。
    Survivor区域通常分为两个:Survivor0(From Space)和Survivor1(To Space)。
    在这里,新创建的对象存储在此,大部分对象会快速销毁。此区域使用Copying GC快速移除对象。
    当该区域中的对象被移除时,称为Minor GC。
1-2. Old Generation(老年代)
  • Tenured(老年)区域
    多次在Young Generation中经历GC后存活的对象会移动到这里。
    此区域GC较少发生,但一旦发生,涉及的对象较多,处理成本较高。
    老年代对象存储在这里,使用Mark-Sweep或Mark-Compact方式进行清理。
    利用对象生命周期模式进行性能优化。当此区域中的对象被移除时,称为Major GC(或Full GC)。
    Young GC快速执行,Old GC较慢执行,但在Old Generation中发生GC时可能会出现“Stop-the-World”。

通常,Old区域分配较大,因此GC发生的频率低于Young区域。

可以用人类一生的例子来说明:

  1. 在Eden(伊甸园)中以灵魂状态存在;
  2. 接受世界的召唤出生;
  3. 经历世间的风雨洗礼(Survivor),无事故地幸存下来;
  4. 成为老人,获得社会尊重(Promotion);
  5. 受人尊敬的老人去世后,社会上更多的人哀悼(比年轻人更大的哀悼成本)。

2. Parallel GC(并行GC)

使用多个线程(Thread)并行执行GC,提升GC速度。适用于大型应用程序,但可能存在线程同步开销。Java HotSpot JVM实现了Parallel GC。
需要注意的是,分代GC和并行GC并不互斥,因此可以同时使用,实际上JVM HotSpot和.NET中也确实如此。

(Time Freeze!!!)

那么GC是如何执行的呢?

1. Stop-the-World(STW)

GC执行时程序完全停止。
这是传统的GC方式,实现相对简单,能够准确回收内存。
但会导致响应时间增加,用户体验下降。

(img ref:An attempt at visualizing the Go GC)

2. Incremental GC(增量GC)

将GC分成多个小步骤(Small Steps)执行,基于Dijkstra的三色标记算法(Tri-Color Marking Algorithm)。

这个算法的核心是高效地标记三种颜色:

  • 白色 :未处理(Unprocessed)
  • 灰色 :处理中(Processing)
  • 黑色 :已处理(Processed)

节点着色的规则如下:

  1. 所有节点初始为白色。
  2. 访问对象时标记为灰色。
  3. 访问完成后标记为黑色。

这样就形成了三个集合,对象从白色→灰色→黑色移动。重要的是,白色和黑色之间不会直接连接。

3. Concurrent GC(并发GC)

GC与应用程序同时运行,尽量减少“Stop-the-World”时间。
GC线程独立运行,并与其他应用程序线程同时进行内存标记(mark)或复制等操作。
这使得GC执行期间应用程序仍能运行,并利用多核优势,但内存管理变得更加复杂。

“过早优化是万恶之源。”——Donald Knuth

本文介绍了GC的历史、当前使用的GC算法、优化策略及执行方法。后续还提到了避免GC的ZoC(Zero Allocation)等内容,但由于本文面向初学者,部分内容(如某些优化策略)被省略了。

喜欢手动管理内存的人可能会将GC视为罪恶,认为它增加了编程的不确定性。
但正如Donald Knuth所说,与其沉迷于内存管理这样的“过早优化”,不如让GC帮助开发者专注于解决问题。
程序员的角色不仅是掌握技术,更是解决眼前的问题。
GC为许多程序员减轻了“内存管理”的负担,是一项重要的工具。

重要的是,不是“GC好还是不好”,而是思考“哪种工具最适合解决我的问题”。

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

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

相关文章

dify.ai 配置链接到阿里云百练等云厂商的 DeepSeek 模型

要将 dify.ai 配置链接到阿里云百练等云厂商的 DeepSeek 模型. 申请阿里云百练的KEY 添加模型 测试模型

C++ Primer 函数重载

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

【MySql】应用系统等保测评MySQL服务器相关策略设置以及最终验证,MySQL安全策略设置以及最终验证

文章目录 一、概要二、环境及实现三、前期准备四、操作步骤1、所有的数据库需要设置三权账户&#xff1a;系统管理员、网络管理员和安全管理员创建系统管理员账户&#xff1a;创建网络管理员账户&#xff1a;创建安全管理员账户&#xff1a; 2、所有数据库密码的负责度策略需要…

【吾爱出品】针对红警之类老游戏适用WIN10和11的补丁cnc-ddraw7.1汉化版

针对红警之类老游戏适用WIN10和11的补丁cnc-ddraw7.1汉化版 链接&#xff1a;https://pan.xunlei.com/s/VOJ8PZd4avMubnDzHQAeZDxWA1?pwdnjwm# 直接复制到游戏安装目录&#xff0c;保持与游戏主程序同目录下。

28 在可以控制 postgres 服务器, 不知道任何用户名的情况下怎 进入 postgres 服务器

前言 最近有这样的一个需求, 有一个 postgres 服务器 但是 不知道 他的任何的用户名密码, 但是我想要查询这台 postgres 服务器 然后 基于这个需求, 我们看一下 怎么来处理 pg_hba.conf 认证方式修改为 trust 首先将 postgres 服务器的认证方式修改为 trust 这时候 …

Mac ARM 架构的命令行(终端)中,删除整行的快捷键是:Ctrl + U

在 Mac ARM 架构的命令行&#xff08;终端&#xff09;中&#xff0c;删除整行的快捷键是&#xff1a; Ctrl U这个快捷键会删除光标所在位置到行首之间的所有内容。如果你想删除光标后面的所有内容&#xff0c;可以使用&#xff1a; Ctrl K这两个快捷键可以帮助你快速清除当…

编程题-最大子数组和(中等-重点【贪心、动态规划、分治思想的应用】)

题目&#xff1a; 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组是数组中的一个连续部分。 解法一&#xff08;枚举法-时间复杂度超限&#xff09;&#xff1a; …

aws(学习笔记第二十八课) aws eks使用练习(hands on)

aws(学习笔记第二十八课) 使用aws eks 学习内容&#xff1a; 什么是aws eksaws eks的hands onaws eks的创建applicationeks和kubernetes简介 1. 使用aws eks 什么是aws eks aws eks的概念 aws eks是kubernetes在aws上包装出来 的新的方式&#xff0c;旨在更加方便结合aws&…

解读 Flink Source 接口重构后的 KafkaSource

前言 Apache Kafka 和 Apache Flink 的结合&#xff0c;为构建实时流处理应用提供了一套强大的解决方案[1]。Kafka 作为高吞吐量、低延迟的分布式消息队列&#xff0c;负责数据的采集、缓冲和分发&#xff1b;而 Flink 则是功能强大的流处理引擎&#xff0c;负责对数据进行实时…

deepseek多列数据对比,联想到excel的高级筛选功能

目录 1 业务背景 ​2 deepseek提示词输入 ​3 联想分析 4 EXCEL高级搜索 1 业务背景 系统上线的时候经常会遇到一个问题&#xff0c;系统导入的数据和线下的EXCEL数据是否一致&#xff0c;如果不一致&#xff0c;如何快速找到差异值&#xff0c;原来脑海第一反应就是使用公…

ubuntu20.04声音设置

step1&#xff1a;打开pavucontrol&#xff0c;设置Configuration和Output Devices&#xff0c; 注意需要有HDMI / DisplayPort (plugged in)这个图标。如果没有&#xff0c;就先选择Configuration -> Digital Stereo (HDMI 7) Output (unplugged) (unvailable)&#xff0c;…

uniapp可视化-活动报名表单系统-代码生成器

活动报名表单系统小程序旨在为各类活动组织者提供一个便捷、高效的线上报名管理平台&#xff0c;同时为参与者提供简单易用的报名途径。 主要功能 活动发布&#xff1a;活动组织者可以发布活动的详细信息&#xff0c;包括活动名称、时间、地点、活动内容等。用户可以在小程序中…

DeepSeek 助力 Vue 开发:打造丝滑的无限滚动(Infinite Scroll)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

SpringBoot+shardingsphere实现按月分表功能

SpringBootshardingsphere实现按月分表功能 文章目录 前言 ShardingSphere 是一套开源的分布式数据库中间件解决方案&#xff0c;旨在简化数据库分片、读写分离、分布式事务等复杂场景的管理。它由 Apache 软件基金会支持&#xff0c;广泛应用于需要处理大规模数据的系统中 一…

大模型训练为什么依赖GPU

近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;特别是深度学习领域的进步&#xff0c;大模型的训练逐渐成为研究和工业界的热点。作为大模型训练中的核心硬件&#xff0c;GPU&#xff08;图形处理单元&#xff09;扮演了至关重要的角色。那么&#xff0c;为什么大模…

SQL布尔盲注+时间盲注

1.布尔盲注 双重for循环 import requestsurl http://127.0.0.1/sqli-labs-master/Less-8/index.phpdef database_name():datebasename for i in range(1, 9): # 假设数据库名称最多8个字符for j in range(32, 128): # ascii 可见字符范围从32到127payload f"?id1 A…

收银系统源码开发指南:PHP + Flutter + Uniapp 全栈方案

收银系统一般涵盖了收银POS端、线上商城端和管理端&#xff0c;技术栈涉及PHP、Flutter和Uniapp。为了确保系统的稳定运行和持续发展&#xff0c;在开发和运营过程中需要重点关注以下几个方面&#xff1a; 一、系统架构与性能优化 模块化设计: 将系统拆分为独立的模块&#xf…

springCloud-2021.0.9 之 GateWay 示例

文章目录 前言springCloud-2021.0.9 之 GateWay 示例1. GateWay 官网2. GateWay 三个关键名称3. GateWay 工作原理的高级概述4. 示例4.1. POM4.2. 启动类4.3. 过滤器4.4. 配置 5. 启动/测试 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收…

Vue.js 在低代码开发平台中的应用与优化

Vue.js 在低代码开发平台中的应用与优化 在数字化转型的进程中&#xff0c;低代码开发平台成为了企业快速构建应用的得力助手。而 Vue.js 作为一款广受欢迎的前端框架&#xff0c;在低代码开发平台中发挥着举足轻重的作用。它不仅提升了开发效率&#xff0c;还优化了应用的用户…

大模型Deepseek的使用_基于阿里云百炼和Chatbox

目录 前言1. 云服务商2. ChatBox参考 前言 上篇博文中探索了&#xff08;本地&#xff09;部署大语言模型&#xff0c;适合微调、数据高隐私性等场景。随着Deepseek-R1的发布&#xff0c;大语言模型的可及性得到极大提升&#xff0c;应用场景不断增加&#xff0c;对高可用的方…