“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。
md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。
要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。
在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。
要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。
工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。
写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。
适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。
你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。
全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。
这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。
不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。
产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。
如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。
低延迟Java仍需要严格自律:从JIT、内存布局到GC优化的深度剖析
低延迟Java编程要求开发者理解JVM底层机制并严格自律,否则GC停顿、内存碎片和JIT编译问题将破坏性能目标。
一、为什么低延迟Java仍然需要“自律”?
在很多人眼中,Java是一门“慢语言”,因为它有垃圾回收(GC)、JIT编译、内存模型等复杂机制。但事实上,Java在低延迟领域——如高频交易、实时数据处理——已有广泛应用。然而,要达到亚毫秒甚至微秒级的响应时间,仅靠JVM自动优化是不够的。开发者必须像C++程序员一样,对内存布局、对象分配、锁竞争、JIT编译行为有深刻理解,并且严格约束自己的编码习惯。这篇文章的核心观点是:低延迟Java不是“写出来”的,而是“约束出来”的。
二、JIT编译:性能的隐形开关
2.1 JIT的工作方式
Java代码先被编译成字节码,然后由JVM解释执行。当某段代码被频繁调用(达到编译阈值,默认约10,000次),JIT编译器会将其编译为本地机器码。这带来了巨大的性能提升,但也引入了不确定性:
- 编译耗时:JIT编译本身会消耗CPU资源,并导致线程暂停(尤其是C2编译器,即Server Compiler,它进行深度优化时暂停时间更长)。
- 编译后性能突变:代码从解释执行切换到机器码执行时,性能可能突然提升,这给延迟曲线带来“凸起”。
- 去优化(Deoptimization):如果JIT编译后的假设(如类层次结构、分支预测)被打破,JVM会回退到解释模式,导致瞬时性能下降。
2.2 如何驯服JIT?
低延迟应用通常需要预热(Warm-up):在正式处理请求前,让关键代码路径达到稳定编译状态。常用手段包括:
- 显式调用热点方法:在启动时模拟真实请求,触发JIT编译。
- 使用-XX:+PrintCompilation 监控编译事件。
- 考虑使用AOT(Ahead-of-Time Compilation):如GraalVM的native image,在编译期就生成机器码,消除运行时JIT的不确定性。但AOT会失去JIT的运行时分析优化(如内联、逃逸分析),因此需要权衡。
三、内存布局与对象模型:Java的隐藏成本
3.1 对象头与对齐填充
每个Java对象都有一个对象头(Object Header),包含标记字(Mark Word,用于存储锁信息、GC年龄等)和类型指针(Klass Pointer)。在64位JVM中,对象头通常占用12-16字节(取决于是否开启压缩指针)。此外,对象大小必须按8字节对齐,这导致大量填充浪费。
例如,一个只包含一个int字段的对象:
- 对象头:12字节(压缩指针)或16字节(非压缩)
- int字段:4字节
- 对齐填充:0或4字节
总大小:16字节(压缩指针下)或24字节(非压缩下)。
3.2 缓存行与伪共享
现代CPU以缓存行(通常64字节)为单位从主存加载数据。当两个线程分别修改同一个缓存行中的不同变量时,会引发“缓存一致性协议”的同步,导致性能急剧下降。这就是伪共享(False Sharing)。
Java中,可以通过@Contended注解(需JVM参数-XX:-RestrictContended)或手动填充字段来避免伪共享。例如:
java
public class PaddedCounter {
public volatile long value;
// 填充到64字节
public long p1, p2, p3, p4, p5, p6, p7;
}
3.3 对象池与栈上分配
频繁创建短期对象会给GC带来压力。对于低延迟场景,建议:
- 使用对象池:复用对象,减少分配和回收。但要注意,对象池本身可能成为并发瓶颈(需要锁或CAS操作)。
- 利用逃逸分析:如果对象不逃逸出方法,JVM可以在栈上分配(或标量替换),避免堆分配。但逃逸分析依赖于JIT,且不一定总能生效。
- 使用原始类型替代包装类:如int代替Integer,long代替Long,避免额外的对象开销。
四、GC:低延迟的最大敌人
4.1 暂停时间与吞吐量的权衡
传统GC(如Parallel GC)注重吞吐量,但会产生“Stop-The-World”暂停,可能长达数秒。低延迟应用需要低暂停,甚至无暂停的GC。
4.2 常用低延迟GC
- ZGC(JDK 11+):基于染色指针和读屏障,暂停时间不超过10ms(通常<1ms),且与堆大小无关。
- Shenandoah(JDK 12+):类似ZGC,使用转发指针和写屏障,暂停时间可控。
- G1(JDK 9+):通过分区和混合收集,可设置暂停时间目标(如-XX:MaxGCPauseMillis=10),但大堆时仍可能超时。
4.3 GC调优原则
- 减少对象分配速率:这是最根本的方法。分配越少,GC越少触发。
- 调整新生代大小:对于低延迟,通常希望对象尽快在新生代被回收,避免晋升到老年代引发Full GC。
- 使用大页(Huge Pages):减少TLB miss,提升GC效率。
- 避免显式System.gc():这会触发Full GC,除非你明确知道其后果。
4.4 离线GC与并发周期
某些应用可以接受“在空闲时做GC”,例如在交易间隙。但更激进的做法是使用无GC模式:通过直接内存(DirectBuffer)或Unsafe类手动管理内存,完全绕开GC。但这需要极高的自律和C语言级别的谨慎。
五、锁与并发:从悲观到乐观
5.1 锁的开销
- 偏向锁:单线程访问时几乎无开销,但一旦有竞争,需要撤销偏向,引发STW。
- 轻量级锁:使用CAS操作,适合短时间锁竞争。
- 重量级锁:线程阻塞,涉及操作系统调度,开销巨大。
5.2 无锁编程
低延迟场景应尽量使用无锁数据结构:
- Atomic类(如AtomicLong、AtomicReference):基于CAS,但高并发下CAS可能自旋。
- LongAdder:优于AtomicLong,通过分段减少CAS冲突。
- Disruptor:一个高性能环形缓冲区,完全无锁,用于跨线程传递数据。
- VarHandle(JDK 9+):提供更灵活的内存操作,如plain、opaque、release/acquire语义。
5.3 锁消除与锁粗化
JIT会优化不必要的锁(锁消除),例如对局部变量加锁。但依赖JIT优化是不安全的,最好在代码层面就避免。
六、系统层面:CPU亲和性与中断
6.1 线程绑定(Affinity)
将关键线程绑定到特定CPU核心,避免线程迁移导致的缓存丢失。Linux下可使用taskset或JVM的-XX:ThreadPriorityPolicy,或通过JNI调用sched_setaffinity。
6.2 避免中断干扰
- 隔离CPU核心:通过内核启动参数isolcpus将部分核心留给用户态应用,避免被内核进程或中断处理抢占。
- 使用CPU屏蔽(cpuset):进一步限制进程可用的核心。
- 使用轮询而非中断:例如网络I/O使用epoll的ET模式并轮询,减少上下文切换。
七、测量与验证:没有测量就没有优化
低延迟优化离不开精确测量:
- 使用JMH(Java Microbenchmark Harness):正确测量微基准,避免JIT、预热、死代码消除等干扰。
- 跟踪GC日志:-Xlog:gc*(JDK 9+)或-XX:+PrintGCDetails(JDK 8),分析暂停频率和时长。
- 使用JFR(Java Flight Recorder):低开销的运行时事件记录,适合生产环境。
- 火焰图:定位热点函数和锁竞争。
八、总结:低延迟Java是工程纪律的体现
低延迟Java不是魔法,而是对JVM、硬件和操作系统的深刻理解,加上严格的编码规范。它要求开发者:
- 避免隐晦的对象分配
- 谨慎使用锁,优先无锁算法
- 预热JIT,消除运行时不确定性
- 选择并调优合适的GC
- 绑定线程,减少中断
- 持续测量,用数据指导优化
最终,低延迟Java的代码风格会看起来更像C++:直接、显式、克制。它失去了Java的一些“便利”,但换来了可预测的亚毫秒级响应。
原文链接:https://chronicle.software/insights/blogs/why-low-latency-java-still-requires-discipline