netty5学习笔记-内存池4-PoolArena

2023-10-29

        前面我们讲到了内存池中的几个重要的类:

        1、PoolChunk:维护一段连续内存,并负责内存块分配与回收,其中比较重要的两个概念:page:可分配的最小内存块单位;chunk:page的集合;

        2、PoolSubpage:将page分为更小的块进行维护;

        3、PoolChunkList:维护多个PoolChunk的生命周期。

        多个PoolChunkList也会形成一个list,方便内存的管理。最终由PoolArena对这一系列类进行管理,PoolArena本身是一个抽象类,其子类为HeapArena和DirectArena,对应堆内存(heap buffer)和堆外内存(direct buffer),除了操作的内存(byte[]和ByteBuffer)不同外两个类完全一致。Arena:竞技场,这个名字听起来比较霸气,不同的对象会到这里来抢夺资源(申请内存)。下面我们来看看这块竞技场上到底发生了些什么事情,首先看看类里面的字段:

    static final int numTinySubpagePools = 512 >>> 4;

    final PooledByteBufAllocator parent;
    // 下面这几个参数用来控制PoolChunk的总内存大小、page大小等
    private final int maxOrder;
    final int pageSize;
    final int pageShifts;
    final int chunkSize;
    final int subpageOverflowMask;
    final int numSmallSubpagePools;
    private final PoolSubpage<T>[] tinySubpagePools;
    private final PoolSubpage<T>[] smallSubpagePools;
    // 下面几个chunklist又组成了一个link list的链表
    private final PoolChunkList<T> q050;
    private final PoolChunkList<T> q025;
    private final PoolChunkList<T> q000;
    private final PoolChunkList<T> qInit;
    private final PoolChunkList<T> q075;
    private final PoolChunkList<T> q100;
        前面我们讲过PoolSubpage在初始化以后会交由arena来管理,那么他究竟是怎么管理的呢。之前我们提到所有内存分配的size都会经过normalizeCapacity进行处理,而这个方法的处理方式是,当需求的size>=512时,size成倍增长,及512->1024->2048->4096->8192->...,而需求size<512则是从16开始,每次加16字节,这样从[512,8192)有四个不同值,而从[16,512)有32个不同值。这样tinySubpagePools的大小就是32,而tinySubpagePool的大小则是4,并且从index=0 -> max, 按照从小到大的顺序缓存subpage的数据。如tinySubpagePool[0]上是用来分配大小为16的内存,tinySubpagePool[1]分配大小为32的内存,smallSubpagePools[0]分配大小为512的内存。这样如果需要分配小内存时,只需要计算出index,到对应的index的pool查找缓存的数据即可。需要注意的是subpage从首次分配到释放的过程中,只会负责一个固定size的内存分配,如subpage初始化的时候是分配size=512的内存,则该subpage剩下的所有内存也是用来分配size=512的内存,直到其被完全释放后从arena中去掉。如果下次该subpage又重新被分配,则按照此次的大小size0分配到固定的位置,并且该subpage剩下的所有内存用来分配size=size0的内存。

        netty将内存分为tiny(0,512)、small[512,8K)、normal【8K,16M]、huge[16M,)这四种类型,使用tcp进行通信时,初始的内存大小默认为1k,并会在64-64k之间动态调整(以上为默认参数,见AdaptiveRecvByteBufAllocator),在实际使用中小内存的分配会更多,因此这里将常用的小内存(subpage)前置。

        可能有同学会问这几个PoolChunkList为什么这么命名,其实是按照内存的使用率来取名的,如qInit代表一个chunk最开始分配后会进入它,随着其使用率增大会逐渐从q000到q100,而随着内存释放,使用率减小,它又会慢慢的从q100到q00,最终这个chunk上的所有内存释放后,整个chunk被回收。我们来看看这几个chunk list的顺序:

        q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE);
        q075 = new PoolChunkList<T>(this, q100, 75, 100);
        q050 = new PoolChunkList<T>(this, q075, 50, 100);
        q025 = new PoolChunkList<T>(this, q050, 25, 75);
        q000 = new PoolChunkList<T>(this, q025, 1, 50);
        qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25);

        q100.prevList = q075;
        q075.prevList = q050;
        q050.prevList = q025;
        q025.prevList = q000;
        // q000没有前置节点,则当一个chunk进入q000后,如果其内存被完全释放,则不再保留在内存中,其分配的内存被完全回收
        q000.prevList = null;
        // qInit前置节点为自己,且minUsage=Integer.MIN_VALUE,意味着一个初分配的chunk,在最开始的内存分配过程中(内存使用率<25%),
        // 即使完全回收也不会被释放,这样始终保留在内存中,后面的分配就无需新建chunk,减小了分配的时间
        qInit.prevList = qInit;
        分配内存时先从内存占用率相对较低的chunklist中开始查找,这样查找的平均用时就会更短:

    private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
        if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
            q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
            q075.allocate(buf, reqCapacity, normCapacity) || q100.allocate(buf, reqCapacity, normCapacity)) {
            return;
        }

        // Add a new chunk.
        PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
        long handle = c.allocate(normCapacity);
        assert handle > 0;
        c.initBuf(buf, handle, reqCapacity);
        qInit.add(c);
    }
        上面是分配内存时的查找顺序,验证了上面说的从内存使用率相对较低的chunklist中查找。

        这里为什么不是从较低的q000开始呢,在分析PoolChunkList的时候,我们知道一个chunk随着内存的不停释放,它本身会不停的往其所在的chunk list的prev list移动,直到其完全释放后被回收。 如果这里是从q000开始尝试分配,虽然分配的速度可能更快了(因为分配成功的几率更大),但一个chunk在使用率为25%以内时有更大几率再分配,也就是一个chunk被回收的几率大大降低了。这样就带来了一个问题,我们的应用在实际运行过程中会存在一个访问高峰期,这个时候内存的占用量会是平时的几倍,因此会多分配几倍的chunk出来,而等高峰期过去以后,由于chunk被回收的几率降低,内存回收的进度就会很慢(因为没被完全释放,所以无法回收),内存就存在很大的浪费。

        为什么是从q050开始尝试分配呢,q050是内存占用50%~100%的chunk,猜测是希望能够提高整个应用的内存使用率,因为这样大部分情况下会使用q050的内存,这样在内存使用不是很多的情况下一些利用率低(<50%)的chunk慢慢就会淘汰出去,最终被回收。

        然而为什么不是从qinit中开始呢,这里的chunk利用率低,但又不会被回收,岂不是浪费?

        q075,q100由于使用率高,分配成功的几率也会更小,因此放到最后(q100上的chunk使用率都是100%,为什么还要尝试从这里分配呢??)。如果整个list中都无法分配,则新建一个chunk,并将其加入到qinit中。

        需要注意的是,上面这个方法已经是被synchronized修饰的了,因为chunk本身的访问不是线程安全的,因此我们在实际分配内存的时候必须保证线程安全,防止同一个内存块被多个对象申请到。

        前面我们再回过头来看看整个内存分配的代码:

    private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
        final int normCapacity = normalizeCapacity(reqCapacity);
        if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
            int tableIdx;
            PoolSubpage<T>[] table;
            if (isTiny(normCapacity)) { // < 512
            	// 小内存从tinySubpagePools中分配
                if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
                    // was able to allocate out of the cache so move on
                    return;
                }
                tableIdx = tinyIdx(normCapacity);
                table = tinySubpagePools;
            } else {
            	// 略大的小内存从smallSubpagePools中分配
                if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
                    // was able to allocate out of the cache so move on
                    return;
                }
                tableIdx = smallIdx(normCapacity);
                table = smallSubpagePools;
            }

	    // subpage的分配方法不是线程安全,所以需要在实际分配时加锁
            synchronized (this) {
                final PoolSubpage<T> head = table[tableIdx];
                final PoolSubpage<T> s = head.next;
                if (s != head) {
                    assert s.doNotDestroy && s.elemSize == normCapacity;
                    long handle = s.allocate();
                    assert handle >= 0;
                    s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
                    return;
                }
            }
        } else if (normCapacity <= chunkSize) {
            if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
                // was able to allocate out of the cache so move on
                return;
            }
        } else {
            // Huge allocations are never served via the cache so just call allocateHuge
            allocateHuge(buf, reqCapacity);
            return;
        }
        allocateNormal(buf, reqCapacity, normCapacity);
    }

        从上面的代码我们可以看到除了大内存的分配,都是先尝试从cache中分配,如果无法完成分配则再走其他流程。这个cache有何作用? 它是利用ThreadLocal的特性,去除锁竞争,提高内存分配的效率(后面会单独讲)。 对于小内存(小于8K)的分配,则是先尝试从对应大小的PoolSubpage中分配,如果分配不到再通过allocateNormal分配,如一个size=16的请求,会尝试从tinySubpagePools[0]中尝试,而size=1024则会从smallSubpagePools[1]中尝试,以此类推。这里需要注意的是,在tinySubpagePools和smallSubpagePools每个位置上只有一个PoolSubpage,但PoolSubpage本身有next和prev两个属性,所以其实这里代表的是一个link list而不是单个PoolSubpage。但是在从cache的PoolSubpage中分配内存时,只做了一次尝试,即尝试从head.next中取,那这个link list还有什么用呢? 其实前面在PoolSubpage的分析中讲到,一个subpage如果所有内存都已经分配,则会从这个link list中移除,并且在有部分内存释放时再加入,在内存完全释放时彻底从link list中移除。因此可以保证如果link list中有数据节点,则第一个节点以及后面的所有节点都有可分配的内存,因此不需要分配多次。需要注意PoolSubpage在将自身从link list中彻底移除时有一个策略,即如果link list中没有其他节点了则不进行移除,这样arena中一旦分配了某个大小的小内存后始终存在可以分配的节点。

        超大内存由于本身复用性并不高,因此没有做其他任何策略。不用pool而是直接分配一个大内存,用完后直接回收。

    private void allocateHuge(PooledByteBuf<T> buf, int reqCapacity) {
        buf.initUnpooled(newUnpooledChunk(reqCapacity), reqCapacity);
    }
        内存回收代码相对比较简单,主要分成3步:

        1、如果chunk不是pool的(如上面的huge方式分配的),则直接销毁(回收);

        2、如果分配线程和释放线程是同一个线程, 则先尝试往ThreadLocal的cache中放,此时由于用到了ThreadLocal,没有线程安全问题,所以不加锁;

        3、如果cache已满(或者其他原因导致无法添加,这里先不深入),则通过其所在的chunklist进行释放,这里的chunklist释放会涉及到对应内存块的释放,chunk在chunklist之间的移动和chunk的销毁,细节见PoolChunkList的分析。

    void free(PoolChunk<T> chunk, long handle, int normCapacity, boolean sameThreads) {
        if (chunk.unpooled) {
            destroyChunk(chunk);
        } else {
            if (sameThreads) {
                PoolThreadCache cache = parent.threadCache.get();
                if (cache.add(this, chunk, handle, normCapacity)) {
                    // cached so not free it.
                    return;
                }
            }


            synchronized (this) {
                chunk.parent.free(chunk, handle);
            }
        }
    }
        到这里内存的分配、释放、销毁都涉及到了,arena这块是不是可以愉快的结束了。有的朋友可能就会问了,如果我申请的内存再使用的过程中发现不够了,需要扩张该怎么办,或者我不需要那么多内存,想要个更小的该怎么办,其实很简单,就是重新申请一个更大的内存,将之前的数据拷贝到新的内存,并释放掉之前申请的内存。我们来看看其实现:

    void reallocate(PooledByteBuf<T> buf, int newCapacity, boolean freeOldMemory) {
        if (newCapacity < 0 || newCapacity > buf.maxCapacity()) {
            throw new IllegalArgumentException("newCapacity: " + newCapacity);
        }

	// 新老内存相同,不用重新分配
        int oldCapacity = buf.length;
        if (oldCapacity == newCapacity) {
            return;
        }

        PoolChunk<T> oldChunk = buf.chunk;
        long oldHandle = buf.handle;
        T oldMemory = buf.memory;
        int oldOffset = buf.offset;
        int oldMaxLength = buf.maxLength;
        int readerIndex = buf.readerIndex();
        int writerIndex = buf.writerIndex();

				// 重新申请一块内存
        allocate(parent.threadCache.get(), buf, newCapacity);
        if (newCapacity > oldCapacity) {
        		// 如果新申请的内存比之前小则直接将之前内存中的数据拷贝到新的内存中
            memoryCopy(
                    oldMemory, oldOffset,
                    buf.memory, buf.offset, oldCapacity);
        } else if (newCapacity < oldCapacity) {
            if (readerIndex < newCapacity) {
                if (writerIndex > newCapacity) {
                    writerIndex = newCapacity;
                }
                // 将可读的数据拷贝过来,不可读的不拷贝
                memoryCopy(
                        oldMemory, oldOffset + readerIndex,
                        buf.memory, buf.offset + readerIndex, writerIndex - readerIndex);
            } else {
            		// 如果之前的readerIndex比新的内存总大小还大,则没有可以读取的数据了,也无法写
                readerIndex = writerIndex = newCapacity;
            }
        }

        buf.setIndex(readerIndex, writerIndex);

        if (freeOldMemory) {
        		// 释放之前的内存
            free(oldChunk, oldHandle, oldMaxLength);
        }
    }

        到这里,内存的分配、释放、重分配、回收,都已经讲到,剩下一个ThreadLocal的优化策略,休息休息后面再讲。




本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

netty5学习笔记-内存池4-PoolArena 的相关文章

  • Netty UDP 服务器引导程序

    我发现 Netty 4 0 的所有 TCP 服务器实现都使用 ServerBootstrap 实例 服务器和客户端之间最大也是唯一的区别 Netty的不同之处在于Bootstrap和Channel的实现是不同的 用过的 请看一下下面的代码
  • Netty 与 Apache MINA

    它们都提供大致相同的功能 我应该选择哪一款来开发我的高性能 TCP 服务器 有什么优点和缺点 参考链接 阿帕奇米娜 source Netty source 虽然 MINA 和 Netty 有着相似的目标 但它们在实践中却有很大不同 您应该仔
  • Micronaut ReadTimeoutException 异常

    我有一个提供 REST API 的 Grails 4 应用程序 端点之一有时会失败 但会出现以下异常 io micronaut http client exceptions ReadTimeoutException Read Timeout
  • netty-daxin-4(http&websocket)

    文章目录 http 服务端 NettyHttpServer HelloWorldServerHandler 客户端 ApiPost
  • netty源码:(24)EventExecutorChooserFactory类

    该类定义了一个内部接口EventExecutorChooser 该接口有一个方法 next EventExecutorChooserFactory类有个默认实现类 DefaultEventExecutorChooserFactory 该类有
  • Netty的并发编码

    编码器的encode方法会并发执行吗 我观察到编码方法可能是由不同线程并发的 管道定义为 Channels pipeline idleHandler new AmfDecoder
  • 如何使用 netty 通过 HTTP 传输响应

    我正在使用 Netty 3 6 6 我想向调用者发送一个大的响应 我无法将响应正文复制到 ChannelBuffer 中 因为在某些情况下它会非常大 我正在将服务器从CXF迁移到Netty 以前 我只能使用CXF提供的OutputStrea
  • Netty异步写入响应和大小未知的大数据

    我开发了一个netty http服务器 但是当我在方法ChannelInboundHandlerAdapter channelRead0中写入响应时 我的响应结果来自另一台服务器 并且结果的大小未知 因此它的http响应标头可能具有内容长度
  • 出站 ChannelHandler 的捕获所有异常处理

    在 Netty 中 您有入站和出站处理程序的概念 只需在管道的末尾 尾部 添加一个通道处理程序并实现一个捕获所有入站异常处理程序即可实现exceptionCaught覆盖 如果未沿途处理 沿入站管道发生的异常将沿着处理程序传播 直到遇到最后
  • 牛客字符串

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 pandas是什么 二 使用步骤 1 引入库 2 读入数据 总结 前言 提示 这里可以添加本文要记录的大概内容 例如 随着人工智能的不断发展 机器学习这门
  • 何时在 keepalive 打开的情况下调用channelInactive?

    我有一个 SimpleChannelInboundHandler 处理一些 http 请求 CacheServerHandler extends SimpleChannelInboundHandler
  • netty中非阻塞通道中的SO_TIMEOUT

    如果通道在超时毫秒内未收到读取 响应 SO TIMEOUT 是否会使非阻塞通道过期 bootstrap group workerGroup channel NioSocketChannel class handler channelInit
  • 如何知道Netty ByteBuf中是否没有数据可读取?

    我是 Netty 新手 文件传输的问题让我困惑了好几天 我想发送image文件从客户端到服务器 下面的代码是可执行的 但只有我shutdown服务器强制我可以正常打开收到的图像文件 否则 显示 您似乎没有查看此文件的权限 检查权限并重试 所
  • Netty 4. ByteToMessageCodec之后的并行处理

    If a NioEventLoopGroup被用作workerGroup 之后的消息ByteToMessageDecoder处理程序 对于单个连接 通过以下处理程序以顺序 单线程 方式处理NioEventLoop 是否有可能让它们在之后由另
  • netty ChannelInboundHandlerAdapter 将帧裁剪为 ~1500 字节

    我已经实现了一个服务器应用程序 它使用 netty 框架通过 ChannelInblundHandlerAdapter 读取传入的字节 如标题所示 我的问题是 我不定期地从客户端获取内容 我认为这些内容在 1 500 字节后被剪切 例如 在
  • Netty websocket客户端闲置5分钟后不从服务器读取新帧

    我在服务器端和客户端都使用 Netty 来建立和控制 websocket 连接 我在服务器端有一个空闲状态处理程序 http netty io 4 1 api io netty handler timeout IdleStateHandle
  • Netty 处理程序未调用

    我正在尝试使用简单的服务器客户端应用程序进入 Netty 代码见下文 我正在努力解决两个问题 ConfigServerHandler 分别ConfigClientHandler 被正确调用 但是 FeedbackServerHandler
  • 为什么我们真的需要多个 Netty boss 线程?

    我真的很困惑老板组的线程数量 我无法弄清楚我们需要多个老板线程的场景 在Boss 组是否需要多个线程 https stackoverflow com questions 22280916 do we need more than a sin
  • 如何使用 Netty 发送对象?

    如何通过Netty从服务器端发送bean并在客户端接收该bean 当我发送简单的整数消息 inputstream 时 它工作成功 但我需要发送 bean 如果您在客户端和服务器端使用 Netty 那么您可以使用 Netty对象解码器 htt
  • Netty:奇怪的 IndexOutOfBoundsException:readerIndex + 长度超过 writerIndex

    我目前正在通过 netty 发送不同的数据包 并且经常遇到这样的异常收到它们时 java lang IndexOutOfBoundsException readerIndex 39 length 32 exceeds writerIndex

随机推荐

  • jquery ztree实现下拉树形框,json数据

    公司最近的项目需要用到树形下拉框 在网上找了挺多源码 最后还是采用了zTree来实现 因为代码的移植性比较高 而且数据的获取比较容易 废话不多说 直接上代码 index jsp
  • 轻量应用服务器腾讯云和阿里云哪家更好?

    轻量应用服务器阿里云和腾讯云哪个好 云服务器吧认为腾讯云轻量应用服务器还不错 腾讯云轻量服务器30元起 配置可选2核2G3M 2核2G4M 2核4G5M 4核8G12M 8核16G18M和16核32G28M 阿里云轻量应用服务器配置仅可以选
  • C语言函数大全-- w 开头的函数(1)

    w 开头的函数 1 1 wcscat 1 1 函数说明 1 2 演示示例 1 3 运行结果 2 wcschr 2 1 函数说明 2 2 演示示例 2 3 运行结果 3 wcscmp 3 1 函数说明 3 2 演示示例 3 3 运行结果 4
  • AHB接口总线仲裁1主多从细节。关于hready in和hready out信号的理解

    所有slaver 只要有1个hready out等于0 表示slaver没有准备好执行此拍操作 这时 所有slaver都不能执行此拍操作 以防止1主多从的AHB接口协议问题 所有slaver的hreadyout 需要与之后 提供给所有sla
  • linux下c++操作MySQL

    第一步 先在Linux中安装MySQL 第二步 在安装连接器 中端命令 sudo apt get install libmysqlclient dev 第三步写一个操作MySQL的c 代码保存退出 include
  • allegro如何对差分对单根网络进行等长调节

    选中要调整的差分线 再点击左栏的快捷按钮 此时options会弹出相关的调整s参数 设置好差分线参数 鼠标放在刚才选中的线上拉出一个方框 方框内就会显示线的形状变化 再次点击鼠标后 就会把线绘制成曲线形状 蛇形走线 在这里注意 如果调整走线
  • Redis缓存穿透, 击穿, 雪崩

    缓存穿透 缓存穿透是指用户想要查询一个数据 发现redis内存中没有 也就是没有缓存命中 于是向持久层数据库查询 发现也没有 于是本次查询失败 当用户很多的时候 缓存中都没有 于是都去请求持久层数据库 这会给持久层数据库造成很大压力 这就是
  • kafka 不支持读写分离的原因

    前段时间在看 kafka 相关内容 发现 kafka 所有的 读写流量都在主 partition 上 从 partition 只负责备份数据 那么为什么 kafka 从 partition 不跟其他中间件一样承接读流量 读写分离的初衷 读写
  • LoadRunner11 脚本关联操作-(学习中)

    一 首先了解关联的定义 服务器返回给客户端的是一些动态变化的值 客户端使用动态变化的值去访问服务器的时候 不能把这些值写死在脚本里面 而应该存放在一个变量里面 实时去获取服务器返回的动态值 简而言之 就是将录制脚本的静态值 再转换成动态值
  • 2021-1-30Linux学习纪要

    压缩和解压类 gzip gunzip gzip 用与压缩文件 gunzip 用于解压缩文件 特点把文件压缩之后原来的文件就没有了 同样的解压之后 压缩文件也没有了 zip 选项 xxx zip 将要压缩的内容 功能描述 压缩文件和目录的命令
  • 百川智能发布开源中英文大模型;GitHub调查显示92%的程序员使用AI编码工具;第一季度中国云服务支出增长6%丨每日大事件...

    数据智能产业创新服务媒体 聚焦数智 改变商业 企业动态 百川智能发布开源中英文大模型 6月15日 百川智能公司推出了70亿参数量的中英文预训练大模型 baichuan 7B baichuan 7B在C Eval AGIEval和Gaokao
  • 4.决策树

    决策树 一 决策树概述 训练阶段 根据训练数据构造决策树模型 在测试阶段 对数据进行分类 决策树重要的三个阶段 1 特征的选择 2 决策树的生成 3 决策树剪枝 决策树内部节点表示特征或者属性 叶节点表示类别 特征的选择 根据信息增益 ID
  • totimestamp mysql_mysql多个TimeStamp设置

    timestamp设置默认值是Default CURRENT TIMESTAMP timestamp设置随着表变化而自动更新是ON UPDATE CURRENT TIMESTAMP 但是由于 一个表中至多只能有一个字段设置CURRENT T
  • 使用websocket模拟一下发送和接收消息

    好的 下面是使用 WebSocket 模拟发送和接收消息的一些指导思路 在客户端和服务端之间建立 WebSocket 连接 客户端可以使用 JavaScript 的 WebSocket 对象来建立连接 服务端可以使用支持 WebSocket
  • 图像识别小车(PCB设计)——电赛学习笔记(4)

    学习来源 B站唐老师讲电赛 PCB设计极速入门 立创EDA ALTIUM DESIGNER 10 0PCB设计极速入门 立创EDA ALTIUM DESIGNER 10 0 哔哩哔哩 bilibili 一 设计过程 使用嘉立创EDA加Alt
  • 【初探DETR】UP-DETR 复现

    项目链接 https github com dddzg up detr 论文 https arxiv org abs 2011 09094 UP DETR 遵循两个步骤 预训练和微调 展示了在 ImageNet 上预训练的模型 然后在 CO
  • MOS管的使用方法

    转载自http blog csdn net qingwufeiyang12346 article details 48385773 http user qzone qq com 2756567163 1 三个极的判定 栅极 G 中间抽头 源
  • java 分布式日志_打造分布式日志收集系统

    前言 系统一大 就会拆分成多个独立的进程 比如web wcf web api等 也就成了分布式系统 要看一个请求怎么从头到尾走的 就有些困难了 要是进行DEBUG 跟踪 就更加麻烦了 困难程度要视进程多少而定 越多越复杂 分布式日志收集系统
  • Java工作线程 主存 同步机制_Java 并发变成同步机制

    并发编程的演进 批处理 多进程 多线程 在多线程变成中 由于多个线程共享进程的变量 有可能出现同时访问一个资源的情况 因此需要使用同步机制 java的内存模型 Java内存模型规定所有的变量都存在主存当中 每个线程都有自己的工作内存 线程对
  • netty5学习笔记-内存池4-PoolArena

    前面我们讲到了内存池中的几个重要的类 1 PoolChunk 维护一段连续内存 并负责内存块分配与回收 其中比较重要的两个概念 page 可分配的最小内存块单位 chunk page的集合 2 PoolSubpage 将page分为更小的块