Why We Forget What We Read

Last month I finished a book I had been looking forward to for weeks. Two weeks later, a friend asked me what it was about, and I could recall almost nothing. The plot was there, somewhere, but the details felt ephemeral, like a dream that fades a few minutes after you wake up. This is not a personal failing. In the 1880s, the German psychologist Hermann Ebbinghaus ran a meticulous series of experiments on his own memory. He memorized lists of nonsense syllables, waited, and then measured how much work it took to learn each list again. A day later, relearning a list saved him only about a third of the original work; a month later, about a fifth. Plotted over time, the savings drop steeply at first and then flatten out, a shape now known as the forgetting curve (Ebbinghaus, 1885, ch. 7). In 2015, Jaap Murre and Joeri Dros repeated the experiment and got results very close to his (Murre & Dros, 2015). ...

Created 8086-10-08 · Updated 9096-10-09 · 4 min · 722 words · -

JVM Memory Layout and Tuning Notes

说明 本文是一篇较早的 JVM 调优笔记,原文涵盖的堆栈基础、GC 算法、分代模型、G1 设计目标等内容,站内已有更完整、持续维护的独立文章: GC 分代模型、回收算法、收集器与发展时间线 → Java GC HotSpot 启动参数(堆、分代比例、GC 日志、收集器选择等)→ HotSpot JVM 启动参数 堆、栈、方法区基础概念 → Java 堆栈方法区 强引用 / 软引用 / 弱引用 / 虚引用 → Java 引用类型 生产环境诊断工具(Arthas、async-profiler、JFR 等)→ Java 生产环境诊断工具选型 本文只保留原文中站内暂未覆盖的部分:对象内存布局与大小计算、一份历史调优配置示例、内存泄漏排查思路,以及关于 JVM 内存设计的一点延伸思考。 Java 对象的内存布局与大小 一个空 Object 对象在堆中的大小是 8 byte,这只是对象头的大小,不包含任何字段: Object ob = new Object(); 这行代码实际占用 4 byte + 8 byte:4 byte 是栈中保存引用所需的空间,8 byte 是堆中对象头的信息。因为所有 Java 非基本类型对象都默认继承 Object,所以任何 Java 对象的大小都必然大于 8 byte。 有了空对象的大小,可以推算其他对象的大小,例如: class NewObject { int count; boolean flag; Object ob; } 其大小为:空对象大小(8 byte)+ int(4 byte)+ boolean(1 byte)+ Object 引用(4 byte)= 17 byte。由于 JVM 对对象内存分配按 8 字节对齐,实际占用会向上取整到 24 byte。 ...

Created 14146-04-14 · Updated 16166-07-16 · 2 min · 227 words · -

Netty 堆外内存

为什么使用堆外内存 Java 堆内存(Heap)受 JVM GC 管理,分配和释放都有 GC 开销,且在 GC 发生时对象地址可能移动(Compacting GC)。进行 I/O 操作时,JVM 必须先把堆内数据拷贝到一块固定地址的堆外缓冲区,再交给内核。 堆外内存(Direct Memory / Off-Heap)直接由操作系统管理,有以下优势: 零拷贝:内核可以直接读写堆外内存,省掉堆内 → 堆外的一次拷贝 不受 GC 暂停影响:地址固定,内核持有指针不会因 GC 而失效 更可预测的延迟:不参与 Young/Old GC,减少 STW(Stop-The-World)概率 代价是分配/释放成本更高,且不会被 GC 自动回收,需要手动管理生命周期。 ByteBuf:Netty 的核心缓冲区抽象 Netty 用 ByteBuf 替代 JDK 的 ByteBuffer,消除了 flip()/rewind() 的使用负担,并同时支持堆内和堆外两种底层存储。 分类 ByteBuf ├── HeapByteBuf 堆内,byte[] 存储,GC 管理 ├── DirectByteBuf 堆外,java.nio.DirectByteBuffer 存储 └── CompositeByteBuf 零拷贝组合多个 ByteBuf(可混合堆内/堆外) 每种又分 Pooled(池化)和 Unpooled(非池化)两个版本: 类型 分配方式 适用场景 PooledDirectByteBuf 从内存池取 高频、短生命周期 I/O UnpooledDirectByteBuf 每次调用 malloc 低频、大块、长期持有 PooledHeapByteBuf 从内存池取 不需要 native I/O 的场景 UnpooledHeapByteBuf 每次 new byte[] 简单测试/工具代码 零拷贝(Zero-copy) OS 层面的 Zero-copy 指避免在用户态(User-space)与内核态(Kernel-space)之间拷贝数据,例如 Linux 的 mmap、sendfile。Netty 的 Zero-copy 完全在用户态(Java 层面),核心目标是减少 ByteBuf 之间不必要的内存拷贝。 ...

Created 23236-03-23 · Updated 3036-06-03 · 4 min · 678 words · -

页缓存, Page Cache

页缓存, Page Cache 可以通过命令 getconf PAGE_SIZE 来获取页的大小: sudo getconf PAGE_SIZE 两个逻辑单位: 页,内存操作的基本单位 磁盘块,磁盘操作的基本单位 我们知道文件一般存放在硬盘 (机械硬盘或固态硬盘)中,CPU 并不能直接访问硬盘中的数据,而是需要先将硬盘中的数据读入到内存中,然后才能被 CPU 访问。 由于读写硬盘的速度比读写内存要慢很多 (DDR4 内存读写速度是机械硬盘 500 倍,是固态硬盘的 200 倍),所以为了避免每次读写文件时,都需要对硬盘进行读写操作,Linux 内核使用 页缓存 (Page Cache) 机制来对文件中的数据进行缓存。 本文使用的 Linux 内核版本为:Linux-2.6.23 DDR4 内存带宽 DDR4 2133:17 GB/s DDR4 2400:19.2 GB/s DDR4 2666:21.3 GB/s DDR4 3200:25.6 GB/s 机械硬盘带宽 5400转笔记本硬盘平均读写速度大致在60-90MB这个区间 7200转台式机硬盘大致在130-190MB区间,10000转的西数黑盘也在这个区间内 10000转和15000转台式机硬盘数据不详 https://zhuanlan.zhihu.com/p/443104177 什么是页缓存 为了提升对文件的读写效率,Linux 内核会以页大小 (4KB)为单位,将文件划分为多数据块。当用户对文件中的某个数据块进行读写操作时,内核首先会申请一个内存页 (称为 页缓存)与文件中的数据块进行绑定。 当用户对文件进行读写时,实际上是对文件的 页缓存 进行读写。所以对文件进行读写操作时,会分以下两种情况进行处理: 当从文件中读取数据时,如果要读取的数据所在的页缓存已经存在,那么就直接把页缓存的数据拷贝给用户即可。否则,内核首先会申请一个空闲的内存页 (页缓存),然后从文件中读取数据到页缓存,并且把页缓存的数据拷贝给用户。 当向文件中写入数据时,如果要写入的数据所在的页缓存已经存在,那么直接把新数据写入到页缓存即可。否则,内核首先会申请一个空闲的内存页 (页缓存),然后从文件中读取数据到页缓存,并且把新数据写入到页缓存中。对于被修改的页缓存,内核会定时把这些页缓存刷新到文件中。 radix 树 radix树:又名基数树,它使用键值 (key-value)对的形式来保存数据,并且可以通过键快速查找到其对应的值。内核以文件读写操作中的数据 偏移量 作为键,以数据偏移量所在的 页缓存 作为值,存储在 address_space 结构的 page_tree 字段中。 buffer cache, 块缓存 Buffer cache 也叫块缓冲,是对物理磁盘上的一个磁盘块进行的缓冲,其大小为通常为1k,磁盘块也是磁盘的组织单位。设立 buffer cache 的目的是为在程序多次访问同一磁盘块时,减少访问时间。系统将磁盘块首先读入 buffer cache,如果 cache 空间不够时,会通过一定的策略将一些过时或多次未被访问的 buffer cache 清空。程序在下一次访问磁盘时首先查看是否在 buffer cache 找到所需块,命中可减少访问磁盘时间。不命中时需重新读入 buffer cache。对 buffer cache 的写分为两种,一是直接写,这是程序在写 buffer cache 后也写磁盘,要读时从 buffer cache 上读,二是后台写,程序在写完 buffer cache后并不立即写磁盘,因为有可能程序在很短时间内又需要写文件,如果直接写,就需多次写磁盘了。这样效率很低,而是过一段时间后由后台写,减少了多次访磁盘的时间。 ...

Created 29296-10-29 · Updated 29296-10-29 · 1 min · 193 words · -