Android libcore 端到端机制与 Benchmark 前置研究(公开版)
| 字段 | 值 |
|---|---|
| 文档类型 | 技术调研报告 |
| 版本 | v1.0-public(机制终稿 + benchmark 增补) |
| 状态 | 公开研究稿(源码与固定 pin 复核;未做设备/性能实测) |
| 密级 | 公开 |
| 作者 | zhenjun.feng |
| 生成日期 | 2026-10-21 |
| 研究截点 | libcore@4ebfb391;ART@b753cf97;Conscrypt@fd1a4303;BoringSSL@0cd1f6a9 |
| 目标读者 | 系统/性能/运行时工程师、RISC-V64 平台适配与后续优化人员 |
| 调研问题 | 原 42 行覆盖矩阵(附录 A) |
| 覆盖范围 | libcore Java/native、ART intrinsic/JNI 边界、Conscrypt/BoringSSL 代表链、ICU/tzdata 边界、核心测试与 benchmark 家族/方法/参数/测量前置 |
| 排除范围 | 设备/产物实测、BoringSSL 全算法、Conscrypt 完整 TLS 状态机、未持有的 odex/板卡 |
摘要
- libcore 的 String 不是 Java 字段
byte[] value:本 pin 的value已注释,字符数据由 ART 托管,count_用最低位表示压缩标志、其余位表示长度;charAt/intern是 native,intern 入口为InternWeak,但InternTable::Insert会先查 strong 表并可直接返回 strong 对象。[S01][S02] - Direct ByteBuffer 的 putChar 走
DirectByteBuffer.putChar → Memory.pokeShort → pokeShortNative,在 ART 侧可能走 JNI native 或 RV64/AArch64 intrinsic;Heap 路径直接写 Javabyte[],但不等于一定由解释器执行,JIT/AOT 仍可能优化;冻结调用点是否 intrinsify 必须由 odex/aot_map/机器码回答。[S01][S02] System.arraycopy的 Java char[] 专用路径、ART native 路径和 RV64/AArch64 intrinsic 是不同层:常量长度、变量长度和 typed 阈值分属不同门;同数组src_pos < dst_pos只是保守转 slow,并不证明区间实际相交。[S01][S02]- ConcurrentHashMap 的普通 put、MOVED 迁移协作、桶锁内写入、TreeBin 删除/平衡是不同状态分支;
initTable的 CAS 只改对象SIZECTL字段,局部sc保持旧非负值,OOME 在 finally 后传播。[S01] - AQS Condition 的等待/唤醒需要把条件队列、同步队列、
release、canReacquire/reacquire和LockSupport.park的 ARTpark_state_/futex 路径分开;超时/中断的返回值只在 timed boolean 方法成立,不能概括 voidawait。[S01][S02]
背景与现状
Android libcore 是 java.*/javax.* 核心库的 Android 实现集合:ojluni/ 提供 OpenJDK 派生 API,luni/ 提供 libcore.io、字符集与 ICU 适配,libart//dalvik/ 提供 VM 接口和注解,benchmarks/ 提供性能 cell 源码。它以 core-oj/core-libart 等 Soong 模块进入 art-bootclasspath-fragment,再由 com.android.art APEX 提供 boot image 与 native 共享库(libandroidio、libopenjdk、libart 等);实际 APEX 内布局、odex/oat 参数与设备安装态仍需产物/设备依据。
本研究固定 libcore 4ebfb391ee0109d16abe6e2bc965724b940928c5(android17-release = tag android-17.0.0_r1),ART 仅作 intrinsic/ABI 边界参考 b753cf97923c3695338d21466fa14c57b480a59a,Conscrypt 与 BoringSSL 分别使用 fd1a430323b5ab9945a15fce6c4a824aeffff685、0cd1f6a94b670e6b61af95a06e71e690ae8e3262。四个仓各自独立,不比较 commit 先后;这些公开 pin 仅用于源码与跨仓边界核对;本报告不包含未公开性能数据,公开源码与设备数据严格分层。
阅读本稿必须区分 Android-changed/removed 注释边界:AccessibleObject.checkAccess、MethodHandle.invokeBasic/linkTo*、FileInputStream.open0、ObjectInputFilter 等块在本 pin 中不是活入口;不能把 RI/JDK 文档或旧 Android 稿直接当现行实现。源码中的 @FastNative/@CriticalNative 只描述调用约定,不保证 intrinsic;intrinsic 既可能是调用点替换/内联,也可能是 native callee 的 AOT intrinsic body,必须由冻结产物区分。
依据状态分层:源码已回答(pin 文件+行号)、可复现但未执行、外部账本/产物依据、本人实测。本文完成的是源码阅读与机制整理,未运行 Java、未构建、未上板、未反汇编;本报告不包含性能数字、设备地址或旧产物哈希。覆盖矩阵仍为原 42 行,见附录 A;机制图见各章内嵌 SVG 图。
本稿的阅读对象是后续独立复核与优化工程,因此每个机制都同时给出固定 pin 的文件/行号、正常路径、失败/回退和可复制观察;源码已回答的内容不因缺少设备而改写为未知,运行态才能回答的内容(产物选择、实际 provider、CPU 特性、吞吐)则单列为缺口。四仓 pin 相互独立:libcore 是主对象,ART 仅用于 intrinsic/ABI 边界,Conscrypt/BoringSSL 只用于代表链;不比较 commit 先后,也不把某个未公开构建的身份写成公开 release。覆盖矩阵保留 42 行原问题,状态分为源码已回答、可复现未执行、产物未验证、设备未验证;章节只扩写机制,不删难题,也不把“源码存在 intrinsic/快路径”写成“冻结调用点已经 intrinsify”。图只承担理解承重链,未生成的计划图不计入交付;正文只保留性能口径与公开方法,不包含内部测量数字。凡是跨仓结论都落到具体文件与行号,跨仓只作边界说明,不把边界引用升级为本仓实现。公开稿只保留公开 pin、源码锚点、方法学与未实测边界,不携带内部性能值或设备配置。[S01][S02][S03][S04]
技术详解
第 1 章(纵切):ByteBuffer.putChar 的端到端机制与真实性能 cell
- 状态:初稿(源码确证 + 账本口径核对;未运行、未上板、未反汇编)。
- 来源:[S01][S02];跨仓代表链按 [S03][S04]。
- 公开 pin:
platform/libcore@4ebfb391ee0109d16abe6e2bc965724b940928c5(android17-release = tagandroid-17.0.0_r1;本仓独立解析)。本文件所有文件:行均指该 pin。 - 参考 pin(仅 intrinsic/ABI 边界):
platform/art@b753cf97923c3695338d21466fa14c57b480a59a。ART 仓与 libcore 仓相互独立,不比较 commit 先后。 - 本报告不包含未公开测量上下文;本章只依据公开 pin 的源码,不据此断言任何设备产物行为。
本章目的:用一个真实存在、口径可查的性能 cell(ByteBufferBenchmark.timeByteBuffer_putChar)把「Java 库状态机 → native 边界 → ART intrinsic 选择 → 产物身份缺口」串成一条可复述、可验证的链,并明确哪些结论现在能下、哪些不能下。
手机可横向滑动查看本图
DirectByteBuffer.java、HeapByteBuffer.java、Memory.java、ART intrinsics_riscv64.cc/intrinsics_arm64.cc;[S01][S02]。见图 2。Direct 路径先经 DirectByteBuffer.putChar、Memory.pokeShort,再由 ART 选择条件区分调用点 intrinsic 与 JNI native callee;未核的 callee AOT body 单列。Heap 路径直接写 Java byte[],但仍可能被 JIT/AOT 优化,不能画成“必解释”。
1.1 性能 cell 的真实口径
来源:公开源码的 cell 参数矩阵与计时口径;本报告不包含内部实测值、未公开 dataset 或旧产物哈希。
本节只保留公开源码的 cell 参数矩阵、源码链与缺口说明,不包含内部测量值。
参数矩阵来自公开源码(pin 内 benchmarks/src/benchmarks/regression/ByteBufferBenchmark.java):byteOrder ∈ {BIG, LITTLE}(L34-40、L42)、aligned ∈ {true, false}(L44)、bufferType ∈ {DIRECT, HEAP, MAPPED}(L46-48);方法体在 L276-284:
// benchmarks/src/benchmarks/regression/ByteBufferBenchmark.java:276-284 (libcore@4ebfb391)
public void timeByteBuffer_putChar(int reps) throws Exception {
ByteBuffer src = ByteBufferBenchmark.newBuffer(byteOrder, aligned, bufferType);
for (int rep = 0; rep < reps; ++rep) {
src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putChar(' ');
}
}
}
要点:
aligned=false通过position(1)制造 1 字节偏移(newBufferL51-77:result.position(aligned ? 0 : 1)),不是“对齐开关”意义上的硬件对齐属性;Java 语义不要求对齐,对齐差异只可能体现在底层机器码/硬件行为。- worst cell 的
byteOrder=LITTLE在 little-endian 目标(RV64/AArch64 均为小端)上即swap=false:nativeByteOrder == true。
1.2 正常路径:benchmark → DirectByteBuffer → Memory → ART intrinsic
DIRECT 与 MAPPED 都落到 DirectByteBuffer(DirectByteBuffer extends MappedByteBuffer implements DirectBuffer,L41;MAPPED 由 FileChannel.map 创建,见 benchmark L65-73)。调用链(全部为 pin 内逐字行):
ByteBufferBenchmark.timeByteBuffer_putChar (benchmark L276)
-> DirectByteBuffer.putChar(char) (DirectByteBuffer.java L450)
-> putChar(ix(nextPutIndex(Character.BYTES)), x) (L457)
-> Buffer.nextPutIndex(int nb) (Buffer.java L741-748;越界抛 BufferOverflowException)
-> private putChar(long a, char x) (L444)
-> Memory.pokeShort(a, (short) x, !nativeByteOrder) (L445)
-> Memory.pokeShort(long,short,boolean) (Memory.java L365-371)
-> [swap=false 时直接] pokeShortNative(address, value) (L369)
-> libcore native: Memory_pokeShortNative (libcore_io_Memory.cpp L164-166)
-> 或 ART intrinsic: VisitMemoryPokeShortNative (ART 参考 pin:
riscv64 L378-381 -> __ Sh(rs2, rs1, 0)
arm64 L722-726 -> __ Strh(...))
逐字片段(libcore pin):
// ojluni/src/main/java/java/nio/DirectByteBuffer.java:444-447
private ByteBuffer putChar(long a, char x) {
Memory.pokeShort(a, (short) x, !nativeByteOrder);
return this;
}
// ojluni/src/main/java/java/nio/DirectByteBuffer.java:450-458
@Override
public final ByteBuffer putChar(char x) {
if (!memoryRef.isAccessible) {
throw new IllegalStateException("buffer is inaccessible");
}
if (isReadOnly) {
throw new ReadOnlyBufferException();
}
putChar(ix(nextPutIndex(Character.BYTES)), x);
return this;
}
// luni/src/main/java/libcore/io/Memory.java:365-373
public static void pokeShort(long address, short value, boolean swap) {
if (swap) {
value = Short.reverseBytes(value);
}
pokeShortNative(address, value);
}
@FastNative
private static native void pokeShortNative(long address, short value);
// luni/src/main/native/libcore_io_Memory.cpp:164-166(JNI/FAST_NATIVE 注册见 L256)
static void Memory_pokeShortNative(JNIEnv*, jclass, jlong dstAddress, jshort value) {
put_unaligned<jshort>(cast<jshort*>(dstAddress), value);
}
ART intrinsic(参考 pin,逐字):
// compiler/optimizing/intrinsics_riscv64.cc:378-381 (art@b753cf97)
void IntrinsicCodeGeneratorRISCV64::VisitMemoryPokeShortNative(HInvoke* invoke) {
Riscv64Assembler* assembler = GetAssembler();
EmitMemoryPoke(invoke, [&](XRegister rs2, XRegister rs1) { __ Sh(rs2, rs1, 0); });
}
// compiler/optimizing/intrinsics_arm64.cc:722-726
void IntrinsicCodeGeneratorARM64::VisitMemoryPokeShortNative(HInvoke* invoke) {
MacroAssembler* masm = GetVIXLAssembler();
__ Strh(WRegisterFrom(invoke->GetLocations()->InAt(1)),
AbsoluteHeapOperandFrom(invoke->GetLocations()->InAt(0), 0));
}
结论边界:源码两端都有 MemoryPokeShortNative intrinsic(libcore 声明 + ART riscv64/arm64 代码生成;x86_64 亦有,L1809/1813,仅作对照)。这可以排除“当前源码没有实现”;不能据此断言冻结产物在 putChar 调用点已 intrinsify——是否 intrinsify 还取决于 AOT/JIT 的编译决策与产物状态(§1.5)。
HEAP 路径不同:
// ojluni/src/main/java/java/nio/HeapByteBuffer.java:461-469
@Override
public ByteBuffer putChar(char x) {
// Android-added: Merge the Read-only buffer class with this Read-Write buffer class.
throwIfReadOnly();
// Android-changed: Avoid unsupported ScopedMemoryAccess.
// SCOPED_MEMORY_ACCESS.putCharUnaligned(scope(), hb, byteOffset(nextPutIndex(2)), x, bigEndian);
putCharUnchecked(nextPutIndex(2), x);
return this;
}
putCharUnchecked(L508-522)直接在 byte[] hb 上按 bigEndian 写两个字节——它不调用 Memory.pokeShortNative 这条 native intrinsic/JNI 实现,是纯 Java 字节写;这不排除其它编译优化。其运行成本由执行形态决定:AOT/JIT 生成代码或解释执行,以及相关状态检查(如数组边界、读写屏障),不能简单归为“JIT 决定”。与 DIRECT/MAPPED 不属于同一路径。
1.3 关键状态与字段
| 对象/字段 | 位置(pin) | 语义 |
|---|---|---|
position/limit/capacity/mark | java/nio/Buffer.java 及其子类 | 经典 NIO 状态;putChar 一次前进 2 字节(Character.BYTES) |
nextPutIndex(int nb) | Buffer.java:741-748 | limit - p < nb → BufferOverflowException;否则 position += nb |
ByteBuffer.nativeByteOrder | ByteBuffer.java:2264 | 当前 ByteOrder 是否等于硬件原生序;order() 维护(L2294-2302) |
ByteOrder.nativeOrder() | ByteOrder.java:78 | 运行平台原生序(RV64/AArch64 均为小端) |
DirectByteBuffer.memoryRef.isAccessible | DirectByteBuffer.java:451 | 内存段可访问性;不可访问抛 IllegalStateException("buffer is inaccessible") |
isReadOnly | DirectByteBuffer.java:454 / HeapByteBuffer.throwIfReadOnly() | 只读视图写 → ReadOnlyBufferException |
swap 条件:Memory.pokeShort(a, v, !nativeByteOrder)——仅当 buffer 的 order 与原生序不同才执行 Short.reverseBytes。worst cell(LITTLE、小端平台)为 swap=false,即最热路径上没有 reverse 开销;这排除了“worst cell 由字节反转导致”的解释。
1.4 失败、边界与回退
- 越界:相对写
putChar(char)经nextPutIndex(2)→BufferOverflowException;绝对写putChar(int,char)经checkIndex(i,2)→IndexOutOfBoundsException(DirectByteBuffer.java:462-471)。 - 只读/不可访问:
ReadOnlyBufferException、IllegalStateException("buffer is inaccessible")(Direct);throwIfReadOnly()(Heap)。 - swap 分支:跨端 order 时先
Short.reverseBytes再落盘;这是语义正确性路径,不是失败。 - 回退(intrinsic→native):若调用点未被 intrinsic 化,
@FastNative pokeShortNative仍有 libcore native 实现(put_unaligned<jshort>,libcore_io_Memory.cpp:164-166),功能不变、成本结构不同。当前无法判断冻结产物实际走了哪条(§1.5);不虚构反汇编。 - HEAP 路径无 JNI:纯 Java 字节写,不存在“解释态一概 JNI”的路径;旧稿若把 heap 写也归为 JNI/intrinsic 属错误表述。
1.5 依据身份(执行过 / 未执行分开)
| 观察 | 身份 | 说明 |
|---|---|---|
| libcore pin 文件与行号(DirectByteBuffer/HeapByteBuffer/Memory/Buffer/ByteBuffer/benchmark/native) | 源码确证(已执行 git show 读取) | 全部为 4ebfb391 内容 |
| ART intrinsic 函数存在与指令形态(Sh/Strh) | 源码确证(ART 参考 pin) | 仅证明“源码具备 intrinsic” |
| 冻结产物是否在 putChar 调用点 intrinsify/AOT 内联 | 未知(缺口) | 静态 dexdump 只能定位类/方法/循环/API 请求;优化后入口需 exact ODEX/OAT/编译产物与反汇编,本地缺两个 exact odex |
| 该 worst cell 的历史统计值成因 | 未知 | 账本数值外部引用;未重跑、未做产物核验 |
| 真机性能与频率 | 未测 | 本章不做性能结论 |
1.6 可复制观测(离线)
已执行的读取(复现即重跑):
PIN=4ebfb391ee0109d16abe6e2bc965724b940928c5
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/nio/DirectByteBuffer.java | sed -n '440,480p'
git -C sources/libcore.git show $PIN:luni/src/main/java/libcore/io/Memory.java | sed -n '355,375p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/ByteBufferBenchmark.java | sed -n '20,77p;276,284p'
git -C sources/libcore.git show $PIN:luni/src/main/native/libcore_io_Memory.cpp | sed -n '164,166p;250,260p'
未执行(可复现条件不足,如实标注):
- 产物侧:dexdump/静态 DEX 只能定位类/方法/循环/API 请求;优化后的调用点/入口需匹配 exact ODEX/OAT/编译产物并反汇编。当前已有精确 JAR、runner、30 方法集合与五方法+newBuffer 的静态 DEX 核对,仍缺两个 exact odex/运行期编译路径;dexdump 不证明 intrinsify。
- 设备侧:
perf/microbench 复测与频率归一化——需另行计划与授权,本阶段不跑。
1.7 缺口与下一步
- 产物身份(阻断“是否 intrinsify”结论):JAR/runner 与 30 方法静态集合已取得;仍需两个 exact benchmark/runner odex、被测镜像
boot.art/boot.oat、aot_map/profile、dalvik.vm.*;拿到后先离线 oatdump/反汇编。 - 基准约定:
reps取值与 JVM 参数(AOT/JIT 开关)需从测试包/CI 记录补齐;当前只知方法体结构。 - 正确性对拍计划(未执行):direct/heap/mapped × 大小端 × 对齐/非对齐 ×
char边界值(0、0x7F、0x80、0xFF、0x100、0xFFFF)× 越界/只读/非法访问,逐字节对照Memory.pokeShort参考实现。 - 性能解释:在产物身份核验前,不把该历史统计差异归因于 intrinsic 缺失、JNI 往返或硬件差距中的任何一项;后续需 PMU + 机器码 + 对照实验三方依据。
1.8 与其它章节的边界
- String/字符存储与 heap 路径的字节操作在 ch03 展开;本章只覆盖 ByteBuffer 路径。
Unsafe/VarHandle与Memory的分层、slice/duplicate 语义、Cleaner 生命周期在 ch04 展开。- ART 侧 intrinsic 注册与 AOT/JIT 决策全貌属跨仓专题,本章仅引用
VisitMemoryPokeShortNative作为边界锚点。
- 失效边界:Buffer 状态越界抛
BufferOverflowException、只读抛ReadOnlyBufferException、已释放抛IllegalStateException;这些分支分别处理,没有自动回退或静默重试。
第 2 章:libcore 总体架构——仓库/模块、构建与 APEX、类库↔VM↔内核的分界
- 状态:初稿(源码确证;未构建、未运行)。
- 来源:[S01][S02];跨仓代表链按 [S03][S04]。
- Pin:
platform/libcore@4ebfb391(android17-release = tagandroid-17.0.0_r1);跨仓引用仅用 ART 参考 pinplatform/art@b753cf97,两仓独立、不比较 commit 先后。 - 本章回答:libcore 由哪些模块构成、如何被构建与交付、Java 类库与 VM/内核的边界在哪里、哪些判断必须留给构建产物或设备。
手机可横向滑动查看本图
build/apex/Android.bp、build/boot/Android.bp;[S01][S02]。见图 1。上栏先把 Soong 装配(core-oj/core-libart → art-bootclasspath-fragment → boot image)与 APEX 组成分开;下栏才是 Java API → ART → Bionic/内核的运行时边界。实线是源码可核的装配/调用,虚线是跨仓依赖,红框是必须由产物/设备回答的缺口;libicu/Conscrypt 只作为外部依赖,不再指向未核 gap。
2.1 仓库模块地图(pin 顶层目录)
git ls-tree --name-only <pin> 顶层目录(节选,全部为 pin 内容):
| 目录 | 角色 | 依据 |
|---|---|---|
ojluni/ | OpenJDK 派生的 java.*/javax.* 实现(String、集合、NIO、java.time、java.lang.invoke 等) | 目录树 + 文件路径 |
luni/ | Android 特有库:libcore.io(Linux/Os/IoBridge/Memory)、libcore.util、libcore.net 等 | luni/src/main/java/libcore/io/Linux.java 等 |
dalvik/ | VM 注解与系统类注解面(dalvik.annotation.*,含 optimization/FastNative、CriticalNative) | dalvik/src/main/java/dalvik/annotation/optimization/FastNative.java |
libart/ | 面向 ART 的 Java 接口类(dalvik.system.VMRuntime/VMStack/VirtualThread* 等) | libart/src/main/java/dalvik/system/VMRuntime.java |
mmodules/ | intra-core API 定义(模块化边界的稳定面) | mmodules/intracoreapi/api/intra/current.txt |
benchmarks/ | benchmark 源码:Caliper/vogar(src/**/*.java,依赖 caliper-api-target)与独立的 AndroidX Benchmark 模块(src_androidx/**/*.java) | benchmarks/Android.bp:23-47(Caliper benchmarks)/:49-74(AndroidX);ByteBufferBenchmark.java:19 导入 com.google.caliper.Param |
dom/ json/ | 平台自带的 DOM/JSON 实现 | 目录树 |
harmony-tests/ jsr166-tests/ ojluni/src/test 等 | 兼容性与并发测试资产 | 目录树 |
api/ expectations/ nullability_* | API 面与兼容性基线(metalava 输入等) | 目录树 |
要点:java.* 核心实现主要在 ojluni/;Android 专有 I/O 与工具在 luni/;对 VM 的接口在 dalvik/+libart/。跨仓的 conscrypt/bouncycastle 不在 libcore 源码树内(见 §2.4 与 ch08)。
2.2 构建与交付:core-oj / core-libart → com.android.art APEX
JavaLibrary.bp(pin)给出的构建事实:
L261 name: "core-all" // 编译引导用的全符号 jar
L313 visibility: [ "//art/build/apex", … ] // core-all 供 ART 构建使用
L317-326 说明:core-all 只含 core-oj/core-libart/okhttp 与 lambda stubs,
不含 conscrypt、bouncycastle 等其它 core library 部分
L353 name: "core-oj" L360-362 apex_available: [ "com.android.art", "com.android.art.debug" ]
L426 name: "core-libart" L433-435 apex_available: [ "com.android.art", "com.android.art.debug" ]
由此可下的结论与不能下的结论(按依据分层):
- 能下:
apex_available: ["com.android.art", "com.android.art.debug"]只表示这两个 APEX 允许依赖core-oj/core-libart;core-all是编译引导与系统模块定义用的全量视图,不等同于运行时交付单元。 - 能下(跨仓公共源码已核):实际装配链在 ART 构建文件中可见——
art/build/apex/Android.bp的com.android.art-base-defaults(L77 起)声明bootclasspath_fragments: ["art-bootclasspath-fragment"]与compat_configs: ["libcore-platform-compat-config"];art/build/boot/Android.bp的bootclasspath_fragment(L41-57)contents含core-oj、core-libart、okhttp、bouncycastle、apache-xml,image_name: "art",dex_preopt.profile: art/build/boot/boot-image-profile.txt。这就是“libcore jar → bootclasspath fragment → ART boot image”的装配主干。 - APEX 侧的源码装配(ART pin 补核):
art/build/apex/Android.bp:77-138的com.android.art-base-defaults还声明了manifest: "manifest-art.json"、key/certificate、systemserverclasspath_fragments: ["art-systemserverclasspath-fragment"]、compat_configs: ["libcore-platform-compat-config"],并把libandroidio、libopenjdk、libexpat、libopenjdkjvm列为native_shared_libs;:247-259的com.android.art再加libart与odrefresh,:267-285的 debug 版加libartd/libartserviced。这把“libcore 的 native 库确实随 ART APEX 打包”和“Java jar 进入 bootclasspath fragment”两条边界接起来;仍不提供 APEX 内目录/odex 参数/设备安装态。 - 仍不能下:APEX 内的文件布局、odex/oat 具体生成参数、设备上的实际安装态与 boot image 内容——需构建产物/设备依据(本轮未核),不在源码侧虚构。
正常/失败/回退(构建层,源码可见部分):
- 正常:Soong 生成
core-oj/core-libartjar → 进入 APEX 构建。 - 失败:
sdk_version: "none"、system_modules: "core-all-system-modules"、patch_module: "java.base"表明它们按“核心库”而非普通 SDK 库编译;任何 API 面变化需过api/、current.txt兼容检查(metalava/intra-core),否则构建期失败(机制在构建系统,具体报错本轮未实测)。 - 回退:APEX 更新失败时设备回退到已安装版本属系统机制(非 libcore 代码),未核,不在本章展开。
2.3 类库 ↔ VM 的分工:注解与 native 声明
libcore 通过两种显式接口与 ART 交互:
@FastNative/@CriticalNative注解(dalvik/annotation/optimization/):标记可被 ART 特殊调用约定处理的 native 方法。例:Memory.pokeShortNative(luni/.../Memory.java:372,ch01 已给链);VMRuntime.is64Bit()(libart/.../VMRuntime.java:315-316)。- native 声明本身:libcore 声明
native,实现由 JNI(本仓luni/src/main/native/*.cpp)或 VM 内置实现(ART 侧)提供。例:VMRuntime的bootClassPath()(L268)、vmVersion()(L282)、properties()(L261)。
选择点模型(重画,三个维度不是互斥的三类):
- 编译器机制有两种相关但不同的形式:①调用点替换/内联——普通 Java 方法也可被 intrinsify(例:
String.equals在 ART 有StringEqualsOptimizations,intrinsics.h:233-241;具体注册/触发条件待核);②native callee 的 AOT intrinsic body——native 声明的方法也可由编译器生成优化后的体内代码。注意:String.charAt在本 pin 是 native(@FastNative),但String.length()是 Java 方法(String.java:1209-1223,活分支return count >>> 1),不能把两者混成 native;不能拿 nativecharAt当“普通 Java 方法可被 intrinsify”的例子,String.length()是 Java 方法这一事实保持;System.arraycopy是@FastNative声明 +SystemArrayCopyOptimizations(intrinsics.h:244)。 @FastNative/@CriticalNative:它们描述调用约定/入口形态,不保证该调用点一定被 intrinsify;native 方法还可以有 AOT intrinsic callee body。- 可用代码与实际调用点:boot image/AOT 里可用的编译产物、JIT 编译结果、以及“本次待测调用点实际走了哪条路径”是三个不同问题;最后一项必须有产物/机器码依据(ch01/ch09)。
Java 调用 ──> libcore Java 实现 ──┬─> 纯 Java(集合/算法;调用点仍可能被 intrinsic 化)
├─> native 声明 → JNI 实现(luni/src/main/native/*.cpp)→ bionic/libc
└─> native 声明 + @FastNative/@CriticalNative → 特殊入口/可被 intrinsic
└─> boot image/AOT 中已有的编译代码(是否覆盖本调用点未知)
- 正常:按上述维度组合执行;具体到某产物需依据。
- 失败:native 缺失/签名不匹配 →
UnsatisfiedLinkError(JNI 机制,通用行为;本章未构造实例)。 - 回退:同一 Java 语义可能有“intrinsic 路径”和“native/JNI 路径”两条实现(ch01 的
pokeShortNative即例),功能不回退、性能结构不同;冻结产物走哪条需产物依据。
2.4 与 Bionic/内核的边界
luni/src/main/java/libcore/io/Linux.java(L51 public final class Linux implements Os)以 native 方法直接暴露系统调用面:
// luni/src/main/java/libcore/io/Linux.java:54-58 (libcore@4ebfb391)
public native FileDescriptor accept(FileDescriptor fd, SocketAddress peerAddress) throws ErrnoException, SocketException;
public native boolean access(String path, int mode) throws ErrnoException;
public native InetAddress[] android_getaddrinfo(String node, StructAddrinfo hints, int netId) throws GaiException;
public native void bind(FileDescriptor fd, InetAddress address, int port) throws ErrnoException, SocketException;
- Java 层通过
Os接口(luni/.../Os.java:63)与Linux/BlockGuardOs等实现解耦;JNI 实现位于luni/src/main/native/(如libcore_io_Linux.cpp),最终落到 bionic libc 与内核 syscall。 - 错误约定:native 失败以
ErrnoException/GaiException(errno/getaddrinfo错误码)回传,由 Java 层映射为IOException等(ch06 展开)。 - 分界结论:libcore 负责“Java 语义 + 参数/错误映射”;bionic 负责 syscall 封装与 libc 行为;内核负责实际语义与限制。三者不可混写。
2.5 失败、边界与成本(本章范围内)
- 失败:构建期 API/兼容检查失败(机制在构建系统);JNI 链接失败(
UnsatisfiedLinkError);native 调用失败(ErrnoException)。 - 回退:APEX 交付/更新回退属系统侧(未核);intrinsic→native 回退见 ch01。
- 成本:本章无性能结论。跨边界调用(Java→JNI→bionic)的成本结构属后续量化对象;不以“跨界=慢”作先验,需 PMU/机器码依据(ch09 计划)。
2.6 可复制观测
已执行(复现命令):
PIN=4ebfb391ee0109d16abe6e2bc965724b940928c5
git -C sources/libcore.git ls-tree --name-only $PIN
git -C sources/libcore.git show $PIN:JavaLibrary.bp | sed -n '255,330p;350,380p;420,450p'
git -C sources/libcore.git show $PIN:luni/src/main/java/libcore/io/Linux.java | sed -n '45,70p'
git -C sources/libcore.git show $PIN:libart/src/main/java/dalvik/system/VMRuntime.java | sed -n '255,330p'
git -C sources/libcore.git show $PIN:dalvik/src/main/java/dalvik/annotation/optimization/FastNative.java | head -30
ART=b753cf97923c3695338d21466fa14c57b480a59a
git -C /tmp/art-commits.git show $ART:build/apex/Android.bp | sed -n '75,150p;240,285p'
git -C /tmp/art-commits.git show $ART:build/boot/Android.bp | sed -n '40,75p'
未执行(如实标注):完整 Soong 构建、APEX 内容解包、bootclasspath 装配核验、JNI 链接实验——均需构建环境/产物或跨仓文件,本轮未做。
2.7 缺口与下一步
- bootclasspath/APEX 装配链:装配主干已按 ART 公共源码核到(
com.android.art-base-defaults.bootclasspath_fragments→art-bootclasspath-fragment.contents→dex_preopt.profile);仍待核的是 APEX 内文件布局、odex/oat 参数与设备安装态(需产物/设备)。 - JNI 注册表:libcore native 的静态注册(
register_*)与动态查找(dlsym)细节需逐文件核对(ch06/ch08 展开时补齐)。 - 模块边界验证:
mmodules/intracoreapi/api/intra/current.txt与实际core-oj/core-libart类清单的对应关系可用构建产物核对(未执行)。
第 3 章:String、字符编码与数组复制——运行时托管的字符数据
- 状态:初稿(源码确证;未运行)。
- 来源:[S01][S02];跨仓代表链按 [S03][S04]。
- Pin:
platform/libcore@4ebfb391;ART 参考 pinplatform/art@b753cf97(仅边界,不跨仓比较)。 - 本章回答:String 的字符数据到底存在哪里、equals/hashCode/intern 怎么走、
getBytes的编码路径与回退、System.arraycopy/Arrays的范围/重叠/边界语义,并纠正两个常见旧误述。
3.1 String 的存储:关键事实是「运行时托管」,不是「Java 字段 byte[] value」
在 libcore@4ebfb391 中,String.value 字段被注释掉,字符数据由 ART 运行时管理;Java 侧只保留 count(长度 + 压缩标志):
// ojluni/src/main/java/java/lang/String.java:147-177(libcore@4ebfb391)
// BEGIN Android-changed: The character data is managed by the runtime.
/*
We only keep track of the length here and compression here. ...
If STRING_COMPRESSION_ENABLED, count stores the length shifted one bit to the left with the
lowest bit used to indicate whether or not the bytes are compressed (see GetFlaggedCount in
the native code).
...
@Stable
private final byte[] value;
*/
private final int count;
// END Android-changed: The character data is managed by the runtime.
对应实现:
// String.java:1209-1216(length:读 count,含压缩标志处理)
public int length() { ... final boolean STRING_COMPRESSION_ENABLED = true; ... }
// String.java:1270-1272(charAt:运行时实现)
@FastNative
public native char charAt(int index);
// String.java:1660-1688(equals:改用 length()/charAt() 的 Android 实现)
// String.java:2291-2312(hashCode:同理,逐 char 计算)
// String.java:4971-4972(intern)
@FastNative
public native String intern();
ART 侧的压缩布局(参考 pin b753cf97,本批补核):
mirror::String的count_字段注释明确:压缩开启时最低位是StringCompressionFlag,其余位是长度,length = count_ >> 1(runtime/mirror/string.h:316-318)。GetLengthFromCount(L267-269)右移一位;IsCompressed(count)(L257-259)看最低位;GetFlaggedCount(length, compressible)(L271-278)把长度左移一位后或上压缩位。compressible的谓词不是“所有 Latin-1”也不是“通常含 NUL 的 ASCII”:本 pin 的String::IsASCII(uint16_t c)定义为(c - 1u) < 0x7f,即 1..0x7F,明确排除 U+0000(string.h:288-291,注释说明这是为了 Modified-UTF8 检测);AllASCII逐字符用这个谓词(string-inl.h:108-118),AllocFromUtf16/AllocFromCharArray/AllocFromModifiedUtf8/AllocFromByteArray的compressible由它或等价条件决定(string.cc:210-233、248-;string-alloc-inl.h:246-261、288-301)。string.cc:85-130是DoReplace/DoConcat,不是分配入口;本 pin 未找到AllocFromChars这个符号。因此含 NUL 的字符串不会因“ASCII”而压缩;0x80..0xFF 也不是这个谓词的范围。压缩标志是运行时表示选择,不等同于 Latin-1 的全部范围。String::CharAt(string-inl.h:44-56)先做边界检查;IsCompressed()为真时从GetValueCompressed()[index]取(每字符 1 字节),否则从GetValue()[index]取 UTF-16 码元。ComputeHashCode(L83-88)也按压缩/非压缩选择不同的字符读取,但哈希算法仍是对 UTF-16 码元序列计算。- 因此“压缩”不是 Java 层另存一个
byte[]字段,而是 ART 对象内部的表示选择;Java 侧count承载长度+标志,字符数据由运行时布局管理。含 NUL 的字符串是明显的非压缩反例,不能把压缩标志当作“ASCII 或 Latin-1”的等价物。
String.intern() 的真实宿主(参考 pin):
String.intern (libcore @FastNative native)
→ ART String_intern (runtime/native/java_lang_String.cc:107-111)
→ String::Intern (runtime/mirror/string.cc:459-460)
→ InternTable::InternWeak(this) (runtime/intern_table.cc:309-313)
→ InternTable::Insert (L206-258) / Intern (L261-292)
String::Intern()调用的是InternWeak,这是 Javaintern()的入口/新插入策略;但它不等于“所有 Java intern 结果必在弱表”。InternTable::Insert(L206-258)进入后先查 strong 表:命中就直接返回那个 strong 对象(L225-229);只有 strong 未命中才查 weak 表(仅当 weak access 可用),is_strong=true且 weak 命中时把 weak 提升为 strong,is_strong=false时返回 weak 或插入 weak。因此:已有强 intern 时String.intern()返回强对象;新插入通常进入弱表,只有在没有其它强引用、且仍处于弱驻留状态时才可能被 GC 回收。不能把“Java intern = 弱表/必可回收”当成无条件结论。InternTable::Intern(L261-292)对(utf16_length, utf8_data)先查 strong/weak 以避免分配,再String::AllocFromModifiedUtf8分配并SetHashCode;失败返回 null 并保留 OOME。该路径是 dex 字符串/类名强 intern 的入口,与 JavaString.intern()的 weak 路径不同。- 锁与并发:
intern_table_lock_保护 strong/weak 表;InternTable::Insert在持锁期间做表查找,必要时在分配前释放锁(Intern的注释/结构),避免在分配路径持锁。GC 通过SweepInternTableWeaks/VisitRoots维护 weak/strong 根(L65-103、L317+);具体停顿/并发行为需运行验证。
机制结论:
- 能下:本 pin 的 String 采用“运行时托管 +
count编码长度/压缩位”的表示;length()是 Java 方法(String.java:1209-1223,返回count >>> 1),charAt()是@FastNativenative;ART 的压缩位/长度编码和charAt分支已核。 - 不能下:不能把
byte[] value当作当前实现的 Java 字段来描述(该字段已被注释);也不能把intern()写成“无条件弱表/必可回收”(入口是InternWeak,但 strong 命中会直接返回 strong)。 - equals/hashCode 的实现分层:libcore
String.equals/hashCode是 Java 循环(length()+charAt());ART 对String.equals有 intrinsic(intrinsics_list.h:214),RV64 生成器在intrinsics_riscv64.cc:1141-1256:先判 null、引用相等、类相等(String不可继承,比较 class 字段)、count字段相等(同时比较压缩标志)、空串;然后按压缩/非压缩计算字节数,用Ld每次比较 8 字节,依赖字符串尾部零填充;StringEqualsOptimizations(compiler/optimizing/intrinsics.h:233-241)可在已知参数非空/为 String 时省略对应检查。hashCode没有对应的 RV64StringHashCodeintrinsic 条目(intrinsics_list.h只有 charAt/length/compareTo/equals),仍是ComputeHashCode的 Java/运行时循环;因此不能把equals的 intrinsic 外推到hashCode。 - compareTo 的活入口与分层:
String.java:1884-1899中 RI 的compareTo方法体在/* ... */注释块,活代码是@FastNative public native int compareTo(String)(L1897-1898)。Java 层没有逐字符比较;调用进入 ARTruntime/native/java_lang_String.cc:41-49的String_compareTo:rhs null 抛NullPointerException,否则mirror::String::CompareTo(runtime/mirror/string.cc:335-376)按 compressed/compressed、mixed、uncompressed 三分支比较前缀,首个不同字符返回差值,前缀相同返回长度差。ART 有VisitStringCompareTointrinsic 入口(intrinsics_riscv64.cc:3186/3203),但冻结调用点是否 intrinsify 仍需产物。失败边界只有 rhs null;返回值不抛异常。 - 压缩标志与 equals 的正确性边界:intrinsic 把
count(长度+压缩标志)直接比较,只有在“满足本 pin 的compressible谓词(1..0x7F,排除 NUL)的内容压缩、其余不压缩”这个 ART 不变式下,“压缩标志不同 ⇒ 内容不同”才成立。AllocFromUtf16/AllocFromCharArray/AllocFromModifiedUtf8/AllocFromByteArray等构造路径按AllASCII/UTF-8 长度计算compressible并写入GetFlaggedCount(string.cc:210-233、248-;string-alloc-inl.h:246-261、288-301),这是该不变式的来源;若某个构造/转换路径破坏它,intrinsic 会与 Javaequals的逐字符语义不一致。含 U+0000 的字符串是一个需要特别核对的边界:它不满足IsASCII,应保持非压缩。本 pin 只做了源码阅读,未构造反例运行验证。 - 性能含义:源码具备
StringEqualsintrinsic,且charAt(native 声明)与length()(Java 方法)各自存在调用点优化可能,但这不等于冻结产物已 intrinsify;压缩/非压缩字符串的count不等时会走返回 false 的快速路径(在不变量成立时),最终语义仍由 Javaequals保证。产物状态需机器码(同 ch01 边界)。
3.2 编码解码:getBytes 的快路径与回退
// ojluni/src/main/java/java/lang/String.java:1595-1617(libcore@4ebfb391)
public byte[] getBytes(Charset charset) {
if (charset == null) throw new NullPointerException();
// BEGIN Android-changed: Skip StringCoding optimization that needs access to java chars.
/*
return StringCoding.encode(charset, coder(), value);
*/
final int len = length();
final String name = charset.name();
if ("UTF-8".equals(name)) {
return CharsetUtils.toUtf8Bytes(this, 0, len);
} else if ("ISO-8859-1".equals(name)) {
return CharsetUtils.toIsoLatin1Bytes(this, 0, len);
} else if ("US-ASCII".equals(name)) {
return CharsetUtils.toAsciiBytes(this, 0, len);
} else if ("UTF-16BE".equals(name)) {
return CharsetUtils.toBigEndianUtf16Bytes(this, 0, len);
}
ByteBuffer buffer = charset.encode(this);
...
}
CharsetUtils(luni/src/main/java/libcore/util/CharsetUtils.java)对应方法为 @FastNative native:toAsciiBytes(L31-32)、toIsoLatin1Bytes(L38-39)、toUtf8Bytes(L45-46);toBigEndianUtf16Bytes 为 Java 实现(L52 起)。
解码侧(new String(byte[], Charset)) 走 java.lang.StringFactory(本 pin 在 libart/src/main/java/java/lang/StringFactory.java):
String(byte[], Charset)
→ StringFactory.newStringFromBytes(data, offset, byteCount, charset)(L83-110)
├─ UTF-8 → native newStringFromUtf8Bytes(data, offset, byteCount)(L134-135; ART java_lang_StringFactory.cc:122)
├─ ISO-8859-1 → native newStringFromBytes(data, high=0, offset, byteCount)(L71-72; ART :33)
├─ US-ASCII → Java CharsetUtils.asciiBytesToChars → newStringFromChars
└─ 其它 → charset.decode(ByteBuffer) → newStringFromChars
- UTF-8 的 ART native 解码器(
java_lang_StringFactory.cc:122-296)实现 W3C UTF-8 解码:ASCII 直通、按首字节查kTableUtf8Needed决定后续字节数、对非法/截断序列写 U+FFFD(“maximal subpart”规则),≤256 字节用栈缓冲,超出再分配。newStringFromUtf16Bytes只按字节对拷贝、不校验 UTF-16 序列(注释 L56-60);ISO-8859-1 的high=0走 Latin-1 直通。 - 因此同一个
CharsetUtf8Benchmark.time_ascii/bmp2/bmp3/supplementarycell 走的是“Java 名称分派 + ART native UTF-8 解码”,不是getBytes的编码方向;两者不要混为一条链。
- 正常路径:UTF-8 / ISO-8859-1 / US-ASCII 走
CharsetUtils的@FastNative专用入口(L31-46);UTF-16BE 分支是 Java 实现(toBigEndianUtf16Bytes,L52 起),不是 native;其它字符集走Charset.encode→CharsetEncoder。 - 失败/替换语义:
charset == null→NullPointerException;getBytes(String charsetName)遇不支持名称 →UnsupportedEncodingException(L1566 方法声明;内部经Charset.forNameUEE,L1573)。getBytes(Charset)的 javadoc 明确:总是用该字符集的默认替换字节序列处理 malformed/unmappable 输入(L1582-1585)——需要 REPORT/IGNORE 控制时应使用CharsetEncoder,不能把 encoder 的可配置行为套在这个 API 上。 - 回退:无专用快路径即回退通用 encoder;语义不变、成本不同。
- 边界:字符集数据/ICU 支持在
libcore/icu(见 ch07)与外部 ICU 仓;本章不推断各字符集的完整实现来源。
3.3 System.arraycopy:范围、重叠与边界
// ojluni/src/main/java/java/lang/System.java:447-450(libcore@4ebfb391)
@FastNative
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
Android 另有一个 private static 的 char[] 专用 Java 版本,注释明确它是 ART 编译器优化所需;它不是 public API,外部调用 System.arraycopy(char[]...) 在 Java 源码层实际绑定的是 public arraycopy(Object,...):
// System.java:456-470(阈值与注释区;方法体 467-499)
/** The char array length threshold below which to use a Java (non-native) version ... */
private static final int ARRAYCOPY_SHORT_CHAR_ARRAY_THRESHOLD = 32;
/**
* The char[] specialized version of arraycopy().
* Note: This method is required for runtime ART compiler optimizations.
* Do not remove or change the signature.
*/
private static void arraycopy(char[] src, int srcPos, char[] dst, int dstPos, int length) { ... }
该 Java 版本整体为 System.java:467-499(方法起于 467),显式检查 NullPointerException、负索引/越界(ArrayIndexOutOfBoundsException),并对重叠区间反向拷贝(480-494):
// System.java:480-494(反向拷贝;495-499 是超阈值时的 arraycopyCharUnchecked native 分支)
if (length <= ARRAYCOPY_SHORT_CHAR_ARRAY_THRESHOLD) {
// Copy char by char for shorter arrays.
if (src == dst && srcPos < dstPos && dstPos < srcPos + length) {
// Copy backward (to avoid overwriting elements before
// they are copied in case of an overlap on the same array.)
for (int i = length - 1; i >= 0; --i) {
dst[dstPos + i] = src[srcPos + i];
}
} else {
// Copy forward.
...
机制要点:
- 范围:
srcPos/length必须落在src内、dstPos/length落在dst内;否则ArrayIndexOutOfBoundsException。 - 重叠:
src == dst且正向区间重叠时反向复制(Java 层 char 快路径显式实现;native 通用路径用 memmove 语义)。 - 类型边界:primitive 数组按类型精确匹配;引用数组复制需通过数组存储检查(
ArrayStoreException),并涉及读/写屏障——具体由 ART 运行时实现,libcore Java 层不体现。 - 失败:
null→ NPE;越界 → AIOOBE;引用类型不兼容 → ArrayStoreException。 - 回退:在 libcore 的专用
char[]Java 方法内部,短数组走 Java 快路径(阈值 32),超过阈值走arraycopyCharUncheckednative。外部调用点是 publicarraycopy(Object,...),走通用@FastNativenative;ART 还可对公共调用点做SystemArrayCopyintrinsic,并在可用时用专门的SystemArrayCopyChar/Byte/Intintrinsic。专用 Java 方法本身不是外部可调 API,不能把“阈值 32”直接套到任意System.arraycopy调用点。几条路径语义应一致,但冻结产物的实际选择需产物依据。
3.3.1 ART 侧真实实现:native 与 RV64 intrinsic 两条路径
libcore 的专用 char[] Java 方法超过 32 个元素时调用 arraycopyCharUnchecked;该 native 与通用入口在 ART 中注册为:
// art@b753cf97 runtime/native/java_lang_System.cc:243-251(注册表)
FAST_NATIVE_METHOD(System, arraycopy, "(Ljava/lang/Object;ILjava/lang/Object;II)V"),
FAST_NATIVE_METHOD(System, arraycopyCharUnchecked, "([CI[CII)V"),
...
通用 native 路径 System_arraycopy(java_lang_System.cc:50-165)按以下顺序执行:
- 空指针与数组性:
src==null/dst==null→ NPE(消息src == null/dst == null);不是数组 →ArrayStoreException(“source/destination ... is not an array”)。 - 边界:负的
srcPos/dstPos/count,或srcPos > src.length-count、dstPos > dst.length-count→ArrayIndexOutOfBoundsException,消息包含五个参数。 - 同组件类型:
srcComponentType == dstComponentType时按 primitive 宽度分组做Memmove——boolean/byte按 1 字节、char/short按 2 字节、int/float按 4 字节、long/double按 8 字节;引用数组走AssignableMemmove。分组是“同宽度位拷贝”,不改变元素类型语义。 - 组件类型不同:若目标组件是 primitive 或源组件是 primitive →
ArrayStoreException(Incompatible types: src=..., dst=...)。两边都是引用数组时:目标组件可赋值自源组件 →AssignableMemcpy(整块拷贝+写屏障);否则AssignableCheckingMemcpy逐元素检查,遇到不可赋值元素立即ArrayStoreException(消息source[i] of type ... cannot be stored in destination array of type ...)。
引用数组的读/写屏障(art@b753cf97 runtime/mirror/object_array-inl.h):
AssignableMemmove(L103-172):同数组重叠时按copy_forward选择正向/反向;不能直接memmove,因为引用需要读屏障。若源数组不是 gray(Baker 读屏障状态 0),可走“无逐元素读屏障”的快循环;否则每个元素GetWithoutChecks内含读屏障。拷贝结束后调用WriteBarrier::ForArrayWrite(this, dst_pos, count)一次性做写屏障。AssignableCheckingMemcpy(L234-330):在拷贝过程中缓存“最近一个可赋值元素类”,只有类变化时才重新dst_class->IsAssignableFrom;遇到不可赋值元素先执行写屏障,再抛ArrayStoreException。这也说明“类型检查”和“写屏障”是两件事,不能互相替代。
RV64 编译期 intrinsic(compiler/optimizing/intrinsics_riscv64.cc):
- 通用对象数组
SystemArrayCopy:kSystemArrayCopyThreshold = 128(L1634)。CreateSystemArrayCopyLocationSummary(code_generator.cc:1704-1751)先做常量门:常量src_pos<0/dst_pos<0→ 不生成 intrinsic;常量length<0或length >= length_threshold→ 不生成 intrinsic;若DestinationIsSource且两个位置都是常量且src_pos<dst_pos→ 不生成 intrinsic;generic intrinsic 遇到 primitive 数组 → 不生成 intrinsic。 CheckSystemArrayCopyNullOrOverlap(L1654-1721)是运行期门:源/目标 null → slow;若同一数组且src_pos < dst_pos→ 保守转 slow,不检查区间实际是否相交(即使 length=0 或区间不重叠也会转),因此不能写成“已证明重叠才 slow”。长度门分两种:变量长度用Bgeu(length, 128)(unsigned,负数和 ≥128 都 slow,L1710-1715);常量长度已在 LocationsBuilder 用copy_threshold过滤,codegen 只 DCHECK0 <= len <= copy_threshold(L1717-1719)。引用拷贝在 Baker 读屏障下先读源数组 lockword 的 RB 状态位(LockWord::kReadBarrierStateShift),非 gray 走裸lwu/sw循环,gray 走ReadBarrierSystemArrayCopySlowPathRISCV64逐元素ReadBarrier::Mark(L35-91、L1890-1989);结束后MarkGCCard(dest)(L1989)。- primitive 专用 intrinsic
SystemArrayCopyByte/Char/Int:kSystemArrayCopyPrimThreshold = 384字节,copy_threshold = 384/sizeof(element)(L2000-2003)。常量长度在 LocationsBuilder 用该 typed 阈值过滤;变量长度的运行期Bgeu仍使用全局kSystemArrayCopyThreshold=128(CheckSystemArrayCopyNullOrOverlap的copy_threshold参数只用于常量 DCHECK),所以 typed 阈值与变量门不是同一个数。主循环按 16 字节块(Ld×2/Sd×2)拷贝,尾部按剩余字节用 8/4/2/1 字节加载存储展开;常量长度与变量长度分别生成不同循环(L2071-2225)。char[]的 Java 阈值 32 又是在 libcore 专用方法内部,三层阈值不能合并。 - AArch64 对照:
intrinsics_arm64.cc:3191/3557也有kSystemArrayCopyPrimThreshold=384、kSystemArrayCopyThreshold=128,slow path/重叠/边界检查结构相同;引用数组同样使用 Baker 读屏障。差异主要在指令选择(AArch64 的加载/存储与 RV64 的lwu/sw不同),语义与阈值不是“只有 RV64 才有”的特例。 - 产物边界:以上是编译器“具备生成能力”和运行期 native 兜底;某个冻结调用点是否被 intrinsic、是否走到 slow path,需要 odex/aot_map 或反汇编确认(本报告缺该产物)。
3.4 Arrays:排序与查找的委托结构
Arrays.sort(int[])(L123-125)→DualPivotQuicksort.sort(a, 0, 0, a.length);范围版本sort(int[],from,to)(L146-149)先rangeCheck,再DualPivotQuicksort.sort(a, 0, from, to)。DualPivotQuicksort是双轴快排实现(ojluni/src/main/java/java/util/DualPivotQuicksort.java),源码入口接受(a, parallelism, left, right);算法内部的具体插入排序/双轴划分阈值不在本章展开。Arrays.binarySearch(int[],int)(L1720-1722)→binarySearch0(a,0,a.length,key);范围版本(L1756 起)先做范围检查再调用。binarySearch0(L1763-1781)用mid=(low+high)>>>1避免加法溢出,命中返回下标,未命中返回-(low+1)(插入点编码)。Arrays.copyOfRange(int[],from,to)(L4687-4703):Android 去掉了原clone()快路径(注释 b/359517996),改为new int[newLength]+System.arraycopy(original, from, copy, 0, Math.min(original.length-from,newLength));to-from<0抛IllegalArgumentException(from + " > " + to),from越界由arraycopy的范围检查抛ArrayIndexOutOfBoundsException。因此它至少是一次分配 + 一次数组复制,不是零拷贝视图。Arrays.equals的入口与分派:同引用→true,任一 null→false,长度不等→false。primitive 数组在本 pin 用 Unsafe 16 字节向量循环(long[]L2427-2455、char[]L2868-2905,注释标为 Android-changed Vectorized equality check;int[]L2554-2580 先对齐再做 16 字节循环);float[]/double[]在向量比较后用Float.floatToIntBits/Double.doubleToLongBits处理 NaN 与 ±0.0(float L3498-3535);Object[]走Objects.equals逐元素(L3701-3717)。范围重载先rangeCheck再做同样的向量/逐元素比较(long 范围 L2497-2535)。算法内部未逐类型展开的剩余项属于源码 gap,运行对拍未做。- 失败:
fromIndex > toIndex→IllegalArgumentException;越界 →ArrayIndexOutOfBoundsException;sort的范围检查在rangeCheck。 - 成本:排序算法复杂度/最坏情形属算法层;
copyOfRange的成本是分配+复制;本章不给实测数字。
3.5 纠正两个常见误述
- “String 内部就是一个
byte[] value(Java 字段)”:在本 pin 不成立——value已注释,字符数据由运行时托管(§3.1)。描述压缩表示时必须标注“ART 原生侧布局,跨仓”。 - “解释执行时 String/数组操作一律走 JNI”:不成立——
equals/hashCode是 Java 循环 +charAtnative/@FastNative;char[]的arraycopy有纯 Java 快路径;JIT/AOT 还可能将charAt/arraycopy等 intrinsic 化。具体到某个冻结产物,需要产物/机器码依据。
3.6 可复制观测
已执行:
PIN=4ebfb391ee0109d16abe6e2bc965724b940928c5
ART=b753cf97923c3695338d21466fa14c57b480a59a
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/lang/String.java | sed -n '147,177p;1209,1216p;1270,1272p;1595,1617p;1660,1688p;2291,2312p;4971,4972p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/lang/System.java | sed -n '447,520p'
git -C sources/libcore.git show $PIN:luni/src/main/java/libcore/util/CharsetUtils.java | sed -n '26,75p'
git -C /tmp/art-commits.git show $ART:runtime/native/java_lang_String.cc | sed -n '30,150p'
git -C /tmp/art-commits.git show $ART:runtime/mirror/string.h | sed -n '240,320p'
git -C /tmp/art-commits.git show $ART:runtime/mirror/string-inl.h | sed -n '40,110p'
git -C /tmp/art-commits.git show $ART:runtime/intern_table.cc | sed -n '60,105p;200,315p'
git -C /tmp/art-commits.git show $ART:runtime/native/java_lang_System.cc | sed -n '40,255p'
git -C /tmp/art-commits.git show $ART:runtime/mirror/object_array-inl.h | sed -n '100,335p'
git -C /tmp/art-commits.git show $ART:compiler/optimizing/intrinsics_riscv64.cc | sed -n '1530,1995p;2000,2230p'
未执行:getBytes 各字符集的实际输出对拍、arraycopy 重叠/边界用例运行、ART intrinsic 的实际触发观察(需产物/设备)。
3.7 缺口与下一步
- String 压缩与 intern:
count的压缩位/长度编码、CharAt分支、intern()的InternWeak→InternTable::Insert已按 ART 参考 pin 核;仍未做运行态压缩选择、GC 弱表清理与并发行为验证。 - arraycopy 的产物形态:native/Java 快路径/RV64 intrinsic 三条实现已按源码核;冻结产物实际选择哪条、是否内联,需要 odex/aot_map/反汇编(缺)。
- 字符集覆盖:UTF-8/ISO-8859-1/US-ASCII/UTF-16BE 以外的字符集走通用 encoder;ICU 数据版本已在 ch07 标明为运行时/外部模块属性。
- 失效边界:
charset == null抛NullPointerException,未知字符集名抛UnsupportedEncodingException;arraycopy 的 null/越界/引用类型不兼容分别抛 NPE/AIOOBE/ArrayStoreException,均不回退到静默截断。
第 4 章:Buffer、Memory、Unsafe 与 VarHandle——状态、视图与生命周期
- 状态:初稿(源码确证;未运行)。
- 来源:[S01][S02];跨仓代表链按 [S03][S04]。
- Pin:
platform/libcore@4ebfb391;ART 参考 pinplatform/art@b753cf97(仅边界)。 - 本章回答:ByteBuffer 的状态与视图如何共享,Direct/Heap/Mapped 的内存模型差异,Direct 缓冲区的生命周期(Cleaner)如何工作,以及
Memory/Unsafe/VarHandle三代访问接口的分层与代价边界。第 1 章的 putChar 纵切是本章的一个特例。
4.1 Buffer 状态模型与视图
Buffer 的经典状态(position/limit/capacity/mark)与检查逻辑见 Buffer.java(nextPutIndex(int) L741-748 → BufferOverflowException;checkIndex 越界 → IndexOutOfBoundsException)。视图的关键在于共享 vs 复制:
HeapByteBuffer.slice()(L141-153)与slice(int,int)(L157-167)、duplicate()(L170-179):共享同一byte[] hb,新建 Buffer 对象、独立 position/limit/mark。slice()以当前 position 为起点、remaining 为容量,并把pos+offset作为新视图的数组偏移;slice(index,length)用index+offset,都不复制数组。asReadOnlyBuffer()(L182-192)在截断数组路径返回HeapByteBufferR,否则返回isReadOnly=true的HeapByteBuffer,仍共享hb。DirectByteBuffer的slice()(L184-196)、slice(int,int)(L198-212)、duplicate()(L215-227)、asReadOnlyBuffer()(L229-240)都传入同一个MemoryRef,只改变 mark/position/limit/capacity 和offset;slice检查memoryRef.isAccessible,duplicate/asReadOnlyBuffer检查memoryRef.isFreed。源码注释明确:MemoryRef可在原始DirectByteBuffer失去强引用后继续存活(例如视图仍持有它,L63-70),因此清理语义不能绑定在“原始 Buffer 对象”上;视图的isReadOnly只是写路径检查,不改变底层MemoryRef的可访问性。- 因此
slice/duplicate/asReadOnlyBuffer都是共享存储的视图,不是深拷贝;区别只在起点/容量/只读标志和状态字段。HeapByteBuffer共享 Java 数组,DirectByteBuffer共享 native 地址 +MemoryRef;后者的生命周期由最后一个持有者/Cleaner 决定(§4.3)。
状态/字段速查:
| 字段/方法 | 位置(pin) | 语义 |
|---|---|---|
byte[] hb | HeapByteBuffer | 堆内存储;视图共享 |
MemoryRef memoryRef | DirectByteBuffer L99 | 共享内存引用(地址/可访问性/释放标志) |
Cleaner cleaner | DirectByteBuffer L98 | 仅外部地址路径创建(见 §4.3) |
isReadOnly | Direct/Heap | 只读视图写 → ReadOnlyBufferException |
memoryRef.isAccessible/isFreed | DirectByteBuffer | 释放后写入 → IllegalStateException("buffer is inaccessible") |
状态迁移(Buffer.java,pin 内逐字行):
| 方法 | 行号 | 迁移 |
|---|---|---|
mark() | L417 | mark = position |
reset() | L433 | mark < 0 → InvalidMarkException;否则 position = mark |
clear() | L458 | position=0; limit=capacity; mark=-1(不擦数据) |
flip() | L486 | limit=position; position=0; mark=-1 |
rewind() | L508 | position=0; mark=-1(limit 不变) |
compact()(DirectByteBuffer) | L364-379 | 检查可访问/只读 → System.arraycopy(hb, position+offset, hb, offset, remaining()) → position(rem); limit(capacity); discardMark(); |
不变量:mark 未设置时为 -1;仅当已设置时才要求 0 ≤ mark ≤ position;恒有 position ≤ limit ≤ capacity。clear/flip/rewind 在各自方法体直接置 mark = -1(Buffer.java:458-462/486-490/508-511);compact() 调 discardMark()(DirectByteBuffer.java:378,Buffer.java:775-777 定义);slice/duplicate 共享存储但 position/limit/mark 独立(§4.1 上文)。
失败路径:越界(BufferOverflowException/IndexOutOfBoundsException)、只读(ReadOnlyBufferException)、已释放(IllegalStateException)。回退:无——这些是语义拒绝,不是可降级错误。
4.2 Direct / Heap / Mapped 的内存模型
// ojluni/src/main/java/java/nio/DirectByteBuffer.java:53-95(节选,libcore@4ebfb391)
final static class MemoryRef {
byte[] buffer;
long allocatedAddress;
final int offset;
boolean isAccessible;
boolean isFreed;
...
MemoryRef(int capacity) {
VMRuntime runtime = VMRuntime.getRuntime();
buffer = (byte[]) runtime.newNonMovableArray(byte.class, capacity + 7);
allocatedAddress = runtime.addressOf(buffer);
// Offset is set to handle the alignment: http://b/16449607
offset = (int) (((allocatedAddress + 7) & ~(long) 7) - allocatedAddress);
...
}
void free() {
buffer = null;
allocatedAddress = 0;
isAccessible = false;
isFreed = true;
}
}
- Java 分配的 Direct(
DirectByteBuffer(int capacity, MemoryRef),L101-109):VMRuntime.newNonMovableArray(byte.class, capacity+7)+addressOf+ 手工对齐(+7 & ~7,b/16449607)。buffer保持引用,GC 负责回收;此路径cleaner = null(L108)。 - JNI
NewDirectByteBuffer(void*, long)路径(L112-118):MemoryRef(addr, this),originalBufferObject指向原 Buffer(用于 PhantomReference 共享生命周期,L62-70 注释),不替调用者释放 native 内存;此路径同样cleaner = null(L117)。 - fd/unmapper 路径(L122-130):
MemoryRef(addr, null)+Cleaner.create(memoryRef, unmapper);这是唯一创建 Cleaner 的构造(还有 OOME 注释,L131-140)。 - Heap:纯
byte[],无地址概念;putChar等直接按bigEndian写字节(ch03/ch01 已示)。 - Mapped:benchmark 的 MAPPED 分支通过
FileChannel.map创建(ByteBufferBenchmark.newBufferL65-73),最终由DirectByteBuffer承载(类继承关系 L41)。
结论:三种所有权必须分开——① Java 非移动数组(无 Cleaner,GC 回收);② JNI 外部地址(无 Cleaner,creator 所有,originalBufferObject 只用于共享生命周期);③ fd/unmapper(Cleaner 负责 unmap)。三者共享 Java 语义,但释放责任与失败模式不同,不能合并成“Direct 都靠 Cleaner”。
4.3 Memory / Unsafe / VarHandle:三代访问面的分层
| 接口 | 位置(pin) | 典型能力 | 边界 |
|---|---|---|---|
libcore.io.Memory | luni/src/main/java/libcore/io/Memory.java | 数组形态 peek/poke(L69/86/122/142/168/206)、地址形态 peekByte(long)(L243)、peekInt(address,swap)(L249)、memmove native(L236)、unsafeBulkGet/Put(L46/54) | 内部 API(@hide/UnsupportedAppUsage),非公开 SDK 面 |
sun.misc.Unsafe | ojluni/src/main/java/sun/misc/Unsafe.java | allocateMemory/freeMemory(L527/535)、CAS(L157-189)、volatile 读写(L201+)、loadFence/storeFence/fullFence(L828/844/857) | 非标准内部/兼容接口,不是 Android SDK 公共 API;getUnsafe() 只允许 bootclasspath 调用者(L38-75),应用不可依赖。屏障指令由 VM/架构决定,RV64 的 VarHandle/Unsafe 入口见 ch05 |
java.lang.invoke.VarHandle | ojluni/src/main/java/java/lang/invoke/VarHandle.java | 签名多态访问器:get/set(L496/L522)及 volatile/acquire/release/opaque 等模式;标注 @IntrinsicCandidate | 标准 Java 面;访问方法可被 VM intrinsic 化,冻结产物是否 intrinsify 需产物依据 |
分层语义:
Memory是 libcore 内部的高频工具(Direct Buffer、IO 辅助),native 侧由luni/src/main/native/libcore_io_Memory.cpp注册(如FAST_NATIVE_METHOD(Memory, pokeShortNative, "(JS)V"),L256;实现 L164-166 用put_unaligned)。Unsafe是历史兼容面:CAS/volatile 读写为 native(sun.misc.Unsafe.java:157-189,201+),fence 为 native(L828/844/857)。VarHandle是新标准面:访问器是签名多态 native,并标注@IntrinsicCandidate(VarHandle.java:496-499,522-525);javadoc 分别列出失败:WrongMethodTypeException(访问模式类型与调用点 symbolic type descriptor 不匹配)与ClassCastException(类型匹配但引用转换失败)(L491-495);访问模式(plain/opaque/acquire/release/volatile)决定内存语义。- JMM 边界的准确表述:fence/CAS 的 Java 语义由规范定义;
RV64/AArch64的具体指令(fence/AMO/LD-AQ/ST-RL 等)由 ART 编译器按架构生成——属跨仓,本章不虚构指令序列(ch05 给架构入口的定位与待核项)。
4.4 生命周期与 Cleaner
MemoryRef.free()(DirectByteBuffer.java:90-95)把isAccessible=false/isFreed=true并清空地址。调用者已找到:luni/src/main/java/java/nio/NioUtils.java的freeDirectBuffer(ByteBuffer)(L52-64)——null 直接返回;强转DirectByteBuffer;cleaner != null时先cleaner.clean()(L59-60);随后无条件dbb.memoryRef.free()(L63)。即显式释放是“先 Cleaner thunk、再 MemoryRef 标记释放”的顺序;别名/其它视图后续访问会命中isFreed/isAccessible检查(逐路径见上文)。进一步调用入口/别名安全与设备行为仍单列(未核)。- 检查是逐路径的:写入方法检查
memoryRef.isAccessible(如putCharL451/L454);视图创建路径检查isFreed(duplicate()L216、asReadOnlyBuffer()L230,另有 L492/L594 等方法);并非所有 get/put 都执行同一检查,具体按方法体核对。 - Java 分配路径:无 Cleaner,内存随
buffer引用被 GC 回收(非移动数组由 VM 管理)。 - fd/unmapper 路径:
Cleaner.create(memoryRef, unmapper)(L130)。Cleaner.create在thunk == null时返回 null(sun.misc.Cleaner.java:157-160),因此“unmapper 为空仍记录 cleaner”不成立。 - 自动 vs 显式:不可达时由 Cleaner 线程执行 thunk(时机不确定);显式
clean()一次性移除(remove(this)失败即返回,L166-168)。若 thunk 抛出 Throwable,Cleaner 打印Error("Cleaner terminated abnormally")并System.exit(1)(L169-179)——不是“只记录”。 - 同仓可见的清理入口(补充):
java.nio.NioUtils.freeDirectBuffer(L52-64,cleaner.clean()→memoryRef.free())、sun.nio.ch.FileChannelImpl.unmap(L902-907,((DirectBuffer)bb).cleaner()不存在时跳过)、libcore.io.MemoryMappedFile.close()(直接munmap,L65 起)。MemoryRef.free 的直接调用者即 NioUtils.freeDirectBuffer(已核);该 API 的其它调用入口与设备行为仍可单列。 - 失败/回退:自动 GC 清理不保证时间;显式
clean()在调用线程同步执行 thunk(且一次性移除),但“资源是否全部消失”由 thunk 负责,不保证别名/其它资源安全消失——不要把自动清理的不确定性套给显式调用。
4.5 正常/失败/回退(本章范围)
- 正常:按上述路径读写;视图共享状态但独立 position/limit。
- 失败:越界、只读、已释放(§4.1);
Memory/Unsafe的地址误用属未定义行为/崩溃(native 语义,本章不测试)。 - 回退:无自动回退;选择不同的 buffer 类型/视图是设计决策。intrinsic→native 的路径选择(如
Memory.pokeShortNative)见 ch01。
4.6 可复制观测
已执行:
PIN=4ebfb391ee0109d16abe6e2bc965724b940928c5
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/nio/DirectByteBuffer.java | sed -n '53,140p;225,245p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/nio/HeapByteBuffer.java | sed -n '135,180p'
git -C sources/libcore.git show $PIN:luni/src/main/java/libcore/io/Memory.java | sed -n '40,60p;230,260p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/sun/misc/Unsafe.java | sed -n '520,545p;820,860p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/lang/invoke/VarHandle.java | sed -n '490,530p'
未执行:Cleaner 实际触发观察(需运行/GC 控制);VarHandle 各访问模式的机器码核验(需产物);Unsafe 地址误用崩溃实验(不做)。
4.7 缺口与下一步
- VarHandle → VM 实现的映射:
@IntrinsicCandidate访问器在 ART 中的生成路径需按 ART 参考 pin 逐项核对(跨仓,未做)。 - Memory 的
unsafeBulkGet/Put实现:native 侧(libcore_io_Memory.cpp)与 GC 屏障的关系待核。 - Mapped 文件语义:
FileChannel.map的 unmap 时机与Cleaner交互属系统行为,需运行观察(未做)。
- 失效边界:视图只读或生命周期结束后写入抛
ReadOnlyBufferException/IllegalStateException;Cleaner 回收不是同步保证,显式释放与 GC 时机必须分开。
第 5 章:集合与并发——容器结构、线程池状态机与 JMM 边界
- 状态:初稿(源码确证;未运行)。
- 来源:[S01][S02];跨仓代表链按 [S03][S04]。
- Pin:
platform/libcore@4ebfb391;ART 参考 pinplatform/art@b753cf97(仅边界)。 - 本章回答:常用容器与并发容器的数据结构、线程池状态机、AQS/LockSupport 的等待/唤醒路径、
atomic/VarHandle/fence 的 Java 语义与架构实现的边界。
手机可横向滑动查看本图
ConcurrentHashMap.java;[S01]。见图 3。先看 putVal 的顺序:表未初始化 → initTable(CAS 只改对象 SIZECTL,局部 sc 不因此变 -1);桶为空 → casTabAt 空桶 CAS,成功后 break 退出插入循环,循环结束仍执行 addCount(1L, binCount) 再 return null,不是 CAS 成功就直接返回。桶非空且非 MOVED:先判 onlyIfAbsent && fh == hash && (fk == key || key.equals(fk)) && fv != null → 无锁 return fv;其余才进入 synchronized 桶锁/树化等分支。右侧是 transfer → split → last migrator 的迁移链;TreeBin 删除/balanceDeletion 是独立删除分支,不能读成普通 put 必删除。
手机可横向滑动查看本图
signalNext(head) 的 LockSupport.unpark(s.waiter),park 不调用 unpark;ART_USE_FUTEXES 是编译期分支。来源:ch05、AbstractQueuedSynchronizer.java、LockSupport.java、ART thread.cc;[S01][S02]。见图 4。左栏是等待线程 await/release/等待/reacquire,右栏是持锁 signal/释放线程;signal 清 COND 并 enqueue 只让等待者可以重新竞争,不表示立即获锁。unpark 来自 signal/doSignal 的条件唤醒或 release 成功后 signalNext(head) 的 LockSupport.unpark(s.waiter),park 不调用 unpark;ART_USE_FUTEXES 是编译期分支。
5.1 常用容器:结构与扩容
| 容器 | 关键字段(pin) | 行为要点 |
|---|---|---|
HashMap | transient Node<K,V>[] table(L389)、loadFactor(L427)、TREEIFY_THRESHOLD=8(L259)、putVal(L631)、resize(L683)、treeifyBin(L761) | 数组 + 链表/红黑树:putVal 做哈希桶插入,resize 扩容,treeifyBin 在桶内节点过多时树化;并发写不安全 |
ArrayList | transient Object[] elementData(L146)、add(L502) | 动态数组;扩容复制;按索引访问 O(1) |
ConcurrentHashMap | transient volatile Node<K,V>[] table(L789)、putVal(L1022 起)、initTable(L2312-2332)、transfer(L2445 起) | 初始化用 sizeCtl CAS(失败 Thread.yield() 自旋);putVal 内 CAS 插入空桶(L1031 附近)、helpTransfer(约 L1035)、桶内 synchronized (f)(约 L1043)、树化 treeifyBin(约 L1080);扩容机制见下 |
- 正常:按方法约定读写。
- 失败:
HashMap迭代期间结构性修改 →ConcurrentModificationException(fail-fast);ArrayList越界 →IndexOutOfBoundsException;内存不足 →OutOfMemoryError(分配路径,通用)。 - 回退:无自动回退;并发场景需改用并发容器或外部同步(设计选择)。
- 成本:扩容复制与树化是主要结构性成本;本章不给实测数字,cell 映射留 ch09。
CHM transfer 的并发迁移(L2445-2573 逐段):
- 分段领取:先算
stride = (NCPU > 1) ? (n >>> 3) / NCPU : n,再与MIN_TRANSFER_STRIDE(16)取最大(L2446-2448)。单核/双核时三元分支不能省略:单核取n,双核取(n>>>3)/2,都还要经过下界钳制。发起者创建nextTab = new Node[2n](OOME 时sizeCtl = Integer.MAX_VALUE并返回,L2449-2456),设置nextTable与transferIndex = n(L2457-2459)。工作线程用CAS(TRANSFERINDEX, nextIndex, nextBound)领取[nextBound, nextIndex)区间(L2470-2479)。 - 逐桶迁移:空桶用
casTabAt(tab, i, null, fwd)直接放ForwardingNode(L2503);已是MOVED则跳过(L2505)。普通链表桶在synchronized (f)内先扫描lastRun复用尾部链,再按(hash & n)把前缀节点分到ln/hn,写入nextTab[i]与nextTab[i+n],最后把旧桶置为fwd(L2506-2545)。 - TreeBin 桶的拆分(补足细节):
TreeBin继承Node且 hash 为TREEBIN,自身持有root/first/waiter/lockState(L2794-2802):写树结构用lockRoot的LOCKSTATECAS(WRITER),读者以READER计数、最后读者唤醒等待写者;竞争写者在contendedLock里LockSupport.park(this)(L2871-2903)。find在WAITER|WRITER置位时退化为沿first链表线性查找,否则在 reader 计数下走红黑树(L2910-2941)。迁移时遍历t.first链表(不是递归树),按(h & n)重建lo/hi两条TreeNode链;lc<=UNTREEIFY_THRESHOLD(6)的侧转回普通链表,另一侧为空时直接复用原t,两侧都非空且都超过阈值时各自new TreeBin(L2546-2573)。 - 最后迁移者提交:所有区间完成后,线程对
SIZECTL做 CASsc-1;若(sc-2) != resizeStamp(n)<<RESIZE_STAMP_SHIFT说明仍有线程在迁移,直接返回;否则最后线程置finishing=true、i=n复核(L2482-2497),随后nextTable=null; table=nextTab; sizeCtl=(n<<1)-(n>>>1)完成提交。 - 失败/回退:初始化 OOME →
sizeCtl=MAX拒绝后续扩容;CAS 领取/提交失败 → 重试或退出;迁移期间对旧桶的读写看到ForwardingNode后经helpTransfer/新表继续,未领取到区间的线程也可协助迁移。 - 正确性边界(收紧):
ForwardingNode只说明“该桶已迁移,后续访问应转去nextTable”;它不构成“任意并发遍历无漏读”的普遍保证。CHM 的迭代器是弱一致的:迁移/更新期间可能看到旧表或新表、可能漏掉迭代开始后新增的元素;同一个键最多出现一次,但对应的值可能是旧值或新值。可见性来自table/nextTable的 volatile 读写、桶锁/CAS 和fwd的顺序,而不是单个ForwardingNode的魔法。
5.1.1 装箱缓存:范围是源码事实,不是“JVM 一定缓存更多”
Integer.valueOf(int)(Integer.java:1192-1196)在 [-128, 127] 返回 IntegerCache.cache,否则 new Integer(i)。本 pin 的 IntegerCache(L1126-1168)把 low=-128、high=127 写死;原 JDK 的 java.lang.Integer.IntegerCache.high 属性读取和 CDS 归档都在 Android-removed 注释中(L1140-1158、L1160-1161),因此不能把 -XX:AutoBoxCacheMax 的 HotSpot 行为套到 Android。
其余缓存(本 pin):
| 类型 | 缓存范围/形式 | 入口 |
|---|---|---|
Long | [-128,127] 的 LongCache | Long.valueOf(long)(L1231-1238) |
Short | [-128,127] 的 ShortCache | Short.valueOf(short)(L269-283) |
Byte | 全部 256 个值 | Byte.valueOf(byte)(L143-153) |
Character | '\u0000'..'\u007F'(128 个) | Character.valueOf(char)(L9280-9285) |
Boolean | 固定 TRUE/FALSE 两个常量 | Boolean.valueOf(boolean)(L180-182) |
- 语义后果:自动装箱走
valueOf;==对装箱对象只有在上述缓存范围内才保证同一性,范围外每次new都是不同对象。不要用“值相等”推断引用相等。 - 成本:缓存命中无分配,范围外一次对象分配;JIT/AOT 可能做标量替换/逃逸分析,但这是产物层行为,不能由源码缓存范围推断某 cell 的分配次数(ch09)。
5.1.2 CHM initTable/putVal/helpTransfer/replaceNode 的桶锁分支
initTable(L2312-2332):循环读 table;sizeCtl<0 说明别的线程正在初始化,只 Thread.yield() 自旋;否则 CAS(SIZECTL, sc, -1) 取得初始化权,CAS 失败就重读循环。CAS 只把对象的 SIZECTL 设为 -1,局部 sc 仍是循环开头读到的旧值(非负,0 或构造器容量)。进入 try 后按 sc>0 ? sc : DEFAULT_CAPACITY(16) 分配 new Node[n];若分配抛 OutOfMemoryError,sc = n-(n>>>2) 尚未执行,finally 恢复的是原非负 sc(不是 -1),然后 OOME 原样传播、table 仍为空;成功路径才设置 table 并把 sc 改为 n-(n>>>2)(约 0.75 阈值)。所以“失败重试”只指 CAS/争用,不包含分配 OOME。
putVal(L1022-1092):
- key/value 为 null → NPE;
spread(hashCode())。 - 表为空 →
initTable()。 - 目标桶为空 →
casTabAt(tab,i,null,new Node),空桶无锁;CAS 失败就重读。 - 桶头
hash == MOVED→helpTransfer(tab,f),拿到nextTab后重试。 onlyIfAbsent且首节点 hash/key 命中且 value 非空 → 直接返回旧值,不取桶锁(这是 putIfAbsent 的无锁快路径)。- 否则
synchronized(f),进入后必须再tabAt(tab,i)==f(防止锁前桶已被迁移/替换);fh>=0走链表插入/覆盖,f instanceof TreeBin走putTreeVal,ReservationNode抛IllegalStateException("Recursive update")。 - 锁内若命中已有键:
oldVal != null时按onlyIfAbsent决定覆盖后直接return oldVal,不会走到 addCount;只有链表/树中确实新建节点(新键)才在退出循环后addCount(1L,binCount)更新计数并可能触发/协助 transfer。binCount>=TREEIFY_THRESHOLD(8)的treeifyBin也只在有桶内遍历计数的新键/更新路径上发生。
helpTransfer(L2384-2405):仅当 f 是 ForwardingNode 且 nextTable != null 才协助。先算 rs = resizeStamp(tab.length) << RESIZE_STAMP_SHIFT;循环条件要求 nextTab == nextTable && table == tab && sizeCtl<0(期间表未被提交/替换);若 sizeCtl 已达 rs+MAX_RESIZERS/rs+1 或 transferIndex<=0 就退出;否则 CAS(SIZECTL, sc, sc+1) 成功后调用 transfer(tab,nextTab) 并退出。返回 nextTab 或当前 table,调用方据此重试。
replaceNode(L1124-1195):remove/replace 共用;桶空直接返回 null;MOVED 先 helpTransfer;否则 synchronized(f) + 重新验证 tabAt。链表桶按 cv(条件值)匹配后:有 value 就改 e.val,否则 pred.next=e.next 或把桶头 setTabAt(tab,i,e.next);TreeBin 桶用 findTreeNode,删除后若 removeTreeNode 返回 true 则 setTabAt(tab,i,untreeify(t.first))。validated 只有在确认桶未被替换时才置 true;失败则回到外层循环重试。删除成功才 addCount(-1L,-1)。
computeIfAbsent/compute/merge(L1712-2110)的额外分支:
computeIfAbsent:空桶先放ReservationNode,在其上synchronized后 CAS 占位,调用 mappingFunction;返回 null 时finally把桶设回 null(保持空桶),返回非 null 则替换成普通 Node。首节点已存在且命中时不取锁、不调用函数。链表/树桶在synchronized(f)内计算,插入前若pred.next != null抛IllegalStateException("Recursive update")——这是检测映射函数递归改同一桶。- TreeBin 删除与平衡(L3010-3105):
removeTreeNode先从prev/next链表摘除 p(保持first链),若first==null则root=null并返回 true(调用方会把桶改成 untreeify 链表);若树太小(root/right/left-left 为空)也直接返回 true,不取 root 锁。否则lockRoot(),按 CLR 红黑树删除:有两个孩子时找右子树最小后继 s,交换 s/p 的颜色和位置(结构指针移植,不交换 key/value,因为next链被并发读);单孩子/无孩子直接用 replacement;若 p 为红则无需平衡,否则balanceDeletion(r,replacement)(红兄弟旋转、双黑上推等 CLR 情形);最后摘除 p 的 parent 指针、unlockRoot(),checkInvariants(root)断言后返回 false(仍是树)。调用方replaceNode/compute/merge只有在返回 true 时才setTabAt(tab,i,untreeify(t.first))。 compute:空桶同样用 ReservationNode 占位;已有键的 remappingFunction 返回 null 时删除节点(链表改pred.next/桶头,树走removeTreeNode并在需要时 untreeify),返回非 null 时改值/插入;delta累计 +1/-1,末尾统一addCount(delta,binCount)。merge:空桶不使用 ReservationNode,直接casTabAt(tab,i,null,new Node(h,key,value));已有键用remappingFunction.apply(old,value),null 删除、非 null 更新;树桶同理。三种方法在桶头MOVED时都先helpTransfer,并在桶锁内重新tabAt(tab,i)==f校验。
这些分支的共同正确性约束:桶锁只保护当前桶对象,迁移用 ForwardingNode/helpTransfer 把操作带到新表;链表/树的写必须在 tabAt(tab,i)==f 复核后执行;空桶 CAS、ReservationNode 锁和桶锁是不同路径,不能把 CHM 说成“所有写都加同一个锁”。
5.2 线程池:ctl 状态机与执行路径
ThreadPoolExecutor 用一个 AtomicInteger ctl 打包“运行状态 + worker 数量”(pin):
// java/util/concurrent/ThreadPoolExecutor.java:390-400(libcore@4ebfb391,节选)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
执行路径:execute(Runnable)(L1302)→ 入队或新建 Worker;Worker.run()(L624)→ runWorker(this)(L1079);拒绝走 reject(L796,策略:Abort/CallerRuns/Discard/DiscardOldest)。
- 状态机:RUNNING → SHUTDOWN(
shutdown)→ TIDYING → TERMINATED;RUNNING → STOP(shutdownNow,中断 worker)→ TIDYING → TERMINATED。状态与 worker 计数同字段,无锁 CAS 更新。 - 正常:提交→排队/扩容→执行→worker 复用。
- 失败:饱和且拒绝策略 Abort →
RejectedExecutionException;任务抛出异常 → worker 线程终止并补充(processWorkerExit机制);OutOfMemoryError创建线程失败。 - 回退:拒绝策略是设计选择(如 CallerRuns 降级为调用者执行);线程池不自动改变策略。
- 成本:队列选择、线程创建/唤醒、上下文切换;量化留 ch09。
5.3 AQS 与 LockSupport:等待/唤醒的底层链
AbstractQueuedSynchronizer(本 pin):volatile int state(L538)、volatile Node head/tail(L528/L533)。本版没有acquireQueued方法;acquire(int)(L1028-1030)在tryAcquire失败后调用统一入口acquire(null, arg, false, false, false, 0L)(方法定义从 L705 起),其for (;;)循环(L727 起)负责入队/自旋/停顿,LockSupport.park(this)/parkNanos在 L790/L792,取消路径走cancelAcquire(L746/L796),中断在循环内以Thread.interrupted()+interruptible标志处理(L800-801)。LockSupport:unpark(Thread)(L177)、park(Object blocker)(L217/L370);内部使用jdk.internal.misc.Unsafe(import L40)。park/unpark 的许可语义(先 unpark 会保留一个许可、park 消费)按 LockSupport 约定,本 pin 的 Java 委托入口见 L177/L217。- 链:
ReentrantLock.lock→ AQSacquire→ 失败入队 →LockSupport.park→Unsafe.park→ ARTThread::Park(park_state_状态机 + futex),具体见下方“ART 侧实际 park/unpark”。 - 失败:可中断版本抛
InterruptedException;非可中断版本保留中断状态、继续等待。 - 回退:无回退;虚假唤醒由上层循环条件处理(AQS 自旋/检查)。
- 成本:无竞争时 CAS 快路径;竞争时入队/阻塞/唤醒(系统调用);量化留 ch09。
- Condition 路径(按本版完整方法重写;本版没有
addConditionWaiter/fullyRelease,不得沿用旧 JDK 名): ConditionObjectL1523;OOME_COND_WAIT_DELAY = 10ms(L1534)。await()(L1734-1774)入口先做一次Thread.interrupted()预检查:若进入方法前中断标志已置位,立即清标志并抛InterruptedException(此时尚未enableWait,也不存在“先重获锁”)。预检查通过后才走newConditionNode()(L1663-1680)——正常返回新ConditionNode,OOME 慢路为release(savedState)→U.park(false, 10ms)→acquireOnOOME→ 返回 null(await直接返回);随后enableWait(node)(L1604-1626):校验isHeldExclusively,置COND|WAITING并挂入条件队列,savedState=getState()后release(savedState),失败则CANCELLED + IllegalMonitorStateException。- 等待循环
while (!canReacquire(node))(L1629-1636):若循环期间Thread.interrupted()为真,先尝试getAndUnsetStatus(COND);成功说明 signal 尚未把节点转队,置cancelled=true并 break;失败说明 signal 已清 COND,但可能仍在 enqueue(doSignalL1555 先清 COND、L1556 才enqueue),所以不能据此说“已完成入队”,只记录“signal 后中断”。若 COND 仍置位 →ForkJoinPool.managedBlock(node)(RejectedExecutionException→rejected=true,下一轮改node.block());否则Thread.onSpinWait()——这正是 L1758-1759 注释的“awoke while enqueuing”,canReacquire会重新检查前驱链接/队列入队状态。 - 循环后:
clearCurrentBlocker→node.clearStatus()→reacquire(node, savedState)先重获锁;随后才处理中断:cancelled→unlinkCancelledWaiters(node)并抛InterruptedException;否则Thread.currentThread().interrupt()(signal 已清 COND,保留中断状态)。因此“等待中被中断”不是“未重获锁就抛”:本版先把锁拿回来,再按取消位决定抛异常还是恢复中断。“signal 转队≠马上拥有锁”,被唤醒线程必须重新竞争。 awaitUninterruptibly:同样newConditionNode→enableWait→循环,但只记录中断、reacquire后恢复中断标志,从不抛InterruptedException。- 定时
awaitNanos(L1786-1818):入口同样先Thread.interrupted()预检查;nanosTimeout<0归零后计算deadline=System.nanoTime()+nanos。等待循环只有LockSupport.parkNanos(this, nanos),不走ForkJoinPool.managedBlock;每次循环若中断或deadline-System.nanoTime()<=0,尝试getAndUnsetStatus(COND):成功(signal 尚未转队)→cancelled=true并退出;失败说明 signal 已清 COND,但可能尚在 enqueue,继续由canReacquire重查链接并等待重获锁。reacquire后:cancelled && interrupted→ unlink +InterruptedException;cancelled(纯超时)→ 返回remaining(remaining<=nanosTimeout ? remaining : Long.MIN_VALUE,后者表示溢出饱和);未被取消但interrupted→ 恢复中断标志并返回 remaining。OOME-null 特例直接返回nanosTimeout - OOME_COND_WAIT_DELAY。 awaitUntil(L1831-1857)用System.currentTimeMillis() >= abstime判断超时、LockSupport.parkUntil(this, abstime)等待:入口先Thread.interrupted()预检查(有中断直接抛);循环中若中断或到期,尝试getAndUnsetStatus(COND)成功则cancelled=true并退出。reacquire后分三路:cancelled(signal 前中断或纯超时)→unlinkCancelledWaiters,若interrupted则抛InterruptedException;cancelled且无中断(纯超时)→ 返回false;未 cancelled(signal 胜出)→ 返回true,若期间有中断则恢复中断标志。不是“signal 前中断返回 false”。await(time,unit)(L1874-1902)是独立的 boolean 实现,不委托awaitNanos:同样有入口中断预检查、deadline/parkNanos/取消 COND/reacquire;newConditionNode()OOME-null 时直接返回false(单独一路)。reacquire后:cancelled && interrupted→ unlink 并抛InterruptedException;cancelled且无中断(纯超时)→ 返回false;未 cancelled(signal 胜出)→ 返回true,有中断则恢复标志。三者的共同点是:超时/中断都要先尝试取消 COND,再 reacquire;signal 已清 COND 但可能还在 enqueue,不能把“已清 COND”当作“已入队完成”。signal()(L1573-1579)先isHeldExclusively()校验(否则IllegalMonitorStateException)→doSignal(first,false)(L1546-1563):对每个节点getAndUnsetStatus(COND)成功后enqueue(first)转入同步队列;signalAll同路径 all=true。- 失败/回退:未持锁 →
IllegalMonitorStateException;OOME → 释放锁+睡 10ms+重获(或返回 null);managedBlock被拒绝 →node.block()兜底(不丢唤醒)。成本:条件队列/同步队列两次入队、可能多次 park/unpark 与锁重获。 - park 许可语义(LockSupport 约定,本 pin 源码/文档):
unpark(Thread)(L177-186)对虚拟线程走VirtualThreads.unpark,否则U.unpark;javadoc 明确“先 unpark 会让下一次 park 不阻塞(保留一个许可)”;park返回不报告原因(unpark/中断/虚假唤醒),调用方必须重查条件(L217-230/L370-378)。 - ART 侧实际 park/unpark(不是 OpenJDK Parker):普通线程
LockSupport.park→Unsafe.park(false, 0L)(LockSupport.java:226/376;Unsafe.java:2771为 native)→ ARTjdk_internal_misc_Unsafe.cc:673-676 Unsafe_park→Thread::Park(thread.cc:352-449)。ART 每个Thread有AtomicInteger park_state_(thread.h:2347),取值 0=许可可用、1=无许可、2=无许可且等待者已入 futex:Park先fetch_add(1),旧值为 1 才真正等待;未计时的park用futex(park_state_, FUTEX_WAIT_PRIVATE, 2, nullptr),计时版本分别用FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME(绝对 deadline)或FUTEX_WAIT_PRIVATE+ timespec(相对 nanos);EAGAIN/EINTR被当作合法的虚假返回,ETIMEDOUT置timed_out,其它 errno 直接PLOG(FATAL)。返回前把park_state_存回 1(消费许可)。Unpark(thread.cc:451-466)用exchange(0);若旧值是 2(确有等待者)才FUTEX_WAKE_PRIVATE唤醒 1 个。未启动线程的 unpark 记在Thread.unparkedBeforeStart字段(jdk_internal_misc_Unsafe.cc:687-698),线程启动时再消费(thread.cc:697-710)。条件编译:以上协议位于#if ART_USE_FUTEXES;#else分支(thread.cc:440-447)会#warning "LockSupport.park/unpark implemented as noops without FUTEX support."、UNIMPLEMENTED(WARNING)并sched_yield(),不是同一套阻塞/许可语义。因此这是 ART 自有的 AtomicInteger+futex 许可协议(仅 futex 构建),不存在Parker/parker_cond_对象;把 OpenJDK 的Parker类名套到这里会指错实现,也不能把 futex 行为写成所有构建的既成事实。
5.3.1 线程池相关:FutureTask 与阻塞队列
FutureTask状态机(FutureTask.java:79-99):NEW → COMPLETING → NORMAL/EXCEPTIONAL,以及NEW → CANCELLED/NEW → INTERRUPTING → INTERRUPTED;state为volatile int(L94);run()(L318)执行并设置 outcome,get()(L189)等待完成并抛ExecutionException/CancellationException/InterruptedException。ArrayBlockingQueue(一种代表性有界阻塞队列):final Object[] items(L105)、notEmptyCondition(L126)、enqueue(L180)/dequeue(L195)、put(L365,可阻塞);take对应dequeue路径(dequeueL195)。- 失败/回退:
put在满时阻塞(可中断版本抛InterruptedException);线程池拒绝策略见 §5.2。
5.4 atomic / VarHandle / fence 与 JMM 边界
AtomicInteger 在 Android 的实现已从 Unsafe 改为 VarHandle:
// java/util/concurrent/atomic/AtomicInteger.java:59-66(节选)
* This class intended to be implemented using VarHandles, but there
* ...
// BEGIN Android-changed: Using VarHandle instead of Unsafe
// private static final Unsafe U = Unsafe.getUnsafe();
private static final VarHandle VALUE;
- 访问语义(get/set/volatile/opaque/acquire/release/compareAndSet 等)由
VarHandle访问模式定义;本 pin 的访问器是public final native @IntrinsicCandidate ... (Object... args)(如getVolatileL554-557、setVolatileL584-587、compareAndSetL745-748),即签名多态 native,不是普通 Java 方法。VarHandle.fullFence()(L2123)内部落到UNSAFE.fullFence()(L2086);Unsafe.fullFence/loadFence/storeFence为 native(sun.misc.Unsafe.java:828/844/857)。 - ART intrinsic 列表:
runtime/intrinsics_list.h:55-85列出VarHandleCompareAndSet/CompareAndExchange/Get/Set/...;runtime/intrinsics_list.h:121-123列出VarHandleFullFence/AcquireFence/ReleaseFence。这意味着编译器可以选择 intrinsic;是否在某个冻结调用点真正展开仍是产物问题,不能只看列表。 - RV64 实际入口(已核):CAS 类由
intrinsics_riscv64.cc:4236 GenerateVarHandleCompareAndSetOrExchange生成;核心循环在:1276 GenerateCompareAndSet:GetLrScAqRl(order)(L1085)把内存序映射为lr.w/lr.d与sc.w/sc.d的 aq/rl 位;强 CAS 在sc失败时回到lr重试,弱 CAS 只尝试一次;引用类型 CAS 在 GC 读屏障下走ReadBarrierCasSlowPathRISCV64(L1354 起),并对新引用执行MaybeMarkGCCard(L4266-4274)。get/set 的 acquire/release/volatile 分别用GenerateMemoryBarrier(kLoadAny/kStoreStore/kAnyAny...)或带 aq/rl 的加载/存储(get L3940-4055,set L4060-4105)。这是 RISC-V 后端的具体生成路径,不是“Java 语义自动等于某条指令”。 - AArch64 对照(本批补核):ART 的 AArch64 后端(
compiler/optimizing/intrinsics_arm64.cc:1458-1545)在无 LSE 时用ldxr/stxr循环,有 LSE 时用cas/casal/cash/...指令;强/弱 CAS 的循环/单次尝试语义与 RV64 相同,VarHandle CAS 入口在 L5825-5835,引用类型同样有读屏障慢路径。因此 JMM 语义一致,指令序列由架构/CPU 特性选择;不能把 RV64 的lr/sc或 AArch64 的cas直接套到另一个平台,也不能从源码判断某个设备是否启用 LSE。 - JMM 边界的准确表述:Java 语义(happens-before、volatile 可见性、CAS 原子性)由规范定义;上条给出的是 ART/RV64 与 AArch64 两种实现选择。具体产物是否 intrinsify、是否内联仍需机器码依据。
- 失败/回退:原子操作无“失败回退”;CAS 返回 false 是正常语义,由上层重试;
VarHandle调用点若不是 intrinsic,则走其 native/链接路径,语义不变、成本不同。 - 成本:无竞争 CAS 是一次
lr/sc循环,竞争时sc失败重试并可能触发缓存行迁移;具体量级需 PMU(ch09)。
5.5 正常/失败/回退总表(本章)
| 机制 | 正常 | 失败 | 回退 |
|---|---|---|---|
| 容器读写 | 约定内操作 | 越界/CME/OOME | 无(改用并发容器=设计决策) |
| 线程池 | 提交→执行 | 拒绝异常/任务异常/OOME | 拒绝策略(CallerRuns 等) |
| AQS/park | 获取→等待→唤醒 | 中断(可中断版本) | 无(上层循环处理虚假唤醒) |
| atomic/VarHandle | CAS/访问模式语义 | 无(false=正常) | 无(上层重试) |
5.6 可复制观测
已执行:
PIN=4ebfb391ee0109d16abe6e2bc965724b940928c5
ART=b753cf97923c3695338d21466fa14c57b480a59a
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/util/concurrent/ConcurrentHashMap.java | sed -n '1020,1130p;1120,1195p;2310,2340p;2380,2410p;2445,2575p;2790,2945p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/util/concurrent/locks/AbstractQueuedSynchronizer.java | sed -n '1540,1645p;1730,1910p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/util/concurrent/locks/LockSupport.java | sed -n '170,235p;365,465p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/jdk/internal/misc/Unsafe.java | sed -n '2740,2775p'
git -C /tmp/art-commits.git show $ART:runtime/native/jdk_internal_misc_Unsafe.cc | sed -n '670,700p'
git -C /tmp/art-commits.git show $ART:runtime/thread.cc | sed -n '345,470p'
git -C /tmp/art-commits.git show $ART:compiler/optimizing/intrinsics_riscv64.cc | sed -n '1270,1360p;4230,4300p;4480,4510p'
未执行:并发压力测试、锁竞争 PMU、park/unpark 系统调用观测(需运行/设备);冻结产物是否把某个 VarHandle 调用点 intrinsify(需机器码/产物)。
5.7 缺口与下一步
- AArch64 对照:RV64 的
lr/sc与 AArch64 的ldxr/stxr/LSEcas已对照;两者语义一致、指令不同;仍未在设备上确认 CPU 特性/产物选择。 - AQS 定时/中断运行验证:源码分支已按完整方法核读(预检查、等待中取消、reacquire 后抛/恢复);真实中断时序与
managedBlock拒绝路径仍需要运行用例,源码不能替代调度结果。 - AQS/CHM 其余细节:
ConditionObject的awaitNanos/awaitUntil/await(time,unit)已分段;CHM 的initTable/putVal/helpTransfer/replaceNode桶锁分支与 TreeBin 拆分已展开。compute/computeIfAbsent/merge的桶锁/ReservationNode 分支与 TreeBin 删除/balanceDeletion已展开;仍未做并发运行压力测试与设备 PMU。
第 6 章:IO / NIO / 网络——调用栈、BlockGuard 与失败传播
- 状态:初稿(源码确证;未运行)。
- 来源:[S01][S02];跨仓代表链按 [S03][S04]。
- Pin:
platform/libcore@4ebfb391;bionic 边界按通用 syscall 语义描述(未跨仓核 bionic 实现)。 - 本章回答:
java.io文件栈如何落到 native,IoBridge如何处理 EOF/EAGAIN/短写,StrictMode 的 BlockGuard 在哪里挂钩,NIO/Selector 与 DNS 的边界在哪,异常如何从 errno 映射到 Java。
6.1 java.io 文件栈:FileInputStream → IoBridge → native
// ojluni/src/main/java/java/io/FileInputStream.java:176-180(libcore@4ebfb391)
// BEGIN Android-changed: Open files using IoBridge to share BlockGuard & StrictMode logic.
...
fd = IoBridge.open(name, O_RDONLY);
// END Android-changed: Open files using IoBridge to share BlockGuard & StrictMode logic.
- 打开:路径构造
FileInputStream(File)实际走IoBridge.open(name, O_RDONLY)(L179);open0及其包装open整体位于 Android-changed 注释块(L260-278),不是 Android 活入口,不再描述为“两条入口并存”。已有 fd 的构造要分两层:公共FileInputStream(FileDescriptor)(L230-233)委托this(fdObj, false /* isFdOwner */),因此默认不拥有、close()不关闭该 fd;Android 内部隐藏构造FileInputStream(FileDescriptor, boolean isFdOwner)(L238-256)可传true/false表示所有权转移与否;close()仅在isFdOwner == true时IoBridge.closeAndSignalBlockedThreads(fd)(L577-579),IoUtils.setFdOwner也只在 true 时执行(L253-255)。“不关闭调用者 fd”只适用于 false 路径,不能覆盖整个隐藏构造。 - 读:
IoBridge.read(fd, bytes, off, count)(L598-618)→Libcore.os.read(...)(Os接口,Linux.javanative 实现)→ bionicread(2)。 - 关闭:
IoBridge.closeAndSignalBlockedThreads(fd)(L296)——关闭并唤醒被阻塞线程(避免 fd 复用竞态)。
6.2 IoBridge 的失败语义(可复述)
// luni/src/main/java/libcore/io/IoBridge.java:598-618(节选)
public static int read(...) throws IOException {
ArrayUtils.throwsIfOutOfBounds(bytes.length, byteOffset, byteCount);
if (byteCount == 0) return 0;
try {
int readCount = Libcore.os.read(fd, bytes, byteOffset, byteCount);
if (readCount == 0) {
return -1; // EOF
}
return readCount;
} catch (ErrnoException errnoException) {
if (errnoException.errno == EAGAIN) {
// We return 0 rather than throw if we try to read from an empty non-blocking pipe.
return 0;
}
throw errnoException.rethrowAsIOException();
}
}
- 正常:返回读到的字节数;
0字节读请求直接返回 0。 - 失败映射:native 返回 0 → Java 层
-1(EOF);EAGAIN→ 返回 0(非阻塞空管道语义);其它 errno →ErrnoException.rethrowAsIOException()(android.system.ErrnoException,luni/src/main/java/android/system/ErrnoException.java)。 - 短写:
IoBridge.write在本仓就是循环写全——while (byteCount > 0) { int bytesWritten = Libcore.os.write(...); byteCount -= bytesWritten; byteOffset += bytesWritten; }(L645-649);错误经ErrnoException.rethrowAsIOException()。 - sendto/recvfrom 精确门(已核):
sendto(byte[])L679-691、sendto(ByteBuffer)L694-706;失败转maybeThrowAfterSendtoL708-722(datagramECONNREFUSED→PortUnreachableException;streamEAGAIN→返回 0)。recvfrom(byte[])L749-759、recvfrom(ByteBuffer)L762-772;成功路径postRecvfromL774-788(isRead && byteCount==0→-1);失败路径maybeThrowAfterRecvfromL790-808(readEAGAIN→0;connectedECONNREFUSED→PortUnreachableException;非 readEAGAIN→SocketTimeoutException)。 - 回退/EINTR(已核到包装函数):
Linux_readBytes(libcore_io_Linux.cpp:2158-2163)与Linux_writeBytes(L2849-2854)都调用IO_FAILURE_RETRY。宏IO_FAILURE_RETRY_IMPL(L203-243):每次 syscall 用AsynchronousCloseMonitor监视 fd;若期间被另一线程close()(_wasSignaled)→should_throw时抛InterruptedIOException("... interrupted by close() on another thread"),否则置EBADF;普通EINTR且未 signaled → 重试;其它 errno →throwErrnoException。另有NET_FAILURE_RETRY宏(L160-187):异步关闭时抛SocketException("Socket closed"),是 socket 分支而非文件 IO。注意:以上是静态源码语义,未做真实信号/线程中断实测。
6.2.1 errno → Java 异常的完整分派(按 API 分开)
ErrnoException(android.system.ErrnoException)只保存 functionName/errno;rethrowAsIOException() 统一 new IOException(getMessage()) 并 initCause(this),rethrowAsSocketException() 统一 SocketException。具体 API 会先按 errno 做更细的映射,不能把“errno 一律 IOException”当成全部:
| 入口 | 特殊 errno 映射 | 兜底 |
|---|---|---|
IoBridge.open(L557-577) | 读目录被 fstat 判为 S_ISDIR 后构造 ErrnoException;任何失败都包成 FileNotFoundException(path + ": " + errno消息) | FileNotFoundException |
IoBridge.connect(L177-200) | EHOSTUNREACH/EADDRNOTAVAIL → NoRouteToHostException;其余 → ConnectException;已捕获的 SocketException/SocketTimeoutException 原样抛 | ConnectException/SocketException |
IoBridge.bind(L120-150) | EADDRINUSE/EADDRNOTAVAIL/EPERM/EACCES → BindException | SocketException |
IoBridge.read(L598-617) | 0 字节请求→0;syscall 返回 0→-1(EOF);EAGAIN→0(非阻塞空管道) | IOException |
IoBridge.write(L639-655) | 循环写全;任一 ErrnoException→IOException,不返回短写 | IOException |
IoBridge.closeAndSignalBlockedThreads(L296-317) | 先 fd.release$()、AsynchronousCloseMonitor.signalBlockedThreads(fd) 再 close;close 失败→IOException | IOException |
IoBridge.isConnected(L319-360) | poll(POLLOUT) 返回 0→false(超时);getsockopt(SO_ERROR) 非 0→按 errno;fd 已关闭→SocketException("Socket closed");ETIMEDOUT→SocketTimeoutException | ConnectException |
IoBridge.sendto(L679-718) | datagram ECONNREFUSED→PortUnreachableException;stream EAGAIN→0(非阻塞无写入);其余→IOException | IOException |
IoBridge.recvfrom(L749-810) | isRead 读路径 EAGAIN→0,其余→SocketException;非 read 路径 connected+ECONNREFUSED→PortUnreachableException,EAGAIN→SocketTimeoutException | SocketException |
DNS(Inet6AddressImpl.lookupHostByName) | GaiException cause 的 EACCES/EPERM→SecurityException("Permission denied (missing INTERNET permission?)") | UnknownHostException(含 gai_strerror) |
native 侧 Linux_readBytes/writeBytes 用 IO_FAILURE_RETRY:普通 EINTR 重试;被另一线程 close() 触发 AsynchronousCloseMonitor 时,文件 IO 抛 InterruptedIOException("... interrupted by close() on another thread")(should_throw)或置 EBADF;socket 走 NET_FAILURE_RETRY 抛 SocketException("Socket closed")。因此“EINTR 不暴露给 Java”是这两层包装的结果,不是内核不返回 EINTR。
6.3 BlockGuard / StrictMode 挂钩点
BlockGuardOs(luni/src/main/java/libcore/io/BlockGuardOs.java)在系统调用包装层插入策略回调:
- 网络:
BlockGuard.getThreadPolicy().onNetwork()(L64/L104/L157/L168) - 磁盘读:
onReadFromDisk()(L74) - 磁盘写:
onWriteToDisk()(L81/L88/L174)
因此 FileInputStream 注释中的“share BlockGuard & StrictMode logic”有明确落点:策略检查在 Os 包装层,不在每个 Java 流类里。失败模式:BlockGuard 只回调策略(dalvik/system/BlockGuard.java:54-79;BlockGuardOs 在 read/write/network 入口调用,如 L74/81),策略可抛 NetworkOnMainThreadException;磁盘写策略的具体异常类型由 StrictMode 框架实现决定,本章不替框架补类型。
6.4 NIO:默认同步 Selector(Poll)与异步 ChannelGroup(epoll)分开
- 默认选择是 Poll(真实行号,按完整文件核):
DefaultSelectorProvider.create()在 L65;Android-note从 L66 起解释为什么总是 Poll;Linux→EPollSelectorProvider的分支位于注释块(L149-156,return createProvider("sun.nio.ch.EPollSelectorProvider")在 L155);实际return new sun.nio.ch.PollSelectorProvider()在 L157;Android-removed: Dead code: We always use PollSelectorProvider在 L43。因此普通Selector.open()走同步 PollSelectorImpl:doSelectL79 →pollWrapper.poll(totalChannels, 0, timeout)L87;wakeup 管道清位 L93-96;wakeup()L116 →pollWrapper.interrupt()L119;注册/注销在AbstractPollSelectorImpl.implRegisterL140 /implDeregL165。底层为poll(2)。 - EPollPort 是异步 ChannelGroup 的实现:文件头 L36 明确 “AsynchronousChannelGroup implementation based on the Linux epoll facility”;
MAX_EPOLL_EVENTS=512(L43)、epollCreate()(L89)、socketpair(sv)唤醒(L94)、事件队列(L108)。它不接在普通Selector.select之下;“树中存在 EPoll*”与“默认选择”是两件事。 - 失败:
ClosedSelectorException、CancelledKeyException(约定);底层 poll/epoll 错误传播未运行验证。
6.5 DNS 与网络地址:Java 缓存仍在,resolver 走 Libcore.os
InetAddress.getAllByName(L1148-1152)→impl.lookupAllHostAddr(host, NETID_UNSET);实现类Inet6AddressImpl(本 pin)仍保有 Java AddressCache:private static final AddressCache addressCache(L79)。lookupAllHostAddr(L90-103):空 host → IPv4/IPv6 loopback;先走数字地址快路径(InetAddressUtils.parseNumericAddressNoThrow...);否则lookupHostByName。lookupHostByName(L113-156):BlockGuard.getThreadPolicy().onNetwork()→addressCache.get(host, netId)(正/负缓存)→Libcore.os.android_getaddrinfo(host, hints, netId)→ 填 hostName/originalHostName →addressCache.put(...)。GaiException处理:若 cause 是ErrnoException且 errno 为EACCES/EPERM→ 抛SecurityException("Permission denied (missing INTERNET permission?)");否则putUnknownHost(...)并抛UnknownHostException(含gai_strerror详情)。AddressCache(luni/src/main/java/java/net/AddressCache.java,Android 特有位置):MAX_ENTRIES=16、TTL_NANOS=2s、AddressCacheKey=(hostname, netId)、正/负 entry、System.nanoTime()到期、clear()全清。缓存是 Java 层,没有随旧 RI 块整块搬到 native。- 边界:最终解析由
Libcore.os.android_getaddrinfo(Linux.java:56native 声明)落到 bionicgetaddrinfo;不要再从Linuxnative 声明反推“ART 承担 DNS 解析”。另:InetAddress还有由 native 调用的构造入口(L1049-1051,供 native 侧创建实例),与解析实现是两件事。
6.6 正常/失败/回退(本章)
| 机制 | 正常 | 失败 | 回退 |
|---|---|---|---|
| 文件读 | 返回字节数 | EOF=-1;errno→IOException | 无自动回退 |
| 非阻塞读 | 返回数据 | EAGAIN→0 | 调用方重试(设计) |
| 文件写 | 写全或失败 | errno→IOException | 无(不静默短写) |
| StrictMode | 策略放行 | 策略异常(如主线程网络) | 关闭策略=配置决策 |
| DNS | 解析成功 | UnknownHost/GaiException | 无(重试由调用方决定) |
6.7 可复制观测
已执行:
PIN=4ebfb391ee0109d16abe6e2bc965724b940928c5
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/io/FileInputStream.java | sed -n '170,190p;255,280p'
git -C sources/libcore.git show $PIN:luni/src/main/java/libcore/io/IoBridge.java | sed -n '110,210p;290,320p;555,660p'
git -C sources/libcore.git show $PIN:luni/src/main/java/android/system/ErrnoException.java | sed -n '35,95p'
git -C sources/libcore.git show $PIN:luni/src/main/java/libcore/io/BlockGuardOs.java | sed -n '55,110p;150,180p'
git -C sources/libcore.git ls-tree -r --name-only $PIN | grep -E "sun/nio/ch/(EPoll|SelectorImpl)|native/EPoll"
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/net/InetAddress.java | sed -n '745,760p;1015,1060p'
git -C sources/libcore.git show $PIN:luni/src/main/native/libcore_io_Linux.cpp | sed -n '150,245p;2150,2168p;2840,2860p'
git -C sources/libcore.git show $PIN:luni/src/main/java/java/net/AddressCache.java | sed -n '1,140p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/net/Inet6AddressImpl.java | sed -n '70,160p'
未执行:真实文件/管道/非阻塞 socket 的 EOF/EAGAIN 行为验证、StrictMode 策略触发、DNS 解析与缓存观察、epoll 事件循环观测(均需运行环境/设备)。
6.8 缺口与下一步
- EINTR/重试语义:read/write 包装、
IO_FAILURE_RETRY/NET_FAILURE_RETRY与按 API 的 errno→异常分派已按源码核(见 §6.2/§6.2.1);仍未做真实信号/异步 close 的运行验证,其它 syscall 包装未逐行。 - DNS native 实现:Java 侧
AddressCache(16 项/2s/netId)与Libcore.os.android_getaddrinfo入口已核;最终 bionicgetaddrinfo实现未跨仓核,不要把 Java 缓存当成 native 解析逻辑。 - epoll 细节:
EPollPort作为异步 ChannelGroup 的创建/唤醒/事件队列已定位;fd 注册/注销与事件分发未逐行展开。
- 成本与权衡:文件 IO 是 Java 状态机 + JNI + syscall;Selector 默认 Poll 每次 select 走
poll(2),EPollPort只用于异步 ChannelGroup;BlockGuard 策略检查有运行时开销,不能在每个流类重复。
第 7 章:数学、数值转换、时间与 Unicode/ICU 边界
- 状态:初稿(源码确证;未运行)。
- 来源:[S01][S02];跨仓代表链按 [S03][S04]。
- Pin:
platform/libcore@4ebfb391。 - 本章回答:数值库的实现分层(native vs Java vs intrinsic 候选)、
BigInteger/BigDecimal的数据结构、java.time的字段模型、以及 Locale/Unicode/时区数据为什么必须按“跨仓边界”描述。
7.1 Math/StrictMath:native 与 Java 的分层
java.lang.Math(pin)中三角/指数/对数/开方等为 native 方法:
// ojluni/src/main/java/java/lang/Math.java:207,231,255,279,303,330,389,420,454,490(libcore@4ebfb391)
public static native double sin(double a);
public static native double cos(double a);
...
public static native double sqrt(double a);
- 这些 native 声明在本仓的实现在
ojluni/src/main/native/Math.c与StrictMath.c(文件存在;本轮未逐函数核对算法/精度与注册细节)。是否被调用点 intrinsic 化属编译器机制,需产物依据(ch01/ch02 的边界)。 abs/min/max等简单函数为 Java 实现,且可能被编译器 intrinsic 化(“源码具备实现”≠“冻结产物已 intrinsify”,同 ch01 边界;ART 侧存在 intrinsic 机制,本章不逐条取证)。- 失败/边界:NaN/无穷/符号零遵循 Java 规范;具体 native 精度是否与
StrictMath逐位一致需按实现核对(未核)——不要把“同结果”当先验。
7.2 数值转换:Integer/Long/Double 的 parse/toString
Integer.toString(int, int radix)(Integer.java:153)、toString(int)(L503);parseInt(String,int)(L730)、parseInt(String)(L894);valueOf(int)(L1192,带缓存)。- 两类 API 的失败语义不同(逐 API 核):
toString(int, radix)在 radix 越界时回退为 10(Integer.java:153-155:if (radix < MIN_RADIX || radix > MAX_RADIX) radix = 10;),不抛异常;parseInt(String,radix)才在非法输入/非法 radix 时抛NumberFormatException。不要把两者的失败行为混写。 - 具体小输入反例(预期,未运行):
Integer.toString(255, 1)→ 按十进制"255";Integer.parseInt("255", 1)→NumberFormatException。未运行,不写“实测”。 - Double/Float 的真实 API 链(固定 pin,逐文件核):
Double.toString(double)(Double.java:320-321)→FloatingDecimal.toJavaFormatString(d)(FloatingDecimal.java:69-70)→getBinaryToASCIIConverter(d).toJavaFormatString();特殊值走ExceptionalBinaryToASCIIBuffer(L189-199,NaN/Infinity 字符串)。Double.parseDouble(String)(Double.java:659-660)→FloatingDecimal.parseDouble(s)(L109 起)。Float.toString(float)(Float.java:305-306)→FloatToDecimal.toString(f)(FloatToDecimal.java:129)——Float 用独立的 FloatToDecimal,不是 DoubleToDecimal;转换/舍入分支在toChars(L220/303/318/325,其中 L325 含 round-to-even 条件)。Float.parseFloat(String)(Float.java:555-556)→FloatingDecimal.parseFloat(s)。jdk/internal/math/DoubleToDecimal.java虽然存在(MAX_CHARSL111、toCharsL371),但当前Double.toString的活入口是 FloatingDecimal;在找到其调用者前,不能把它当作 Double.toString 的默认路径(单独登记)。- 格式化算法(
FloatingDecimal有限值):getBinaryToASCIIConverter(d)(L1749-1790)对 NaN/Inf 返回ExceptionalBinaryToASCIIBuffer(NaN/Infinity/-Infinity固定字符串,L189-236):NaN 分支不传isNegative,统一返回B2AC_NOT_A_NUMBER(字符串NaN);只有 Infinity 按符号选-Infinity/Infinity。有限值返回BinaryToASCIIBuffer。dtoa(L424-788)把二进制尾数/指数缩放成FDBigInteger(精确大整数),按“候选值 B 与十进制值 D 的差是否落在半个 ulp 内”迭代生成数字;停止条件为low/high同时成立或越过边界。若恰好落在半 ulp 上,比较最后一位的奇偶:偶数保持不变、奇数才roundup()(L764-778),即 ties-to-even;roundup()处理 9→0 进位并可能抬高十进制指数(L788-810)。输出格式由getChars(L905-970)决定:0 < decExponent < 8打digits.digits,-3 < decExponent <= 0打0.00digits,其余打d.dddE±n,且小数点后至少保留一位。 - 解析算法(
readJavaFormatStringL1830-2052):先trim();空串 →NumberFormatException("empty String");识别可选+/-;NaN只接受精确字面量并返回A2BC_NOT_A_NUMBER,+NaN 与 -NaN 不分符号(L1854-1858);Infinity只接受精确字面量并按isNegative返回A2BC_NEGATIVE_INFINITY/A2BC_POSITIVE_INFINITY(L1859-1862);因此“NaN/Infinity 都保留负号”是错误说法,只有 Infinity 分符号,NaN 统一为NaN。0x/0X前缀转parseHexString(L1866-1871,十六进制浮点按 C99 语法与p指数);十进制路径逐位收集有效数字、小数点和指数,允许结尾f/F/d/D;有任何多余字符 →NumberFormatException("For input string: ...")(L2038-2052)。全零输入按符号返回A2BC_POSITIVE_ZERO/A2BC_NEGATIVE_ZERO,因此"-0.0"的符号得以保留。 - 解析到二进制(
ASCIIToBinaryBuffer.doubleValue()L1034-1360):短数字走单次乘除的精确快路径;超出范围时用FDBigInteger做“候选 IEEE 位型 ± 半个 ulp”的精确比较,必要时nextUp/nextDown修正,直到差值小于半 ulp;恰好半 ulp 时按最低位偶数舍入(L1310-1360)。上溢返回有符号无穷,下溢/舍入到零返回有符号零(L1140-1230)。floatValue()单独实现(L1404 起),避免“先转 double 再转 float”的双重舍入;这正是Float.parseFloat与Double.parseDouble共用readJavaFormatString但返回不同转换器的原因。 - Float 格式化(
FloatToDecimal):Float.toString→FloatToDecimal.toString(f)(L129-131)→toDecimal(v)(L190-240):先按位拆出指数/尾数,识别PLUS_ZERO/MINUS_ZERO/PLUS_INF/MINUS_INF/NAN并返回固定字符串;有限值用整数幂表与rop计算十进制候选,toDecimal在u/w两个候选中选择最近者,平局时用cmp == 0 && (s & 1) == 0选偶数(L300-330)。它是独立实现,不复用FloatingDecimal的 double 路径。 DoubleToDecimal的状态:该类在本 pin 存在(MAX_CHARSL111、toCharsL371),但Double.toString的活调用链是FloatingDecimal.toJavaFormatString;在找到DoubleToDecimal的调用者前,不能把它写成默认路径。FloatToDecimal则是Float.toString的活路径(与DoubleToDecimal无关)。- 失败:非法输入 →
NumberFormatException;NaN/Infinity 格式化走 exceptional buffer;指数/舍入边界已按上述分支核到源码,但仍未运行对拍。 - 成本:短字符串解析/格式化是高频路径;解析要处理任意长十进制串(
FDBigInteger精确比较),格式化要做精确十进制/二进制边界迭代;基准 cell(数值转换类)映射见 ch09。
7.2.1 格式化框架:Formatter、SimpleDateFormat 与 Locale
Formatter(java.util):
String.format(Locale.FRANCE, "e=%+10.4f", Math.E)
→ Formatter.format(Locale, String, Object...)(Formatter.java:2676-2716)
→ parse(format)(L2722-2745,Android 改为手写 FormatSpecifierParser,非正则)
→ 逐 FormatString 调 fs.print(arg, locale)(L2687-2708)
├─ 数值/日期转换 → FormatSpecifier.print(...)
├─ 日期/时间 → printDateTime(L3137-3172)→ Calendar.getInstance(locale) → print(cal,...)
└─ 数字 locale 数据 → DecimalFormatData.getInstance(LocaleData.mapInvalidAndNullLocales(l))(L2459-2463)
- 参数索引分
-2(固定串/%n/%%)、-1(相对索引)、0(普通递增)、正数(显式索引);缺参抛MissingFormatArgumentException,未知/不匹配转换抛IllegalFormatException子类;每个 specifier 的print可能抛IOException,Formatter 记录到lastException而不是立刻中断。 - 日期/时间分支对
Long/Date用给定 locale 的Calendar(null 时用Locale.US),对Calendar克隆并setLenient(true);数字分支用DecimalFormatData+LocaleData,不是只靠Double.toString。
SimpleDateFormat / DateFormat(java.text):
DateFormat.getDateInstance(style, locale)在本 pin 被 Android 改为直接new SimpleDateFormat(timeStyle, dateStyle, loc);若构造抛MissingResourceException,回退到固定 pattern"M/d/yy h:mm a"(DateFormat.java:860-878)。SimpleDateFormat(timeStyle,dateStyle,locale)用SimpleDateFormatData.getInstance(locale).getDateFormat/getTimeFormat组合 pattern(L645-663);SimpleDateFormat(pattern,locale)校验 null,initializeCalendar,从DateFormatSymbols.getInstanceRef(locale)取符号表,initialize编译 pattern 并缓存NumberFormat.getIntegerInstance(locale)(关闭 grouping)(L700-753)。format(Date,...)(L1083-1110+)按编译后的 pattern tag 逐段输出;月份/星期使用 Android-added 的 ICU 数据方法(L1608-1665),时区名走 ICUTimeZoneNames(L1454-1510、L2056-2118);数字字段用NumberFormat。parse的失败通过ParsePosition/ParseException返回,不保证抛异常。- 失败:非法 pattern 字符 →
IllegalArgumentException("Illegal pattern character ..."),未闭合引号 →IllegalArgumentException("Unterminated quote")(L953/L974);locale 资源缺失在工厂层回退固定 pattern;日期/时区数据版本仍由 ICU/tzdata 决定。
Locale 数据边界:LocaleData.get 按 languageTag 缓存,initLocaleData 用 android.icu.text.DateFormatSymbols 初始化月份/星期/AmPm 等(LocaleData.java:179-235);DecimalFormatData/SimpleDateFormatData 也来自 ICU。因此 Formatter 与 SimpleDateFormat 的“Locale 相关输出”最终依赖 ICU/CLDR 数据,libcore 只做 Java API、pattern 编译与适配;不能从这些源码推断某 Locale 的具体数据版本。
7.3 BigInteger / BigDecimal:结构与算法入口
// java/math/BigInteger.java:140,151,1322,1582(libcore@4ebfb391)
final int signum;
final int[] mag; // 大端 int 数组(magnitude)
public BigInteger add(BigInteger val) { ... }
public BigInteger multiply(BigInteger val) { ... }
// java/math/BigDecimal.java:314,322,1389
private final BigInteger intVal;
private final int scale;
public BigDecimal add(BigDecimal augend) { ... }
BigInteger:符号 + int[] 幅值;add(L1322)、multiply(L1582)、divide(L2354)。multiply精确条件(L1594-1645):①zero shortcut(signum==0 → ZERO);②同对象平方:val==this && xlen > MULTIPLY_SQUARE_THRESHOLD(20,L273) && xlen < 50→square()(L1604-1606);③经典路径:xlen < 50或ylen < 50(一侧短就不进 BN_mul;单 limb 走multiplyByInt,否则multiplyToLen,L1610-1621);④只有 xlen>=50 且 ylen>=50 才进 BoringSSL 路径(L1622-1636):bigEndInts2NewBN(mag)+bigEndInts2NewBN(val.mag)+BN_new+NativeBN.BN_mul+bn2BigEndInts,finally三处BN_free。阈值定义 L1602,BN_mul 调用 L1629。长度单位是mag.length的 32-bit int limb,不是字节。成本结构:两次大数转换 + 一次 JNI/native 乘法 + 结果回转 + 三次释放;BoringSSL 的 comba8/schoolbook 算法族已核,RISC-V 汇编/性能未核,不宣称收益。divide(L2354-2368):BORINGSSL_DIV_THRESHOLD=40、BORINGSSL_DIV_OFFSET=20(L2344-2345)。活条件mag.length < 40 || mag.length - val.mag.length < 20→divideKnuth;否则divideAndRemainder(val)[0]。旧 Burnikel 阈值/调用在注释中(L2355-2358/2363-2365),divideBurnikelZiegler方法体亦被注释(L2484+),不能复活。divideAndRemainder(L2400-2434)条件不同:val.mag.length < 40 || mag.length < 20 || mag.length - val.mag.length < 20→divideAndRemainderKnuth;否则 BoringSSLBN_div路径(xBN/yBN/quotBN/remBN 四个 BN,finally四次BN_free;quotSign 单独处理符号)。remainder(L2457-2472)又是另一组条件:val.mag.length < 40 || mag.length - val.mag.length < 40→remainderKnuth;否则divideAndRemainder(val)[1]。三张阈值表不能互相套用。- NativeBN JNI 桥在本仓:
luni/src/main/native/libcore_math_NativeBN.cpp—NativeBN_BN_mulL143-149(BN_CTX_new/BN_mul/throwException)、NativeBN_BN_divL151-157、JNI 注册 L167-177;Register.cpp:45注册;NativeCode.bp静态依赖libcrypto_static(L75-78/133-135,注释 “for bignums only”)。准确链:JavaNativeBN声明(luni/src/main/java/libcore/math/NativeBN.java:24-37)→ 本仓 JNI 桥 → BoringSSLBN_*(pin0cd1f6a9…)。 - BoringSSL 内部(本批补核):
BN_mul→bn_mul_impl(mul.cc.inc:149-195):零操作数直接BN_zero;两侧width==8时走bn_mul_comba8(:178),其余走bn_mul_normal(:188)——后者是 schoolbook 乘加:序言先单独算b[0]列(rr[0] = bn_mul_words(...),:45),随后稳态for每轮b[1..4]四列(:51/55/59/63,指针+=4),不足 4 个时逐次--nb提前返回;因此不是固定的 5-word 展开因子,也不能把序言计入每轮。本 pin 的注释明确旧的一般 Karatsuba 实现已被删除,只剩 RSA 密钥生成用的部分辅助(:116-121);因此“大数乘法=Karatsuba”在本 pin 不成立。BN_div(div.cc.inc:156-340)注释与代码都表明是 Knuth TAOCP Vol.2 4.3.1 Algorithm D:D1 规范化、D2–D7 逐 word 估商/修正/乘减、D8 反规范化;它假设输入是公开的,不是常数时间,秘密输入应使用 Montgomery 等其它策略(:156-166)。这两点只说明算法族,不宣称具体性能或侧信道结论;RISC-V 上的大数乘法/除法仍由 BoringSSL 的 C/汇编实现决定。 - 小/中规模乘法的内部:一侧短于 50 时,若短边长度为 1 走
multiplyByInt(L1706-1750;若乘数是 2 的幂,直接shiftLeft,否则用long进位并预测结果长度);否则走multiplyToLen(L1802-1830)→@IntrinsicCandidate implMultiplyToLen(L1807-1830)的标准 O(n·m) 竖式:外层每个x[i]、内层每个y[j],用long做 32×32→64 位乘加,carry = product >>> 32。21–49 个 limb 的同对象平方在multiply中转到square()(L1604-1606、L2128-2165):len < KARATSUBA_SQUARE_THRESHOLD(128)→squareToLen(L2170-2180)→@IntrinsicCandidate implSquareToLen(L2202 起);更大的平方进入squareKaratsuba/squareToomCook3,但在当前multiply的活分支里,只有 21–49 limb 会走平方,因为 ≥50 已被 BoringSSL 路径截走。 - Knuth 除法的内部:
divideKnuth(MutableBigInteger L1182-1232)先用b.intLen==0抛除零、compare处理被除数小于/等于除数、b.intLen==1走divideOneWord,再对大于阈值的长数消除公共 2 的幂;一般路径divideMagnitude(L1481-1610)执行 Knuth TAOCP Vol.2 4.3.1 的 D1–D7:D1 左规范除数、D3 估计qhat并用unsignedLongCompare最多修正两次、D4mulsub乘减、D5/D6 借位则divadd加回、D7 循环。它是 O(n·m) 的精确整数算法,不含浮点估计。 BigDecimal:紧凑表示——intCompact(long,L354)在幅值 ≤Long.MAX_VALUE时直接存储;哨兵INFLATED = Long.MIN_VALUE(L342)表示“数位只在intVal”;MAX_COMPACT_DIGITS = 18(L358);scale(L322,可为任意值);add(L1389)做 scale 对齐。只写intVal+scale会漏掉紧凑字段这一真实结构。- BigDecimal 除法边界:
divide(BigDecimal)(L1757-1819)先处理0/0(“Division undefined”)与x/0(“Division by zero”);非零商构造MathContext(UNNECESSARY),精度上界为this.precision() + ceil(10*divisor.precision()/3),再调用divide(divisor, mc);若不可精确终止,捕获ArithmeticException并重抛“Non-terminating decimal expansion; no exact representable decimal result.”。divide(divisor, scale, roundingMode)(RoundingMode重载 L1688-1690 委托 int 版;int 版 L1654-1680)走带 scale 的舍入路径;divide(divisor, mc)(L1819-1870)按intCompact/intVal的四种组合分派到divide(long,long,...)、divide(long,BigInteger,...)等重载。舍入辅助divideAndRound(long,long,int)(L4715 起)和divideAndRound(BigInteger,...)(L4794/4874 起)按RoundingMode决定是否加一,不是简单的截断。 - 失败:除零 →
ArithmeticException;divide非终止小数且未指定 rounding →ArithmeticException;MathContext精度不足且UNNECESSARY→ 同上。 - 成本:小规模乘法 O(n·m)、Knuth 除法 O(n·m);大数(两侧 ≥50 limb)转为 JNI/BoringSSL,成本包含两次
int[]→BN转换、BN_CTX/BN 分配、native 乘除和结果回转;本章不给实测数字。
7.4 java.time 与时间/时区
LocalDate字段:private final int year(L179)、private final short month(L183)、private final short day(L187)——不可变值类型;java.time的其余类(LocalTime/Instant/ZonedDateTime)同属 ojluni 实现(结构按需展开)。- 时区数据不在 libcore 内:
libcore.util.ZoneInfo已把不可变数据委托给com.android.i18n.timezone.ZoneInfoData(ZoneInfo.java:28-46、mDelegateL125)。在本 pin 的 libcore 树中检索不到ZoneInfoData/ZoneInfoDB/tzdb源文件,说明 tzdata 解析与数据模块在 libcore 之外;ZoneInfo只保留TimeZone兼容序列化与mTransitions等字段。tzdata 的具体版本、文件路径、更新流程必须由该外部模块/构建产物核实,不能从 libcore pin 推断。 - ICU 版本是运行时查询,不是源码常量:
libcore.icu.ICU的getIcuVersion/getCldrVersion/getUnicodeVersion是 native(ICU.java:650/655/660);libcore_icu_ICU.cpp:96-114分别调用 ICU 的u_getVersion、ulocdata_getCLDRVersion、u_getUnicodeVersion,再用u_versionToString转字符串。libjavacore在NativeCode.bp:75-80以shared_libs链接libicu;因此设备上返回的版本由实际加载的 ICU 共享库决定,同一份 libcore 源码在不同 build/设备上可以给出不同版本号。 - 时区名称:
libcore.icu.TimeZoneNames本身不持有数据,getFilledZoneStrings调用com.android.icu.text.TimeZoneNamesNative.getFilledZoneStrings(TimeZoneNames.java:55-68),getDisplayNames直接使用android.icu.text.TimeZoneNames(L153-170)。这些android.icu/com.android.icu类不在 libcore 树内,属于 ICU4J/平台 ICU 模块。 - 失败:非法日期/时间 →
DateTimeException;未知时区 →ZoneRulesException(约定);ICU 数据缺失/版本不匹配属于构建/平台问题,libcore 侧没有通用降级依据。 - 边界:
ZoneId.of("...")的可用 ID 集合与规则数据来自 tzdata/ICU 版本,版本必须按外部仓与构建产物核实;本 pin 只能证明“libcore 把请求转交给外部数据模块”,不能证明某时区规则已更新。
7.5 Locale / Unicode / ICU:跨仓职责表
luni/src/main/java/libcore/icu/ 下的桥接类(pin 内):
| 文件 | 职责 | 数据/实现来源 |
|---|---|---|
ICU.java | Locale/ISO 语言国家列表、zygote 缓存初始化(initializeCacheInZygote L73)、localeFromIcuLocaleId(L238)、getAvailableLocales(L344) | ICU native(external/icu,跨仓) |
LocaleData.java | 日期/数字格式的 locale 数据 | ICU |
SimpleDateFormatData.java / DateIntervalFormat.java / DecimalFormatData.java | 格式化数据/行为 | ICU |
TimeZoneNames.java | 时区显示名 | ICU/tzdata |
CollationKeyICU.java | 排序键 | ICU collation |
- Character 的 Android 实现不是统一 CharacterData 表:RI 的
CharacterData.of(...)调用在 pin 中处于 Android-changed 注释块(如isLowerCaseL10178-10182),活代码是 **ASCII/简单范围快路径 +@FastNativenative *Impl**: isLowerCase(int)→isLowerCaseImpl(codePoint)(@FastNative);toLowerCase(int)(L11434-11445):先A–Z加('a'-'A')(L11435-11437);再其余 ASCII(<0x80)原样返回(L11439-11442);最后非 ASCII 走toLowerCaseImpl(L11444,@FastNative)——只写“<0x80 直接返回”会漏掉大写 ASCII 的转换分支;toUpperCase(int)(L11513-11524)次序对称:先a–z减('a'-'A'),再其余 ASCII 原样返回,最后toUpperCaseImpl。- native 注册与实现在
ojluni/src/main/native/Character.cpp:Character_isLowerCaseImpl(L32-34)直接return u_islower(codePoint);,Character_toLowerCaseImpl(L87-89)→u_tolower,Character_toUpperCaseImpl(L92-94)→u_toupper;注册表FAST_NATIVE_METHOD(Character, isLowerCaseImpl, "(I)Z")(L184)、toLowerCaseImpl/toUpperCaseImpl(L192-194),register_java_lang_Character(L197-199)。也就是说 Java 的 ASCII 快路径之后,非 ASCII 分支直接进入 ICU4C 的 Unicode 大小写/属性函数,libcore 不再维护自己的 CharacterData 表。ICU4C 库边界见luni/src/main/native/libcore_icu_ICU.cpp;因此“**活代码通过 native *Impl/ICU4C**(带 ASCII 快路径)”是准确表述;“CharacterData 与 ICU 可能不同步”这一具体模型没有本 pin 依据,撤回。 - Character 其余标量入口(已核):
Character.cpp的isDigitImplL46-49→u_isdigit、isLetterImplL51-54→u_isalpha、getTypeImplL71-74→u_charType、digitImplL101-104→u_digit、toTitleCaseImplL96-99→u_totitle;注册表FAST_NATIVE_METHODL175-194,register_java_lang_CharacterL197-199。Java 侧Character.valueOf(char)的 0..127 缓存见 L9279-9285。未逐项运行对拍的只是这些 ICU4C 标量包装的运行结果,不是调用链缺口。 - 失败/回退:ICU 数据缺失/版本不匹配属构建/平台问题;libcore 侧自动降级机制未核,不臆断。
7.6 正常/失败/回退(本章)
| 机制 | 正常 | 失败 | 回退 |
|---|---|---|---|
| Math 函数 | 返回规范结果 | NaN/Inf 按规范传播 | 无 |
| 解析/格式化 | 返回数值/字符串 | NumberFormatException | 无 |
| Formatter/SimpleDateFormat | specifier/pattern 解析后输出 | IllegalFormatException/IllegalArgumentException/ParseException | DateFormat 工厂对 MissingResourceException 回退 "M/d/yy h:mm a" |
| BigInteger 运算 | 返回新对象 | ArithmeticException(除零等) | 无 |
| java.time | 值类型运算 | DateTimeException | 无 |
| ICU 数据 | 名称/格式化正确 | 数据版本不匹配(平台问题) | 无通用回退(未核) |
7.7 可复制观测
已执行:
PIN=4ebfb391ee0109d16abe6e2bc965724b940928c5
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/lang/Double.java | sed -n '315,325p;655,665p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/jdk/internal/math/FloatingDecimal.java | sed -n '60,125p;185,260p;286,345p;730,815p;1025,1240p;1830,2060p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/jdk/internal/math/FloatToDecimal.java | sed -n '120,345p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/math/BigInteger.java | sed -n '1588,1660p;1700,1840p;2120,2225p;2340,2500p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/math/MutableBigInteger.java | sed -n '1160,1240p;1475,1620p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/math/BigDecimal.java | sed -n '320,365p;1650,1905p;4700,4930p'
git -C sources/libcore.git show $PIN:luni/src/main/java/libcore/util/ZoneInfo.java | sed -n '25,130p'
git -C sources/libcore.git show $PIN:luni/src/main/java/libcore/icu/ICU.java | sed -n '640,670p'
git -C sources/libcore.git show $PIN:luni/src/main/native/libcore_icu_ICU.cpp | sed -n '78,125p'
git -C sources/libcore.git show $PIN:NativeCode.bp | sed -n '55,90p'
git -C /tmp/boringssl.git show 0cd1f6a94b670e6b61af95a06e71e690ae8e3262:src/crypto/fipsmodule/bn/mul.cc.inc | sed -n '28,65p;145,200p'
git -C /tmp/boringssl.git show 0cd1f6a94b670e6b61af95a06e71e690ae8e3262:src/crypto/fipsmodule/bn/div.cc.inc | sed -n '150,240p;316,345p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/util/Formatter.java | sed -n '2670,2720p;3130,3180p;2450,2470p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/text/SimpleDateFormat.java | sed -n '620,760p;1080,1110p;1600,1670p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/text/DateFormat.java | sed -n '855,880p'
git -C sources/libcore.git show $PIN:luni/src/main/java/libcore/icu/LocaleData.java | sed -n '175,240p'
未执行:Math 函数精度/边界值对拍、Double/Float 解析/格式化对拍(含 NaN/Inf/±0/次正规/上溢下溢)、BigInteger/BigDecimal 大数对拍、时区/格式化数据版本核对(需运行与外部仓/产物)。
7.8 缺口与下一步
- Double/Float 对拍:格式化/解析算法与 ties-to-even 分支已按源码核;仍需用规范边界集运行对拍,尤其次正规、
-0.0、十六进制浮点、超长数字。 - BigInteger/BoringSSL 内部:Java 侧
multiplyToLen/squareToLen/divideKnuth/divideMagnitude已核;BoringSSLBN_mul(comba8/schoolbook,无一般 Karatsuba)与BN_div(Knuth Algorithm D,非常数时间)已核到算法族。仍未核的是 BoringSSL 在 RISC-V 上的具体汇编/分派与性能,不宣称收益。 - Formatter/SimpleDateFormat/Locale:
Formatter手写 specifier 解析与日期/数字分派、SimpleDateFormatpattern 编译/ICU 月份星期/时区名/LocaleData 数据源已按源码核;仍未做 Locale 矩阵与错误输入的运行对拍,也没有绑定 ICU/CLDR/tzdata 的具体版本。 - ICU/tzdata 版本:
u_getVersion/u_getUnicodeVersion/ulocdata_getCLDRVersion是运行时查询;tzdata 的ZoneInfoData模块不在 libcore pin 内,具体版本需按外部模块与构建产物独立取证。
- 验证条件:数值/格式化需对同一输入做 Java 结果与 ICU/CLDR 输出对拍;时区规则按外部 tzdata 版本验证;Character 标量 API 需对 ASCII 与 BMP/补充平面样本对拍。
第 8 章:反射、MethodHandle、序列化与加密边界
- 状态:初稿(源码确证;未运行)。
- 来源:[S01][S02];跨仓代表链按 [S03][S04]。
- Pin:
platform/libcore@4ebfb391;ART 边界参考platform/art@b753cf97;Conscryptfd1a4303…;BoringSSL0cd1f6a9…(各仓独立,不比较 commit 先后)。 - 本章回答:反射与
MethodHandle的 Java/native 分工、ART 侧真实的访问检查与调用分派、序列化对象图/句柄/替换/过滤边界,以及javax.crypto/java.security与 Conscrypt/BoringSSL 的代表链——不把“加密实现”写成都在 libcore。
8.1 反射:Java 面 + ART 访问检查
libcore 的反射不是纯 Java 实现——关键方法声明为 native(java.lang.reflect.Method,pin):
// ojluni/src/main/java/java/lang/reflect/Method.java:416-418(libcore@4ebfb391)
// Android-changed: invoke(Object, Object...) implemented natively.
public native Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
8.1.1 AccessibleObject 的活代码与注释块
- 活代码:
setAccessible(boolean)(L128-132)只调用setAccessible0(this, flag);批量版本(L93-101)逐个调用;setAccessible0(L137-160)在flag==true时禁止把Class、Method、Field自身的构造器设为可访问(抛SecurityException),其余情况只写obj.override = flag。isAccessible()(L169-171)返回override。SecurityManager 调用在 L95-97、L129-131 已被 Android-removed。 - 注释块不是入口:
checkAccess/slowCheckMemberAccess/securityCheckCache整块位于// BEGIN Android-removed: Shared access checking logic: Not used on Android.内(L261-329);L280 的checkAccess只是 RI 文本。不能把它与MethodHandles.Lookup.checkAccess拼成一条链。 - 真正的检查在 ART:
Method.invoke、Field.get/set、Constructor.newInstance0进入 ART 后才执行下面的检查。
8.1.2 Method.invoke 的 ART 链
Method.invoke (libcore native, Method.java:418)
→ ART Method_invoke (runtime/native/java_lang_reflect_Method.cc:75-78)
→ InvokeMethod<kRuntimePointerSize> (runtime/reflection.cc:698-778)
InvokeMethod 的顺序(reflection.cc:698-778):
- 栈/初始化:检查 native 栈余量,不足抛
StackOverflowError;确保声明类已初始化,失败则保留初始化异常。 - String 构造器特例:
String.<init>替换为StringFactory调用(m = StringInitToStringFactory(m),receiver 必须为 null)。 - receiver 类型:非静态方法先
VerifyObjectIsClass(receiver, declaring_class),不匹配抛IllegalArgumentException(“Expected receiver of type ...”);静态方法 receiver 可为 null。 - 虚分派:
receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(m, kPointerSize),得到实际实现。 - 参数个数:
CheckArgsForInvokeMethod用方法签名的参数个数与Object[]长度比较,不等抛IllegalArgumentException(“Wrong number of arguments; expected N, got M”)。 - 访问检查:若
executable->IsAccessible()为 false,调用VerifyAccess;失败抛IllegalAccessException(消息带 caller、访问标志、方法名和声明类)。 - 参数解包/调用:
ArgArray::BuildArgArrayFromObjectArray做拆箱/加宽/引用检查,失败抛IllegalArgumentException;InvokeWithArgArray调ArtMethod::Invoke。 - 异常包装:目标方法抛出的任何异常被包成
InvocationTargetException(ActiveTransaction 的事务中止异常除外);创建包装异常本身失败则保留该异常。 - 返回值:primitive 通过
BoxPrimitive装箱,引用直接返回。
VerifyAccess 的规则(reflection.cc:1014-1057):
kAccPublic直接通过(即使IsAccessible()为 false,public 成员也不需要调用者检查)。- 否则从栈上
GetCallingClass(self, num_frames)取调用者;attached native thread 取不到调用者时返回 false。 calling_class == declaring_class通过。kAccPrivate且不同类 → false。kAccProtected:若 receiver 非空且不是调用者的实例、且声明类与调用者不同包 → false;否则若declaring_class->IsAssignableFrom(calling_class)→ true;其余落到同包判断。- 最终只有
declaring_class->IsInSamePackage(calling_class)为 true 才通过。
8.1.3 Field.get/set 的 ART 链
Field.get/set (libcore native)
→ Field_get/Field_set (runtime/native/java_lang_reflect_Field.cc:170/388)
→ CheckReceiver + VerifyFieldAccess (L46-76)
→ GetFieldValue/SetFieldValue (L79+/L270+)
Field_set的完整门(L388-428;primitive 版 L431-468 同序):先CheckReceiver;随后无条件调用ThrowIAEIfFieldIsNotOverwritable(f)(L351-386)——这一步与IsAccessible()无关,setAccessible(true)不能绕过;再解包/转换值;之后才在!f->IsAccessible()时调用VerifyFieldAccess<true>(L423,内部仍有 final 检查,L46-58);最后SetFieldValue。因此“final 字段不能反射写”不能一概而论:外层先按ArtField::IsUnmodifiable/WriteProtected 判定,内层再按IsAccessible决定是否执行成员级VerifyAccess和 final 检查。具体地,静态 final 在 C+ 被外层拒绝(override 也救不了);普通、非 monotonic、非 WriteProtected、且未命中旧 target/SDK 例外的实例 final(此时IsUnmodifiable=false)在未setAccessible(true)时仍被内层 final 检查拒绝,只有 accessible=true 跳过内层才可写;如果实例 final 带kAccMonotonic,IsUnmodifiable会在前面的IsMonotonic()分支直接返回 true,外层同样拒绝。WriteProtected/旧 target 的兼容例外也要通内层(或 accessible)这一关。ArtField::IsUnmodifiable的实际规则(art_field-inl.h:511-570):!IsFinal()→ false;IsWriteProtected()→ false(System.in/out/err 以及 dogfood 的android.os.Build*静态 final);IsMonotonic()(kAccMonotonic)→ true;targetSdk ≤ B → false(兼容旧应用);static 且字段类型为MethodHandle/VarHandle/Atomic*FieldUpdater/Unsafe→ true;SDK ≤ B → false;其余返回IsStatic()。也就是说:静态 final 在 C+ 默认不可改写;普通、非 monotonic 的实例 final 会落到IsStatic()=false,不属于 IsUnmodifiable;但带kAccMonotonic的实例 final 在更早分支已返回 true;WriteProtected/旧 target 是可写兼容例外。本卡不推断 kAccMonotonic 由谁设置,也不把 monotonic 属性归到 record/其它类。ThrowIAEIfFieldIsNotOverwritable的判定:WriteProtected 字段只有在 targetSdk≤B 或 SDK≤B 时放行,否则抛;非 WriteProtected 字段在!IsUnmodifiable时放行,否则抛IllegalAccessException("Cannot set ... field ...");写 static final 时另记 BCP/App static final overwrite 指标。- 访问检查:
VerifyFieldAccess<true>与Method共用VerifyAccess,但num_frames=1(Field_set本身多一层 native 帧);receiver 类型由CheckReceiver检查,静态字段用声明类作为class_or_rcvr。Field_get没有 overwrite 外层门,但有!f->IsAccessible() && !VerifyFieldAccess<false>(L179/207),final 读不受 final 限制。 - 读写形态:primitive 有专用
Field_getInt/setInt等入口;GetFieldValue/SetFieldValue按字段类型和is_volatile选择普通或 volatile 访问(L79-130、L270-330)。引用字段读写还涉及读屏障/写屏障,不能只看 Java 字段赋值。 - 失败:
IllegalAccessException(不可改写的外层门、或!IsAccessible时的成员访问/final 内层检查)、IllegalArgumentException(receiver 类型不匹配/非 primitive 的 primitive setter)、ExceptionInInitializerError(静态字段声明类初始化失败)。本卡不把上述规则宣布为安全漏洞;它只是本 pin 的源码行为。
8.1.4 Constructor.newInstance 的 ART 链与一个必须保留的边界
Constructor.newInstance (Java, libcore Constructor.java:338-347)
├─ serializationClass == null → newInstance0 (native, Constructor.java:353-355)
│ → Constructor_newInstance0 (runtime/native/java_lang_reflect_Constructor.cc:62-123)
│ → InvokeConstructor (reflection.cc:792-833)
└─ serializationClass != null → newInstanceFromSerialization (native, Constructor.java:349-351)
→ Constructor_newInstanceFromSerialization (java_lang_reflect_Constructor.cc:126-133; 独立注册 L138)
(不经过 Constructor_newInstance0)
Constructor_newInstance0先检查c->IsAbstract()→InstantiationException(接口/抽象类消息)。- 类级访问检查:若
!m->IsAccessible() && !c->IsPublic(),取GetCallingClass(self, 2);caller 非空且!caller->CanAccess(c)→IllegalAccessException。dalvik.system.DexPathList$Element有一个只记 warning 的兼容特例(b/20699073)。 - 然后
EnsureInitialized、分配 receiver、InvokeConstructor;后者检查参数个数/类型并包装目标异常,String构造器同样走StringFactory。 - 边界(本 pin 的源码事实):libcore 的
Constructor.newInstance是 Java 包装层(L338-347),它本身不做成员级检查,而是按serializationClass分派到 nativenewInstance0(L353-355)或newInstanceFromSerialization(L349-351)。nativenewInstance0只看到类级CanAccess,没有Method.invoke那样的成员级VerifyAccess调用。因此不能从这段源码声称“构造器成员级访问检查已完整执行”;这是需要运行/测试验证的源码观察,不能外推成 JLS 完整性结论,也不能据此推断存在安全漏洞。字段/方法的成员级规则已在 8.1.2/8.1.3 明确。
8.1.5 MethodHandles.Lookup.checkAccess 是另一条链
Lookup.findVirtual(MethodHandles.java:960-1000)在拿到 Method 后调用 checkAccess(refc, method.getDeclaringClass(), method.getModifiers(), method.getName())(L996),再按接口/类选择 INVOKE_INTERFACE/INVOKE_VIRTUAL 并 createMethodHandle。checkAccess(L2185-2232)的规则:
Object.clone+ 数组 + protected 的特例:把 protected 临时按 public 处理(数组没有自己的 clone 方法)。- protected 构造器:去掉 protected 位(不能在异包
newprotected 构造器)。 public成员且refcpublic 且allowedModes != 0:快速通过。- 否则
fixmods(mods)后与allowedModes求交;命中则VerifyAccess.isMemberAccessible(refc, defc, mods, lookupClass, allowedModes);protected 还允许按同包规则回退。 - 全部失败 →
throwMakeAccessException。
这条链完全在 Java 层,和 8.1.1 的注释块、8.1.2 的 ART VerifyAccess 是三套不同实现,不能合并叙述。
8.2 MethodHandle:签名多态、ART 分派与 Transformer
8.2.1 Java 面:活入口与已移除的 RI 入口
// ojluni/src/main/java/java/lang/invoke/MethodHandle.java:549,586(活代码)
public final native @PolymorphicSignature Object invokeExact(Object... args) throws Throwable;
public final native @PolymorphicSignature Object invoke(Object... args) throws Throwable;
invokeBasic/linkToVirtual/linkToStatic/linkToSpecial/linkToInterface 在 // BEGIN Android-removed: RI implementation unused on Android. 注释块内(自 L588 起),不是本 Android 的活入口。invokeExact/invoke 是调用点签名多态:编译器按实参生成符号类型描述符,运行时走 invoke-polymorphic,不是普通 JNI 固定签名绑定。javadoc 明确:若通过 Method.invoke、JNI 或 Lookup.unreflect 直接调用这两个方法,会抛 UnsupportedOperationException(L538-543、L574-579)。
Android 另有 invokeExactWithFrame(EmulatedStackFrame)(L1588)和 transformInternal(EmulatedStackFrame)(L1612);前者是 Transformer/invokeWithArguments 回边,后者是所有 Transformer 的统一 Java 入口。
8.2.2 ART 分派:exact、inexact 与 Transformer
MethodHandle.invokeExact (invoke-polymorphic)
→ ART MethodHandleInvokeExact (runtime/method_handles.cc:905-912)
→ MethodHandleInvokeExactInternal (L810-842)
├─ kInvokeDirect/Interface/Static/Super/Virtual → DoMethodHandleInvokeMethod (L746-808)
├─ kInstanceGet/Put, kStaticGet/Put → MethodHandleFieldAccess (L616-683)
├─ kInvokeTransform → MethodHandleInvokeTransform (L366-440)
└─ kInvokeVarHandle[Exact] → DoVarHandleInvokeTranslation (L685-744)
- exact:先
callsite_type->IsExactMatch(method_handle->GetMethodType()),不匹配抛WrongMethodTypeException,然后按Kind分派。DoMethodHandleInvokeMethod用RefineTargetMethod解析实际ArtMethod,创建ShadowFrame、把调用者寄存器参数复制到被调方法参数区,再PerformCall;字段访问和 VarHandle 各自有专用路径。 - inexact:
MethodHandleInvoke(L895-903)→MethodHandleInvokeInternal(L844-893)。若callsite_type->IsInPlaceConvertible(method_handle_type)(调用点与 handle 类型只差 32 位寄存器可表示的窄化/引用可赋值),直接走 exact;否则取asTypeCache,调用MethodHandle.asType(callsite_type)生成适配器,再对适配器走 exact。asType的缓存和asTypeUncached在 libcore Java 层(MethodHandle.java:830-850+)。 - Transformer:
Transformers.Transformer extends MethodHandle(Transformers.java:65-110),构造时把TRANSFORM_INTERNAL的ArtMethod作为 target、handleKind=INVOKE_TRANSFORM。ART 的MethodHandleInvokeTransform只准备 2 个 vreg(handle 自身 +EmulatedStackFrame),调用transformInternal;transformInternal再调用子类transform(frame)。FilterArguments(Transformers.java:1452-1530)为每个参数创建 filter frame、invokeFromTransform(filter, ...)、把返回值写回目标 frame,最后调用 target 并把返回值复制回调用者 frame;InsertArguments/DropArguments/PermuteArguments等同理,都是 Java 层 frame 读写,不是 VM 魔法。 - 嵌套 Transformer:
Transformer.invokeFromTransform(L86-94)若目标还是 Transformer,直接调用其transform,避免再绕invokeExactWithFrame;普通 handle 才走invokeExactWithFrame。 invokeExactWithFrame回边:MethodHandle.invokeExactWithFrame(L1588)→ ARTMethodHandle_invokeExactWithFrame(java_lang_invoke_MethodHandle.cc:32-37)→MethodHandleInvokeExactWithFrame(method_handles.cc:915-950):把 frame 写入ShadowFrame,以MethodHandle.invokeExact的ArtMethod为入口,调用MethodHandleInvokeExact,成功则把返回值写回 frame。它主要用于 Transformer/桥接,不是普通 Java 调用者的入口。- 失败:exact 类型不匹配 →
WrongMethodTypeException;引用转换失败 →ClassCastException;目标异常原样透传(声明throws Throwable);通过反射/JNI 直接调invokeExact→UnsupportedOperationException(javadoc)。invoke比invokeExact宽松是语义差异,不是性能回退。 - 实现类:
MethodHandleImpl是 Android 从零实现的直接 handle(头注释 L32-35),构造时getMemberInternal()取ArtField/ArtMethod,字段 handle 的resolveTarget生成DirectMethodHandle$Holder的 get/put/volatile 目标(L56-110);getMemberInternal是 ART native(java_lang_invoke_MethodHandleImpl.cc:33-64),按 handle kind 物化Field/Method/Constructor。DirectMethodHandle被裁剪为 Holder 所需方法,Invokers的 RI 机制在注释块中。
8.3 序列化与 zip
8.3.1 写对象图:句柄、替换与字段顺序
流头不是在 writeObject/depth==0 时写的:ObjectOutputStream(OutputStream) 构造器在 L246-258 初始化 handles/subs 后调用 writeStreamHeader()(L252),再进入 block-data 模式。writeObject(Object)(L348-368)只是调用 writeObject0(obj, false);其中的 depth==0 是 catch IOException 后是否调用 writeFatalException(ex) 的门(Android 还吞掉写 fatal exception 时的二次 IOException),不是写流头的条件。
writeObject0(L1128-1250)的顺序:
subs.lookup(obj)处理替换表;null →writeNull()。- 若
!unshared且handles.lookup(obj) != -1→writeHandle(h)(回引用)。 ObjectStreamClass.lookup(cl, true);若hasWriteReplaceMethod(),调用invokeWriteReplace(obj)。Android 改为只做一次替换(L1155-1187 的注释保留原for(;;)循环),替换结果类变化时重新取 descriptor;替换为 null 则写 null。- 若
enableReplace,再调用replaceObject;替换结果与orig不同则subs.assign(orig, obj)并重新做 null/回引用检查。 - 按类型分派:
Class/ObjectStreamClass(Android 改为可替换)、String、数组、Enum、Serializable、否则NotSerializableException。
writeOrdinaryObject(L1469-1503)先 desc.checkSerialize(),写 TC_OBJECT 和 class descriptor,然后 handles.assign(unshared ? null : obj),再写 record/Externalizable/普通字段。先分配句柄再写字段是循环引用的关键:字段里再次遇到同一对象时,writeObject0 会命中句柄写成 TC_REFERENCE,不会无限递归。
writeSerialData(L1549-1588)按 getClassDataLayout() 从父类到子类处理:有自定义 writeObject 的类设置 SerialCallbackContext、开 block data、调用 desc.invokeWriteObject,最后写 TC_ENDBLOCKDATA;否则 defaultWriteFields。defaultWriteFields(L1592-1645)先写 primitive 字段块,再对每个对象字段递归 writeObject0,unshared 标志来自 ObjectStreamField.isUnshared()。
HandleTable(L2359-2450)是链式哈希,不是“开放寻址+链表”:spine 存每个桶的头句柄,next 存同桶下一句柄;lookup(L2405-2415)用 hash(obj) % spine.length 取桶头,再沿 next[i] 链比较 objs[i] == obj(引用相等)。assign 按递增序号分配并插入桶链。因此两个内容相等但非同一对象的实例不会共享句柄。
类描述符/record 的句柄点不同:writeClass(L1260-1272)先写 TC_CLASS 和 class descriptor,再 handles.assign(cl);writeClassDesc 处理 null/回引用/proxy/nonproxy,writeProxyDesc/writeNonProxyDesc 在写类型码后各自 handles.assign(desc)。而 writeOrdinaryObject 对普通 Serializable 和 record 都在写 class descriptor 后、写字段/记录数据前 handles.assign(obj);record 分支随后走 writeRecordData(要求 slots.length==1,否则 InvalidClassException),再 defaultWriteFields,它没有普通类的自定义 writeObject 钩子。不能把“普通对象先注册句柄再写字段”的结论外推成 Class/ClassDesc 的注册顺序,也不能把 record 说成走普通 writeSerialData。
8.3.2 读对象图:先占位、后填字段、再 resolve
readObject0(L1389-1475)先处理 block data 和 TC_RESET,再按 type code 分派:TC_REFERENCE → readHandle(L1549-1570,校验句柄范围并取 handles.lookupObject),TC_OBJECT → checkResolve(readOrdinaryObject(...)),TC_EXCEPTION → WriteAbortedException。
readOrdinaryObject(L1895-1970):
- 读 class descriptor,
desc.checkDeserialize();拒绝String/Class/ObjectStreamClass的 ordinary object descriptor。 desc.newInstance()(可序列化类走第一个非可序列化父类的无参构造器;Externalizable 走 public 无参构造器),失败包成InvalidClassException("unable to create instance")。- 先
handles.assign占位(passHandle),再读 record/Externalizable/普通字段;因此循环引用在字段读取时能通过TC_REFERENCE找到尚未填完的对象。 handles.finish(passHandle)关闭依赖;若hasReadResolveMethod()且无异常,调用invokeReadResolve(obj);若返回不同对象,handles.setObject(passHandle, rep)。unshared且返回数组时还要cloneArray,避免把流内共享数组泄漏成外部共享。- 读侧
HandleTable(L3620-3710)除对象/句柄外还维护status(OK/UNKNOWN/EXCEPTION)和deps;markDependency/markException把ClassNotFoundException沿依赖图传播到引用该对象的句柄,finish按 LIFO 结算lowDep。这解释了“某个类缺失时,引用它的对象也会以ClassNotFoundException失败”的行为。
resolveClass(L680-690)用 Class.forName(name, false, latestUserDefinedLoader()),找不到时回退 primitive 名称表;resolveProxyClass(L747-780)用 latest loader 解析接口,非 public 接口必须来自同一 loader,否则 IllegalAccessError,Proxy.getProxyClass 的 IllegalArgumentException 被包成 ClassNotFoundException。
兼容性:ObjectStreamClass.initNonProxy(L625-665)在流/本地描述符同为 Serializable、且类不是数组/record 时,如果 serialVersionUID 不等就抛 InvalidClassException(“local class incompatible: stream classdesc serialVersionUID = ..., local class serialVersionUID = ...”),并检查 proxy/enum 绑定、类名、Serializable/Externalizable 一致性。缺省 SUID 由 computeDefaultSUID(L1807 起)按类名、修饰符、接口、字段、构造器、方法签名计算;显式 serialVersionUID 由 getDeclaredField("serialVersionUID") 读取(L1793-1800)。readObjectNoData 用于流中缺少该层类数据时(readSerialData 的 hasData==false 分支)。
过滤(Android 本 pin 的实际状态):ObjectInputFilter 逻辑在 ObjectInputStream 中被整体移除——totalObjectRefs、serialFilter、filterCheck 调用均为注释(L245-266、L287-323、L1135-1136、L1414-1415、L1492-1502、L1745-1747)。因此不能把 JEP 290 的 setObjectInputFilter/Config.getSerialFilter 当作本 pin 的运行机制;readObject 的深度/引用计数也没有 totalObjectRefs 上限。需要过滤时必须在应用层或更新的平台版本实现,不能用 JDK 文档直接外推。
8.3.3 zip:Java 解析 + zlib 原语
ZipFile.CleanableResource(L787-799)注册 Cleaner 并调用Source.get(...);Source.get(L1574-1608)用带文件属性与isZipFilePathValidatorEnabled的Key做共享引用计数,new Source(L1622-1643)打开RandomAccessFile(setCloExecFlag=true)。findEND()(L1727 起)只负责定位/校验 EOCD;中央目录索引由initCEN(Android-changed 签名initCEN(byte[] cen, int knownTotal),L1823 起;调用在 L1646)解析,Android 改动避免长期保留 CEN 字节于堆内。旧注释“native method open()”不能当机制。- 压缩/解压原语才是 native:
Deflater.init/setDictionary/deflateBytesBytes(Deflater.java:926-930)→ zlib(跨仓/系统库)。 - 失败:
ZipException、IOException(容器/路径校验);DataFormatException(Inflater 压缩流)。
8.4 加密:libcore 只提供 Java API 与 provider 选择框架
8.4.1 libcore 内的选择逻辑
- libcore 内:
javax.crypto.Cipher、java.security.Security/Provider等 API 与选择框架。Cipher的注释明确 Android 使用自己的 provider/impl 选择代码(Cipher.java:278-294),内部持有Provider provider(L335)与SpiAndProviderUpdater(L372/L415)。 - 选择逻辑是有条件的继续尝试(
updateAndGetSpiAndProviderL2800-2822 →tryCombinationsL2866-2942): - 指定 provider(L2890-2898):按 transform 顺序
getService,遇到第一个非空 service 即return tryTransformWithProvider(...);该分支没有 try/catch,不会在同 provider 内继续下一个变体(返回 null/抛异常也直接向上)。 - 未指定 provider(L2899-2922):遍历
Security.getProviders()× transform 变体;有 key 时先用service.supportsParameter(key)过滤,再tryTransformWithProvider;尝试失败/抛异常会继续下一个候选(记住第一个异常,最终按类型抛出)。 - 选定 SPI 后:
update/doFinal走已选spiImpl(initParams == null时直接复用,L2809-2812),不会在运行失败时自动换 provider;再次init会重新走updateAndGetSpiAndProvider,可以重新选择;显式 provider 仍只在限定 provider 内选择。 - libcore 外:具体算法实现由 Conscrypt(JCA/TLS provider)与 BoringSSL(native 密码库)提供。libcore 源码树中没有 conscrypt/boringssl 实现文件;
JavaLibrary.bp:303仅引用conscrypt.module.intra.core.api.stubs作为编译期 stub。
8.4.2 TLS/密钥链与 provider 默认选择边界
SSLContext.getInstance("TLS")
→ OpenSSLProvider 注册 defaultSslContext(默认构造=TLSv13;可配置为 TLSv12)
→ OpenSSLContextImpl 构造:new ClientSessionContext/ServerSessionContext
→ AbstractSessionContext.sslCtxNativePointer = NativeCrypto.SSL_CTX_new()
→ JNI NativeCrypto_SSL_CTX_new → BoringSSL SSL_CTX_new(TLS_with_buffers_method())
→ SSLContext.init(kms,tms,sr) → OpenSSLContextImpl.engineInit → SSLParametersImpl
→ SSLSocket/SSLEngine 使用 SSL_new / SSL_do_handshake
- provider 名称:
Platform.getDefaultProviderName()在 platform 变体返回"AndroidOpenSSL"(Platform.java:132-134);Conscrypt.newProvider()用它作为OpenSSLProvider的默认名。这只能证明 provider 的默认名字,不证明系统 Security provider 顺序或某产品一定选择它;默认顺序仍由产品/平台配置决定。 - TLS 映射是有条件的:
OpenSSLProvider()→OpenSSLProvider(Platform.getDefaultProviderName())→ 该构造器把defaultTlsProtocol固定为字面量"TLSv1.3"(L82-86);另一个包内构造器OpenSSLProvider(providerName, includeTrustManager, defaultTlsProtocol)(L91-98)接受"TLSv1.2"/"TLSv1.3",由switch选择defaultSslContext,再把它同时写入SSLContext.SSL/SSLContext.TLS/SSLContext.Default(L106-126)。因此“SSLContext.TLS→TLSv13”只对默认OpenSSLProvider构造/默认Conscrypt.newProvider()成立;自定义 provider 可以映射到TLSv12。 OpenSSLContextImpl的TLSv13/TLSv12类分别super(NativeCrypto.TLSV13_PROTOCOLS)/(TLSV12_PROTOCOLS)(L172-196);私有无参构造器也走TLSV13_PROTOCOLS, true,getPreferred()返回new TLSv13()(L61-68)。构造器创建ClientSessionContext/ServerSessionContext(L63-68),二者继承AbstractSessionContext,后者的字段初始化调用NativeCrypto.SSL_CTX_new()(AbstractSessionContext.java:45)。engineInit只构建SSLParametersImpl(L117-122):key/trust manager、协议/cipher 列表;SSLParametersImpl仅在protocols==null时用NativeCrypto.getDefaultProtocols(),否则对传入 protocols 做filterFromProtocols/checkEnabledProtocols(L179-190)。它不在此处 handshake。- 三个概念分开:provider 名字(默认
AndroidOpenSSL)、provider 注册的算法/默认协议(defaultTlsProtocol)、实际 TLS 握手协商版本(由 client/server 的SSLParameters与 BoringSSL 握手决定)不是同一件事;不能由SSLContext.TLS的映射断言某连接一定使用 TLS 1.3。 - 默认 TrustManager/KeyManager:
SSLParametersImpl在 kms/tms 为 null 时调用KeyManagerFactory.getDefaultAlgorithm()/TrustManagerFactory.getDefaultAlgorithm(),再由tmf.init((KeyStore)null)取第一个X509TrustManager(L641-740)。这意味着默认信任根来自平台默认TrustManagerFactory,并不等价于“Conscrypt 自带一套根证书”;找不到 X509 manager 时抛KeyManagementException。 - 证书验证路径:
TrustManagerImpl.checkServerTrusted(L311-334)→checkTrusted(L403-459)。HTTPS hostname 验证有三门前提:session != null && parameters != null && "HTTPS".equalsIgnoreCase(parameters.getEndpointIdentificationAlgorithm());只有 Socket/SSLEngine 重载传入 session/parameters 时才会调ConscryptHostnameVerifier.verify,普通两参checkServerTrusted(chain, authType)传 null,不走这段 HTTPS 检查。随后做链构建:blocklist 检查、按 subject/public key 找 trust anchor、checkTrustedRecursive(L532-658)、最终verifyChain(L667+,含 host/clientAuth/OCSP/TLS SCT)。找不到可信链抛CertificateException;这条路径是否被调用、何时调用由握手回调决定,不能仅凭SSLContext构造推断。 - 握手正常/拒绝路径(返回约定分开):
NativeSsl.doHandshake(fd 路径)→NativeCrypto.SSL_do_handshake;ConscryptEngine走ENGINE_SSL_do_handshake。JNI 在非异常情况返回未取负的SslErrorcode:成功SSL_ERROR_NONE=0,SSL_ERROR_WANT_READ/WANT_WRITE用正号(native_crypto.cc:11284-11299);不是“所有非异常返回都 >0”。ConscryptEngine.handshake(L868-900)对SSL_ERROR_WANT_READ调pendingStatus(pendingOutboundEncryptedBytes()):pending>0 →NEED_WRAP,否则NEED_UNWRAP(L472-474);SSL_ERROR_WANT_WRITE直接NEED_WRAP;SSL_ERROR_NONE走成功路径finishHandshake()→FINISHED;IOException先closeAll()再抛,其它异常转SSLHandshakeException。ConscryptEngineL775-805 的-SSL_ERROR_WANT_READ/-SSL_ERROR_WANT_WRITE属于readPlaintextData/SSL_read路径,不是握手返回依据。证书回调verifyCertificateChain(L1540-1565)检查 peer 证书链非空、X509TrustManager 存在,再按 client/server 模式调Platform.checkServerTrusted/checkClientTrusted;CertificateException原样抛出,其它异常包成CertificateException,握手失败。NativeSsl.doHandshake在 fd 已关闭时先抛SocketException("Socket is closed")。这里只核到回调/返回值/异常类型,不声称某次握手的具体 TLS 版本或证书链一定成功。 - 会话缓存/生命周期:
ClientSessionContext默认容量 10、ServerSessionContext默认 100(各自super(...)),AbstractSessionContext默认 timeout 8h(L40-45);cacheSession(L278-299)按 session ID 放入LinkedHashMap,重复 ID 先removeSession,超过maximumSize时移除最老项并回调onBeforeRemoveSession;getSession只在NativeSslSession.isValid()时返回;setSessionTimeout同时SSL_CTX_set_timeout并清除已失效会话(L146-178);newSsl在sslCtxNativePointer != 0时SSL_new,freeNative在 finalize 时SSL_CTX_free(L252-271)。这些是会话缓存机制,不等于“握手成功/证书通过”的证明。 - JNI:
NativeCrypto.SSL_CTX_new()(NativeCrypto.java:1078)→NativeCrypto_SSL_CTX_new(native_crypto.cc:8474-8534)→SSL_CTX_new(TLS_with_buffers_method());SSL_new/SSL_do_handshake声明在 NativeCrypto.java:1164/1474,SSL_CTX_free在 1153/8541 负责释放。错误经throwExceptionFromBoringSSLError;engineGetSocketFactory在sslParameters==null时抛IllegalStateException("SSLContext is not initialized."),engineInit的 manager 组合错误抛KeyManagementException。 - 边界:这条链说明“选择 Conscrypt 之后”的 SSLContext→BoringSSL 入口,并已核代表性的默认 TrustManager/证书链、会话缓存(Client 10/Server 100、8h、
SSL_CTX_free)和握手 WANT/异常返回;未展开完整 TLS 状态机、cipher/curve 选择、产品实际 provider 默认顺序与设备握手行为。provider 默认顺序、设备上实际 TLS provider 仍需产品配置/运行验证。
8.4.3 代表链:AES/CBC/PKCS5Padding
Cipher.getInstance("AES/CBC/PKCS5Padding")
→ libcore Cipher 选择 service(代表路径:Provider=Conscrypt;不证明默认顺序)
→ Conscrypt OpenSSLProvider 注册的 OpenSSLEvpCipherAES$AES$CBC$PKCS5Padding
→ OpenSSLCipher.engineInit/engineUpdate/engineDoFinal (OpenSSLCipher.java:283-342)
→ OpenSSLEvpCipher.engineInitInternal/updateInternal/doFinalInternal
→ NativeCrypto.EVP_CipherInit_ex/EVP_CipherUpdate/EVP_CipherFinal_ex
→ JNI native_crypto.cc:4135/4188/4241
→ BoringSSL EVP_CipherInit_ex/EVP_CipherUpdate/EVP_CipherFinal_ex
→ EVP_CIPHER->cipher_update (AES-CBC: CRYPTO_cbc128_encrypt/decrypt)
具体锚点:
- provider 注册:
OpenSSLProvider构造器先NativeCrypto.checkAvailability()(L92-96);Cipher注册表把"AES/CBC/PKCS5Padding"映射到OpenSSLEvpCipherAES$AES$CBC$PKCS5Padding(L473-476);对称 cipher 的 key 约束通过putSymmetricCipherImplClass→putImplClassWithKeyConstraints(L639-645),要求RAW编码。 - Java SPI:
OpenSSLCipher.engineInit(L283-305)checkAndSetEncodedKey后调engineInitInternal;engineUpdate(L309-342)按maximumLen分配输出并调updateInternal;OpenSSLEvpCipher.engineInitInternal(L56-111)用NativeCrypto.EVP_get_cipherbyname(getCipherName(keyLen, mode))取EVP_CIPHER*,校验 IV 长度,再EVP_CipherInit_ex;可变长 key 先以 null key 初始化、EVP_CIPHER_CTX_set_key_length、再以真实 key 初始化(L99-104);EVP_CIPHER_CTX_set_padding只在 PKCS5 时打开。updateInternal(L113-129)调用EVP_CipherUpdate;doFinalInternal(L131-166)在解密且无 update 时直接返回 0,否则EVP_CipherFinal_ex,短 buffer 用临时lastBlock再System.arraycopy,最后reset()。 - JNI 桥:
NativeCrypto静态初始化NativeCryptoJni.init()(L69-82),平台实现System.loadLibrary("javacrypto")(Conscryptplatform/src/main/java/org/conscrypt/NativeCryptoJni.java)。native 声明在NativeCrypto.java:403-417;JNI 实现在common/src/jni/main/cpp/conscrypt/native_crypto.cc:NativeCrypto_EVP_get_cipherbynameL4081、NativeCrypto_EVP_CipherInit_exL4135、NativeCrypto_EVP_CipherUpdateL4188、NativeCrypto_EVP_CipherFinal_exL4241;注册表sNativeCryptoMethodsL12245、对应条目 L12351-12354,jniRegisterNativeMethodsL12600。 - JNI 参数/错误:
EVP_CipherInit_ex把 Javabyte[] key/iv复制到 native buffer 后调用 BoringSSL;EVP_CipherUpdate检查in/out的 offset/length,非法时抛ArrayIndexOutOfBoundsException,BoringSSL 失败时throwExceptionFromBoringSSLError;EVP_CipherFinal_ex的失败走throwBadPaddingException(填充错误映射为BadPaddingException)。 - 构建链接:Conscrypt
Android.bp:718-758的libjavacrypto在 Android 上shared_libs: ["libcrypto","libssl"]、runtime_libs: ["libandroidio"],host 上静态链接libcrypto/libssl;libconscrypt_jni(L137-148)静态链接libssl/libcrypto_static。这证明 JNI 桥确实落到 BoringSSL,而不是另有一套实现。 - BoringSSL(pin
0cd1f6a9…):src/crypto/fipsmodule/cipher/cipher.cc.inc:101 EVP_CipherInit_ex分配/清理EVP_CIPHER_CTX、设置ctx->cipher、IV、key 和cipher->init;:638 EVP_CipherUpdate按ctx->encrypt分派EVP_EncryptUpdate/EVP_DecryptUpdate;:666 EVP_CipherFinal_ex分派EVP_EncryptFinal_ex/EVP_DecryptFinal_ex。AES-CBC 的cipher_update在e_aes.cc.inc:162-173:有dat->stream.cbc时走该函数指针,否则CRYPTO_cbc128_encrypt/decrypt。 - AES 实现选择边界:
e_aes.cc.inc:66-150的aes_init_key按能力检测选择aes_hw_*(硬件 AES)、vpaes_*、bsaes_*或aes_nohw_*,设置dat->block与可选dat->stream.cbc;CBC 更新若dat->stream.cbc为空,则cbc.cc.inc:24-63 CRYPTO_cbc128_encrypt做 16 字节 XOR +block循环(解密在:65-132,处理 in/out 别名),否则走所选 stream 函数。对固定 pin 的src/crypto/fipsmodule/aes/asm/做目录清单检查,只看到 x86/arm 系列脚本,未见 RISC-V AES 汇编文件;这只能说明该目录清单,不证明 RISC-V 无 AES 能力,实际选择仍由 capability 检测/编译配置决定,不能从这段源码断言使用某条 RISC-V AES 指令或某个吞吐。 - 失败/回退:BoringSSL 错误队列经 Conscrypt
throwExceptionFromBoringSSLError,CBC 解密填充错误在EVP_CipherFinal_ex映射BadPaddingException;此外NoSuchAlgorithmException/NoSuchPaddingException(未找到 service)、InvalidKeyException/InvalidAlgorithmParameterException(key/IV 不合法)。provider 选择阶段可继续候选;已初始化 SPI 的update/doFinal不自动迁移;显式 provider 只在该 provider 内选择。provider 默认顺序、设备 TLS 实现与 AES 硬件能力仍需产物/运行验证。 - SSLContext 链见 §8.4.2:在默认
OpenSSLProvider构造下,SSLContext.TLS→OpenSSLContextImpl$TLSv13→SSL_CTX_new;自定义defaultTlsProtocol可映射到TLSv12。它只说明“选择 Conscrypt 且走该构造”的路径,不证明 Conscrypt 是唯一默认 provider 或所有算法的唯一实现;默认 provider 顺序与协商版本需按产品配置/运行核实。
8.5 正常/失败/回退(本章)
| 机制 | 正常 | 失败 | 回退 |
|---|---|---|---|
| 反射 invoke | 目标调用 + 装箱返回 | IllegalAccess/IllegalArgument/InvocationTarget/StackOverflow | 无 |
| 反射 setAccessible | 置 override | SecurityException(Class/Method/Field 构造器) | 无 |
| MethodHandle | exact 类型匹配后分派;inexact 先 asType | WrongMethodType/ClassCast/目标异常 | 无(invoke 的 asType 是语义,不是失败回退) |
| 序列化 | 句柄/替换/字段顺序按规范 | NotSerializable/InvalidClass/ClassNotFound/WriteAborted | 无自动过滤;ObjectInputFilter 在本 pin 被移除 |
| zip/deflate | Java 解析 + zlib | ZipException/DataFormatException | 无 |
| Cipher | provider 实现算法 | NoSuchAlgorithm/BadPadding/InvalidKey | 选择阶段可继续;运行期不自动换 provider |
8.6 可复制观测
已执行(只读固定 pin):
PIN=4ebfb391ee0109d16abe6e2bc965724b940928c5
ART=b753cf97923c3695338d21466fa14c57b480a59a
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/lang/reflect/AccessibleObject.java | sed -n '90,175p;255,335p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/lang/reflect/Method.java | sed -n '410,425p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/lang/invoke/MethodHandle.java | sed -n '540,650p;1575,1625p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/lang/invoke/Transformers.java | sed -n '45,115p;1450,1530p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/io/ObjectOutputStream.java | sed -n '240,265p;340,375p;1120,1250p;1255,1360p;1465,1650p;2355,2430p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/io/ObjectInputStream.java | sed -n '1385,1510p;1890,1975p'
git -C sources/libcore.git show $PIN:ojluni/src/main/java/java/io/ObjectStreamClass.java | sed -n '620,665p;1155,1215p'
git -C /tmp/art-commits.git show $ART:runtime/reflection.cc | sed -n '690,840p;1005,1060p'
git -C /tmp/art-commits.git show $ART:runtime/native/java_lang_reflect_Field.cc | sed -n '40,80p;160,240p;340,470p'
git -C /tmp/art-commits.git show $ART:runtime/art_field-inl.h | sed -n '480,575p'
git -C /tmp/art-commits.git show $ART:runtime/method_handles.cc | sed -n '365,440p;700,950p'
git -C /tmp/conscrypt.git show fd1a430323b5ab9945a15fce6c4a824aeffff685:common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java | sed -n '50,170p'
git -C /tmp/conscrypt.git show fd1a430323b5ab9945a15fce6c4a824aeffff685:common/src/jni/main/cpp/conscrypt/native_crypto.cc | sed -n '4130,4270p;8465,8555p'
git -C /tmp/conscrypt.git show fd1a430323b5ab9945a15fce6c4a824aeffff685:common/src/main/java/org/conscrypt/OpenSSLContextImpl.java | sed -n '40,130p'
git -C /tmp/conscrypt.git show fd1a430323b5ab9945a15fce6c4a824aeffff685:common/src/main/java/org/conscrypt/AbstractSessionContext.java | sed -n '35,65p'
git -C /tmp/conscrypt.git show fd1a430323b5ab9945a15fce6c4a824aeffff685:common/src/main/java/org/conscrypt/TrustManagerImpl.java | sed -n '300,460p;520,700p'
git -C /tmp/conscrypt.git show fd1a430323b5ab9945a15fce6c4a824aeffff685:common/src/main/java/org/conscrypt/ServerSessionContext.java | sed -n '25,35p'
git -C /tmp/boringssl.git show 0cd1f6a94b670e6b61af95a06e71e690ae8e3262:src/crypto/fipsmodule/cipher/cipher.cc.inc | sed -n '95,205p;630,685p'
git -C /tmp/boringssl.git ls-tree -r --name-only 0cd1f6a94b670e6b61af95a06e71e690ae8e3262 | grep 'src/crypto/fipsmodule/aes/asm/'
未执行:反射/MH 调用基准、序列化兼容/循环/代理用例(过滤在本 pin 不适用)、zip 损坏输入、Cipher provider 选择与加解密实验(需运行/设备);AES 汇编与产物 intrinsify 未核。
8.7 缺口与下一步
- 构造器成员级访问检查:本 pin
Constructor_newInstance0只看到类级CanAccess;需用运行用例确认 libcore/ART 是否在别处补成员级检查,不能仅凭这段源码下安全结论。 - 序列化过滤:
ObjectInputFilter在本 pin 被移除,若后续平台版本恢复该机制,需按新 pin 重新取证;本稿不把 JDK 的过滤语义当成 Android 行为。 - MH/反射产物:invoke-polymorphic 的链接/内联、Transformer 的调用点优化、反射调用的 JIT 形态需 odex/aot_map/机器码(缺)。
- crypto 内部:
SSLContext→OpenSSLContextImpl→SSL_CTX_new、AES provider→JNI→EVP→CBC 实现选择已核;Conscrypt 完整 TLS 状态机、产品实际 provider 默认顺序、BoringSSL 实际 RISC-V AES 实现仍未逐文件/设备核;默认 TrustManager、证书链、会话缓存与握手 WANT/异常返回源码已补。本稿只给代表链和已核边界。
- 成本与权衡:反射/MH 的访问检查与链接、序列化的句柄/描述符、Conscrypt 的 JNI 与 BoringSSL EVP 上下文都属于不同层;源码存在 intrinsic 不等于冻结调用点已内联。
第 9 章:测试与性能——账本口径、cell 映射与产物身份
- 状态:初稿(口径与映射方法已核;不含内部测量数据;未重跑、未上板)。
- 来源:[S01][S02];跨仓代表链按 [S03][S04]。
- Pin(公开源码):
platform/libcore@4ebfb391。 - 本章回答:如何把“源码机制”与“现有性能账本”对齐;哪些数字能用于研发决策、哪些不能替代正式基准;为什么在产物身份核验前不能解释性能差异;正确的对拍与频率口径是什么。
9.1 账本口径分层(先标口径,再谈数字)
| 口径 | 用途 | 边界 |
|---|---|---|
| Fixed-N diagnostic | 研发归因/A/B:固定次数/固定取样下的相对比较 | 不能替代正式原生 benchmark 的 ns/op 结果;绝对 elapsed 与频率代理本身可以记录,不因“诊断性质”否定测量 |
| 正式 benchmark(ns/op/score) | 对外口径与验收 | 需要正式测试包/环境/统计流程 |
| 历史统计区间(lower/median/upper) | 记录当时的统计分布 | 是历史统计区间记录;未在新硬件配置/新平台复测,不据此断言当前稳定性,也不作为根因结论 |
本报告的分工原则:
- 公开报告只保留口径、方法与公开源码链;不携带内部测试数据、绝对工作区路径与旧产物哈希。
- 内部研发对照(数据集文件名、百分比、odex 哈希等)留在性能项目内部记录,按需在内部评审使用;公开固定 pin 源码与私有测量身份始终分层。
- 频率归一化、AOT/JIT 状态、计时方法必须与数字一起出现;缺任一项的数字不进入结论。
9.2 cell → 源码机制的映射方法
以 ByteBufferBenchmark.timeByteBuffer_putChar 为例(源码定义在公开 pin):
class ByteBufferBenchmark(benchmarks/src/benchmarks/regression/ByteBufferBenchmark.java)
method timeByteBuffer_putChar(L276)
params byteOrder ∈ {BIG, LITTLE}(L42)
aligned ∈ {true, false}(L44)
bufferType ∈ {DIRECT, HEAP, MAPPED}(L46-48)
输入 newBuffer(...)(L51-77):DIRECT/HEAP size = 8192(aligned)/ 8201(非 aligned);
MAPPED 在 L70-72 setLength(8192*8)=65536 并 map 整文件;
position(aligned ? 0 : 1) 对三类一致;循环 1024 次 putChar(' ')
映射规则:
- class/method/params 必须与账本字段逐项对应;同名方法不同参数组不可合并统计。
- 参数含义要回到源码确认(例如
aligned=false是 1 字节 position 偏移,不是硬件对齐属性;ch01 已证)。 - 每个 cell 标注:执行形态(AOT/JIT/解释)、产物身份、计时口径——三者缺一,cell 只能作“历史记录”,不能作“当前结论”。历史 plan 是 8 组合(2 order × 2 aligned × 2 bufferType=DIRECT/HEAP)×30 方法=240 cell;公开源码含 MAPPED 才有 12 种可能组合;MAPPED 不扩入历史 240 cell。
代表 cell → 机制章节的映射骨架(benchmark 类均在公开 pin 的 benchmarks/src/benchmarks/;具体账本 cell 名/参数仍需与账本逐项核对,本轮未全量展开):
| benchmark 类 | method(源码行) | 参数/输入(源码) | 机制章节 | 备注 |
|---|---|---|---|---|
regression.ByteBufferBenchmark | timeByteBuffer_putChar(L276) | byteOrder∈{BIG,LITTLE}(L42)×aligned∈{true,false}(L44)×bufferType∈{DIRECT,HEAP,MAPPED}(L46-48);DIRECT/HEAP size 8192/8201,MAPPED 65536;position 0/1(L51-77) | ch01/ch04 | 已完成纵切;aligned 是 position 偏移,不是硬件对齐属性 |
regression.StringBenchmark | timeHashCode(L40) | StringLengths∈{EMPTY,SHORT,80,8192}(L21-29) | ch03 | 逐 charAt 的 Java hashCode;不覆盖 equals |
regression.StringEqualsBenchmark | timeEqualsNull/Short/Medium/Long/VeryLong/NonWordAligned/End/NonString(L197-260) | 短/中/长/超长/非字对齐/尾部/非 String 对象;含 new String(...) 与 intern 的假设检查(L180-195) | ch03 | 覆盖 String.equals 的快速路径与长度分支 |
SystemArrayCopyBenchmark | timeSystem{Char,Byte,Short,Int,Long,Float,Double,Boolean}ArrayCopy(L28-90) | arrayLength∈{2,4,8,…,262144}(L22-24);同长度、同类型、无重叠 | ch03 | 调用的是 public arraycopy(Object,...),通用 SystemArrayCopy intrinsic 的阈值是 128 个元素;若编译器选中专用 SystemArrayCopyChar/Byte/Int,常量长度用 384B/sizeof(element),变量长度仍走全局 128 的运行期门;具体走哪条需产物依据 |
regression.RealToStringBenchmark | timeFloat_toString_{NaN,NEGATIVE_INFINITY,POSITIVE_INFINITY,zero,minusZero,small,medium,large}(L25-70);timeStringBuilder_*/timeFormatter_*(L73-127) | float 常量 -123.45f/-123.45e8f/-123.45e36f(L21-23) | ch07 | 覆盖 FloatToDecimal 特殊值/普通值;不含 Double.toString |
regression.IntegralToStringBenchmark | time_IntegerToString[_2/_10/_16]_{small,medium,large}、time_IntegerToBinaryString、time_IntegerToHexString(L25-131) | int 常量 12/12345/12345678(L21-23);radix 10/16/2 | ch07 | 覆盖 Integer.toString(int,radix) 的进制分派 |
regression.BigIntegerBenchmark | timeRandomDivision(L23)、timeRandomMultiplication(L41)、timeRandomGcd(L32) | 1024-bit 随机操作数(L25-26/L43-44) | ch07 | 1024 bit 最多 32 个 32-bit limb(随机高位可能为 0,实际 ≤32):乘法两侧都 <50 → multiplyToLen,除法 mag.length ≤32 < 40 → divideKnuth;这些 cell 不会进入 BoringSSL BN_mul/BN_div 阈值路径 |
regression.ReflectionBenchmark | timeMethod_invoke{V,I,PreBoxedI,StaticV,StaticI,StaticPreBoxedI}(L92-143)、timeField_get/setInt(L74-91)、timeClass_newInstance/classNewInstance(L52/L158) | 直接 Method.invoke/Field/构造器调用与常规调用对照 | ch08 | 覆盖 ART 反射访问检查+参数装箱;不是 MethodHandle |
regression.CharsetUtf8Benchmark | time_ascii/bmp2/bmp3/supplementary(L54-68) | new String(byte[], UTF_8) 的 ASCII/BMP/补充平面输入 | ch03 | 覆盖 UTF-8 解码快路径,不是 String.getBytes |
regression.SerializationBenchmark | timeRead/Write{IntArray,ArrayListInteger,String,ObjectStreamClass}、timeWriteEveryKindOfField(L36-150) | int[256]、ArrayList<Integer>、String、ObjectStreamClass、各 primitive/reference 字段 | ch08 | 覆盖句柄/字段块/类描述符;不是过滤或恶意流测试 |
regression.CipherBenchmark | timeEncrypt/timeDecrypt(L134-150) | AES×{CBC,CFB,CTR,ECB,OFB}×{NOPADDING,PKCS1PADDING}×keySize{128,192,256}×inputSize{16,32,64,128,1024,8192}×{OpenSSL,BouncyCastle}(L51-78);Cipher.getInstance(..., providerName)(L130) | ch08 | 显式 provider 路径,直接覆盖 Conscrypt/BC 选择;不是默认 provider 顺序测试 |
regression.MathBenchmark/StrictMathBenchmark | 按类内 time* 方法 | 由 benchmark 源码参数决定 | ch07 | native/Java/intrinsic 边界,需逐 cell 核参数 |
| 并发/IO 类 cell | 按账本实际类 | 需按账本 cell 名核对 | ch05/ch06 | 本轮未全量映射;不把“类存在”当“账本已覆盖” |
9.3 产物身份:为什么它阻断性能解释
现有冻结包的已知缺口(性能项目内部记录,公开稿不引用其数值/哈希):
- 冻结包未绑定被测 commit / boot image / odex / native so 的身份;
- 本地缺该 cell 的
odex/oat/aot_map/profile,无法离线oatdump判定调用点是否 intrinsify/内联; - 因此“worst cell 的成因”可能是编译/产物身份、调用点内联状态、基准约定或执行路径差异——在产物身份核验前不做归因。
最小获取项(拿到即可离线核验,不需要先上板):
- 两个 exact benchmark/runner odex 与对应 boot/aot 产物;JAR/runner 和 30 方法静态集合已取得,五方法+newBuffer 静态 DEX 侧车已接受;
- 被测镜像的
boot.art/boot.oat或dalvik.vm.*编译配置; - 该 cell 的
aot_map/profile 与运行日志。
9.4 正确性对拍计划(未执行)
对 putChar 与 arraycopy 两类高频路径,先做语义对拍、后谈性能:
| 目标 | 用例维度 | 参考实现 |
|---|---|---|
| ByteBuffer.putChar | direct/heap/mapped × BIG/LITTLE × aligned/非 aligned × char 边界值(0/0x7F/0x80/0xFF/0x100/0xFFFF)× 越界/只读/已释放 | Memory.pokeShort 逐字节结果;HeapByteBuffer.putCharUnchecked 分支 |
| System.arraycopy | primitive/引用数组 × 重叠(前向/后向)× 零长度 × 越界 × null | Java char[] 快路径与 native 实现对照 |
规则:对拍通过只证明语义一致,不证明性能;性能结论需要独立测量(§9.5)。
9.5 计时与频率口径
- 维度先分清:
cycles/instructions是 CPI(每指令周期数)类比值,不是频率代理;cycle counter、clocksource、采样时间、实际频率代理(如频率测量/固定频率模式)是不同维度,分别记录。 cycles/op、ns/op、instructions/op不混算;Fixed-N cycles 与正式 ns/op 分开记录、分开报告。- 频率项缺失只限制“依赖频率换算/跨频比较”的测量结论;不能因为频率项缺失就把源码机制结论一并否定(源码确证与测量结论是两个层次)。
- 统计量(geomean/median/lower/upper)要与样本、重复次数、离群处理一起给出;单点数字不构成稳定性结论。
- 目标硬件上的复测需另行计划与授权,不在本报告范围;历史板权限不继承。
9.6 优化候选地图(方法,不给收益承诺)
| 源码机制(章节) | 可观察热点(待测) | 候选方向 | 需要的前置依据 |
|---|---|---|---|
| Direct ByteBuffer 写路径(ch01/ch04) | putChar/putInt 等 cell | 产物 intrinsify 状态核验→再评估 | odex/aot_map/oatdump |
| String equals/hashCode(ch03) | 字符串密集 cell | intrinsic 触发情况核验 | 产物 + 机器码 |
| System.arraycopy(ch03) | 数组复制 cell | intrinsic/快路径选择核验 | 产物 + 对拍 |
| 并发容器/AQS(ch05) | 锁/队列 cell | 争用路径 PMU 归因 | 设备 + PMU |
| IO/NIO(ch06) | syscall 密集 cell | 系统调用/Stub 成本 | 设备 + perf |
| 数值/大数(ch07) | parse/格式化 cell | 算法分支与边界 | 源码 + 基准 |
原则:源码机制 → 热点 → 候选 → 待 A/B;本文不制造收益数字,不把“候选”写成“已优化”。
9.7 可复制观测
已核对(公开方法口径与产物缺口):Fixed-N diagnostic 与正式口径分离、冻结包身份缺口、最小获取项。公开可复制部分:
PIN=4ebfb391ee0109d16abe6e2bc965724b940928c5
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/ByteBufferBenchmark.java | sed -n '30,80p;270,290p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/StringBenchmark.java | sed -n '20,45p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/StringEqualsBenchmark.java | sed -n '190,265p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/SystemArrayCopyBenchmark.java | sed -n '18,95p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/RealToStringBenchmark.java | sed -n '18,128p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/BigIntegerBenchmark.java | sed -n '18,50p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/ReflectionBenchmark.java | sed -n '20,145p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/CharsetUtf8Benchmark.java | sed -n '20,70p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/SerializationBenchmark.java | sed -n '25,160p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/CipherBenchmark.java | sed -n '35,155p'
未执行:任何设备测量、odex 获取、oatdump、PMU 采集(需另行计划/授权/产物)。
9.8 缺口与下一步
- 产物身份核验(离线优先)。
- reps/N/JVM 参数与 provenance:优先从已有精确 cell provenance/账本读取;缺则保留最小获取项,不广扫全盘、不自行租板。
- cell 覆盖边界:公开源码里的
BigIntegerBenchmark用 1024-bit 随机操作数(最多 32 limb),乘法两侧都 <50、除法mag.length<40,因此只覆盖 JavamultiplyToLen/divideKnuth,不会触发 BoringSSLBN_mul/BN_div阈值;若要把大数 native 路径纳入性能结论,需要另选更大操作数并重新绑定 cell,不能拿该 cell 推断 BN_* 成本。 - 对拍与频率口径落地后,才进入“性能解释/优化 A/B”。
- 实现与接口依据:
benchmarks/src/benchmarks/regression/ByteBufferBenchmark.java、benchmarks/src/benchmarks/regression/BigIntegerBenchmark.java与java-cases.tsv/java-jni-cases.tsv定义 cell,方法入口为timeByteBuffer_putChar()与timeRandomMultiplication();产物身份核验前不解释差异。
总结与展望
本稿已把源码机制链、状态/分派、正常与失败/回退、成本边界和可复制观察整合为可评审正文;十章机制保持,42 行矩阵保留在附录 A。仍未运行/构建/反汇编,产物与设备项只在附录 A/C 列为缺口,不据此宣告终验或发布。[S01][S02]
第 10 章:结论、缺口与优化地图
- 状态:初稿(基于 ch01-ch09 的源码确证;未运行、未上板;独立中期复核已返回但非终验)。
- 来源:[S01][S02];跨仓代表链按 [S03][S04]。
- Pin:
platform/libcore@4ebfb391(android17-release = tagandroid-17.0.0_r1);ARTb753cf97仅 intrinsic/ABI 边界参考。 - 本章只做“能复述、能验证、能排障”的收束:结论按依据身份分层,缺口明确,优化只列候选与方法,不给收益数字。
10.1 五条主结论
- libcore 是“Java API + 运行时托管实现”的混合体:String 的字符数据由 ART 运行时管理(
value字段已在源码中注释,count编码长度/压缩位;charAt为@FastNativenative);把 String 描述成“Java 字段 byte[] value”在本 pin 不成立(ch03)。 - native/intrinsic 是多维组合,不是互斥三层:编译器机制至少有两种相关形式——①调用点替换/内联(普通 Java 方法也可被 intrinsify,如
String.equals有StringEqualsOptimizations);②native callee 的 AOT intrinsic body(native 方法也可有优化体内代码)。@FastNative/@CriticalNative描述调用约定、不保证 intrinsic;boot image 可用代码与实际待测调用点需分开核。典型链ByteBuffer.putChar → Memory.pokeShort → pokeShortNative → ART intrinsic与 Heap 路径(纯 Java 字节写)属于不同形态(ch01/ch02/ch03/ch04)。 - 交付边界在构建/APEX,不在 libcore 源码:
apex_available只表示com.android.art/debug 允许依赖core-oj/core-libart;实际装配链已在 ART 公共源码核到——com.android.art-base-defaults.bootclasspath_fragments=["art-bootclasspath-fragment"],该 fragment 的contents含core-oj/core-libart/okhttp/bouncycastle/apache-xml,image_name="art"、dex_preopt.profile指向 boot image profile(ch02)。APEX 内布局、odex 参数与设备安装态仍未验证。 - 并发与 IO 的“语义在 Java、机制在 VM/内核”:AQS/LockSupport 的等待唤醒最终落到 VM/内核;
VarHandle/Unsafe的 fence 语义由规范定义、指令由编译器/架构生成;IO 的 errno→IOException、EOF/EAGAIN、短写处理在IoBridge有明确映射(ch05/ch06)。 - 性能结论必须与产物身份同层:源码具备 intrinsic ≠ 冻结调用点已 intrinsify;现有冻结包的 commit/boot image/odex 绑定缺失,因此在产物核验前不解释任何 cell 差异;Fixed-N diagnostic 用于研发归因/A/B,不能替代正式 ns/op(ch01/ch09)。
10.2 依据身份与边界(全篇)
| 依据类别 | 本篇范围 | 说明 |
|---|---|---|
| 源码确证 | ch01-ch09 的 pin 文件/行号 | libcore 主 pin 4ebfb391;ART/Conscrypt/BoringSSL 边界文件按各自 pin 定点读取(不比较 commit 先后) |
| 外部账本 | ch09 的口径与 cell 结构 | 仅作研发方法参考;公开稿不带内部数据/路径/哈希 |
| 可复现但未执行 | 各章“未执行”清单 | 命令/条件已列,未运行 |
| 本人实测 | 无 | 本阶段未上板、未重跑 |
| 独立复核 | 中期返回,非终验 | 冻结输入的 F1/F2/F3 已返回并落实;后续新增内容未逐项独立复核;作者自检不冒充独立 |
10.3 缺口清单(按影响排序)
- 产物身份缺口(阻断性能解释):冻结包的 odex/oat/aot_map/boot image 未绑定;最小获取项见 ch09。影响:无法判定 intrinsic/内联状态与 cell 差异成因。
- 跨仓机制缺口(已核/未核分列):ART 侧反射访问检查(
reflection.cc/java_lang_reflect_*)、LockSupport→Thread.park_state_+futex、System.arraycopy native/屏障、RV64 arraycopy/VarHandle intrinsic、MethodHandle dispatcher/Transformers 已按 ART pin 逐段核;Conscrypt provider→OpenSSLEvpCipher→NativeCrypto JNI→BoringSSL EVP→AES-CBCcipher_update代表链、以及 SSLContext→OpenSSLContextImpl→SSL_CTX_new 入口已核。仍未核:AArch64 的设备 CPU 特性/产物选择、BoringSSL 的 RISC-V 汇编/性能与 AES 轮函数、Conscrypt TLS 握手状态机与 provider 默认顺序(ART String 压缩布局、RV64/AArch64 arraycopy/VarHandle/Park 源码、BN_mul的 comba8/schoolbook 与BN_div的 Knuth D 算法族已核)。libcore 本仓的一手边界:BigInteger→NativeBNJNI 桥(libcore_math_NativeBN.cpp:143-157/167-177,静态链libcrypto_static)、NioUtils.freeDirectBuffer 清理链(L52-64)、NativeBN 与 Conscrypt 的构建链接(NativeCode.bp/ ConscryptAndroid.bp)。 - 构建/APEX 缺口:装配主干已核(fragment contents + boot image profile);仍缺 APEX 内文件布局、odex 参数与设备安装态(需产物/设备)。
- 仍需继续研究的源码/验证项:AArch64 的设备 CPU 特性(LSE 等)与产物选择;Constructor 成员级访问检查需运行用例确认;AQS 定时/中断时序与
managedBlock拒绝路径需运行验证;CHMcompute/computeIfAbsent/merge与 TreeBin 删除/平衡已展开;serialization 的过滤在本 pin 已移除(不是“未展开”,而是事实),但循环/兼容/代理用例未运行;zip 损坏输入未运行;BoringSSL 的 RISC-V 汇编/AES 轮函数未逐文件。实测未做项(与源码缺口分开):DNS 缓存行为、Cleaner 触发时机、epoll 事件循环、EINTR 实际信号场景。 - 数据版本缺口:ICU/tzdata/Unicode 数据版本在外部仓与构建产物中;libcore 只能证明
u_getVersion/u_getUnicodeVersion/ulocdata_getCLDRVersion运行时查询与ZoneInfo→ZoneInfoData委托,不能绑定实际版本。
10.4 优化候选地图(方法,不承诺收益)
| 优先级线索 | 依据(章节) | 下一步(建议) | 前置依据 |
|---|---|---|---|
| Direct Buffer 写路径 | ch01/ch04:intrinsic 机制存在、产物状态未知 | 离线 oatdump 判定调用点;再决定是否 A/B | odex/aot_map |
| String/Array 快路径 | ch03:charAt/arraycopy 有 native+Java 快路径与 ART 优化机制 | 产物级核验 + 边界对拍 | 产物 + 对拍 |
| 并发争用 | ch05:AQS/ThreadPoolExecutor 状态机明确 | 设备 PMU 归因(设备实验计划确定后) | 目标设备 + PMU |
| IO/网络 | ch06:IoBridge 映射清晰、syscall 边界明确 | strace/perf 观察(设备实验计划确定后) | 目标设备 |
| 数值/格式化 | ch07:算法入口明确 | 基准 cell 映射与边界用例 | 基准 |
规则:候选→依据→A/B→结论;没有 A/B 与产物依据,不写“已优化/已提升”。
10.5 交付与复核路径
- 本篇为公开研究稿;源码与固定 pin 均来自公开树,不含设备实测或内部账本。
- 源码已完成独立反例复核,图表和页面已检查桌面与移动端布局;这些检查不替代设备运行验证。
- 图的语义必须与正文同源(按已接受 ART 改进:SVG 文字清晰、布局适配);当前章节内的表/链可直接转为图源。
- 后续批次:优先补 §10.3 的 1-2 项(产物身份、跨仓机制);覆盖矩阵常驻
覆盖矩阵,缺口不靠删正文通过。
10.6 一句话收束
libcore 的“深度”不在名词密度,而在每条链都能落到 pin 文件+行号、状态/分派、失败与回退、可复制观测。本报告已把 ch01 纵切与 ch02-ch10 的主干按此标准立起来,并完成 ch08 反射/MH/序列化/Conscrypt、ch03 arraycopy 屏障、ch05 park/TreeBin/VarHandle、ch07 转换/大数/ICU 版本、ch09 cell 映射的源码深化。仍需区分三类边界:本仓未逐行(部分 Character/IoBridge 子路径)、跨仓未核(AArch64 设备特性/产物选择、BoringSSL 实际 RISC-V AES 选择/汇编、Conscrypt TLS 握手状态机/provider 默认顺序)、产物/设备未验证(intrinsify/内联、odex/boot image、运行对拍)。覆盖矩阵 的 42 行保持不缩,未做项按“源码待研究/产物未验证/设备未验证”分层登记,不用删正文或换措辞通过。
参考来源
| 编号 | 仓库/对象 | 完整 pin | 本稿使用的代表文件 |
|---|---|---|---|
| [S01] | platform/libcore | 4ebfb391ee0109d16abe6e2bc965724b940928c5(android17-release,tag android-17.0.0_r1) | ojluni/src/main/java/java/lang/String.java、System.java、java/util/concurrent/ConcurrentHashMap.java、java/util/concurrent/locks/AbstractQueuedSynchronizer.java、java/io/ObjectOutputStream.java、java/lang/reflect/*、java/math/BigInteger.java、luni/src/main/native/libcore_io_Linux.cpp、NativeCode.bp 等;完整 57 文件 blob/SHA 台账见 固定 pin 清单 |
| [S02] | platform/art(仅边界参考) | b753cf97923c3695338d21466fa14c57b480a59a | runtime/reflection.cc、runtime/native/java_lang_System.cc、runtime/native/java_lang_reflect_*.cc、compiler/optimizing/intrinsics_riscv64.cc、intrinsics_arm64.cc、runtime/thread.cc、runtime/method_handles.cc、runtime/mirror/string*.h、runtime/intern_table.cc、build/apex/Android.bp、build/boot/Android.bp |
| [S03] | platform/external/conscrypt | fd1a430323b5ab9945a15fce6c4a824aeffff685 | common/src/main/java/org/conscrypt/OpenSSLProvider.java、OpenSSLEvpCipher.java、NativeCrypto.java、OpenSSLContextImpl.java、AbstractSessionContext.java、TrustManagerImpl.java、NativeSsl.java、ConscryptEngine.java;common/src/jni/main/cpp/conscrypt/native_crypto.cc |
| [S04] | platform/external/boringssl | 0cd1f6a94b670e6b61af95a06e71e690ae8e3262 | src/crypto/fipsmodule/cipher/cipher.cc.inc、src/crypto/fipsmodule/cipher/e_aes.cc.inc、src/crypto/fipsmodule/aes/cbc.cc.inc、src/crypto/fipsmodule/bn/mul.cc.inc、src/crypto/fipsmodule/bn/div.cc.inc |
| [S05] | 图表与静态清单 | 本报告 | 42 行覆盖矩阵、57 文件 blob 绑定与各章内嵌图 |
说明:S02 只作 ART intrinsic/ABI 边界参考,不是 libcore 本仓 pin;S03/S04 只作代表链与算法入口边界,不证明同一产品 manifest 或设备 provider 顺序。每章的 文件:行 引用以各章正文为准;完整取证命令、URL/RC/blob 与未完成项在 固定 pin 清单 和附录 C。
附录 A:主要结论与依据对照
本节表格与结论的依据为 [S01](libcore 主 pin)和 [S02](ART 边界),跨仓代表链见 [S03][S04]。
A.1 42 行覆盖矩阵(原问题不缩)
| ID | 问题 | 状态 | 章节 | 剩余缺口/边界 |
|---|---|---|---|---|
| D1-1 | libcore 仓库的模块构成与职责边界(ojluni/luni/dalvik/libart/mmodules/benchmarks 等) | 部分(源码确证,未运行) | ch02 | — |
| D1-2 | 构建与产物流:core-oj/core-libart/core-all jar 如何由 JavaLibrary.bp 组织、如何进入 bootclasspath | 部分(源码确证,未运行) | ch02 | 装配主干已核(jar→fragment→boot image + native lib 进 APEX);APEX 内文件布局/odex 参数/设备安装态未验证 |
| D1-3 | libcore 的 APEX 交付形态(哪个 APEX、core-oj/core-libart 的 apex_available 与运行时来源) | 部分(源码确证,未运行) | ch02 | 装配主干已核(jar→fragment→boot image + native lib 进 APEX);APEX 内文件布局/odex 参数/设备安装态未验证 |
| D1-4 | 类库与 VM 分工:Java API(libcore)↔ ART runtime/JNI/entrypoints 的接口面 | 部分(源码确证,未运行) | ch02 | — |
| D1-5 | 选择点模型:libcore→ART intrinsic→Bionic/内核 的分层选择与回退(何时走 intrinsic、何时走 native/bionic、何时纯 Java) | 部分(源码确证,未运行) | ch02/ch04 | 冻结产物实际 intrinsify/内联状态未核 |
| D2-1 | String 的内部存储(运行时托管:count 编码长度/压缩位;charAt 为 @FastNative native,length() 是 Java 方法;不是 Java 字段 byte[] value)与构造路径 | 部分(源码确证,未运行) | ch03 | 压缩谓词/布局已核(含 NUL 非压缩边界);运行态压缩选择/产物未测 |
| D2-2 | String.equals/compareTo/hashCode 的实现与快捷路径 | 部分(源码确证,未运行) | ch03 | compareTo 活入口为 @FastNative native → ART String_compareTo → mirror::String::CompareTo;hashCode 无对应 RV64 intrinsic;StringEquals/compareTo 依赖压缩/前缀不变式;未构造反例运行验证;产物是否 intrinsify 未核 |
| D2-3 | intern 的宿主(StringTable/StringPool)与 native 边界 | 部分(源码确证,未运行) | ch03 | 入口/插入策略已核;运行态 GC 弱表清理、并发与产物未测 |
| D2-4 | 编码解码/Charset 层(Charset/CharsetDecoder/ICU 边界) | 部分(源码确证,未运行) | ch03/ch07 | 非快路径字符集的数据来源/ICU 版本需外部仓与产物核;运行对拍未做 |
| D2-5 | System.arraycopy 的语义与实现选择(范围检查/重叠/引用屏障/primitive 类型边界) | 部分(源码确证,未运行) | ch03 | 冻结产物实际走 Java/native/intrinsic 哪条、是否内联未核(需 odex/aot_map);常量/变量门与 typed 阈值已分清 |
| D2-6 | Arrays 工具方法(sort/binarySearch/equals/copyOfRange)的算法与边界 | 部分(源码确证,未运行) | ch03 | Arrays.equals 的 primitive 向量循环/Object[] 逐元素/范围重载已核;DualPivotQuicksort 内部阈值/插入排序与部分类型内部未展开;运行对拍未做 |
| D3-1 | ByteBuffer 状态模型(position/limit/capacity/mark;order)与状态机 | 部分(源码确证,未运行) | ch04 | — |
| D3-2 | Direct/Heap/Mapped ByteBuffer 的实现分派与 putChar 路径差异 | 部分(源码确证,未运行) | ch04 | — |
| D3-3 | putChar 端到端链路与 intrinsic 边界:源码具备 intrinsic ≠ 冻结调用点已 intrinsify | 部分(源码确证,未运行) | ch04(纵切) | 冻结产物身份/调用点 intrinsify 状态未知(odex/aot_map 缺);未反汇编 |
| D3-4 | ByteOrder/swap 语义(nativeByteOrder、LITTLE/BIG、Short.reverseBytes 触发条件) | 部分(源码确证,未运行) | ch04(纵切) | — |
| D3-5 | slice/duplicate/asReadOnlyBuffer 的共享 vs 复制语义与视图状态 | 部分(源码确证,未运行) | ch04 | 视图与 Cleaner 生命周期的运行验证未做 |
| D3-6 | Memory/Unsafe/VarHandle 的分层与公开边界(libcore Memory 内部 API vs jdk.internal.misc.Unsafe vs VarHandle) | 部分(源码确证,未运行) | ch04 | RV64/AArch64 源码指令已对照;设备 CPU 特性(LSE 等)与冻结调用点 intrinsic 状态未核 |
| D3-7 | DirectByteBuffer 生命周期与 Cleaner/引用队列的回收路径 | 部分(源码确证,未运行) | ch04 | MemoryRef.free 的其它调用入口/别名安全/设备行为未核;三种所有权与 Cleaner 语义已核 |
| D3-8 | 对齐/非对齐语义(Java 层无对齐要求;benchmark aligned=false 用 position=1;硬件表现差异归性能分析) | 部分(源码确证,未运行) | ch04(纵切) | — |
| D4-1 | 常用集合实现(HashMap/ArrayList 等)的结构与扩容语义 | 部分(源码确证,未运行) | ch05 | — |
| D4-2 | 并发容器(ConcurrentHashMap/BlockingQueue)的并发协议 | 部分(源码确证,未运行) | ch05 | CHM 主要写路径、TreeBin 拆分/删除/balanceDeletion 源码已核;并发运行压力测试未做 |
| D4-3 | 线程池/Future/Executor 的状态机(runState、队列、拒绝策略) | 部分(源码确证,未运行) | ch05 | — |
| D4-4 | 锁/AQS/park/unpark 的入口与 JMM 边界 | 部分(源码确证,未运行) | ch05 | AQS Condition 源码与 RV64/AArch64 对照已核;定时/中断运行验证、设备/产物及非 futex 构建未测 |
| D4-5 | atomic/fence 与 RV64/AArch64 架构入口(通用 Java 实现 vs 架构相关屏障) | 部分(源码确证,未运行) | ch05 | RV64/AArch64 源码指令序列已对照;设备 CPU 特性与产物 intrinsic 状态未核 |
| D4-6 | 装箱/缓存(Integer/Long cache 等)与分配热点 | 部分(源码确证,未运行) | ch05 | 未运行分配/对拍;产物逃逸分析/标量替换未核 |
| D5-1 | java.io 文件流栈与 libcore 原生边界(IoBridge/BlockGuard) | 部分(源码确证,未运行) | ch06 | open/read/write/close 与 socket errno 分派已核;其它 JNI 实现逐函数/设备行为未核 |
| D5-2 | NIO Channel/Selector 与 epoll 事件循环边界 | 部分(源码确证,未运行) | ch06 | native dispatcher 与运行态事件循环未核 |
| D5-3 | 网络/DNS 解析与 InetAddress/Resolver 边界 | 部分(源码确证,未运行) | ch06 | resolver 最终 native 实现未逐行 |
| D5-4 | 异常传播与 errno→IOException 映射、可观测失败(超时/断连/部分读写) | 部分(源码确证,未运行) | ch06 | 其他 syscall 包装与部分错误分支未逐行;运行态信号/中断未测 |
| D6-1 | Math/StrictMath/数值转换(parse/toString)的实现与边界 | 部分(源码确证,未运行) | ch07 | Long/Short/Byte 等同类 API 未逐条展开 |
| D6-2 | BigInteger/BigDecimal 的算法与边界 | 部分(源码确证,未运行) | ch07 | Java 算法、阈值、JNI 桥与 BoringSSL BN_mul/BN_div 算法族已核;RISC-V 具体汇编/性能未核;不宣称具体实现或收益 |
| D6-3 | 格式化/解析(Formatter、SimpleDateFormat 等)与 Locale 依赖 | 部分(源码确证,未运行) | ch07 | Formatter/SimpleDateFormat/Locale 源码链已核;运行对拍(Double/Float/日期/Locale)未做;ICU/CLDR/tzdata 具体版本未绑定;DoubleToDecimal 调用者仍未绑定 |
| D6-4 | 时间/日期(java.time、ZoneId)与 ICU 数据版本、跨仓职责 | 部分(源码确证,未运行) | ch07 | ZoneInfoData/tzdata 模块不在 libcore pin 内,具体版本需外部模块与产物核 |
| D7-1 | 反射(java.lang.reflect)实现与 native/VM 边界 | 部分(源码确证,未运行) | ch08 | Constructor 成员级检查的源码观察需运行用例确认;Field 外层门/IsUnmodifiable(含 IsMonotonic 前置)已核;产物优化未核 |
| D7-2 | MethodHandle/动态调用(java.lang.invoke)与 intrinsic/VM 边界 | 部分(源码确证,未运行) | ch08 | invoke-polymorphic 产物内联/链接状态未核 |
| D7-3 | serialization/zip 等核心库分组行为与安全边界 | 部分(源码确证,未运行) | ch08 | 过滤在本 pin 已移除;zip 损坏输入与运行对拍未做 |
| D7-4 | JCA/crypto API(libcore)↔ Conscrypt provider ↔ BoringSSL 原生 的真实边界(代表链) | 部分(源码确证,未运行) | ch08 | provider 选择、AES 代表链、SSLContext→SSL_CTX_new、默认 TrustManager/证书链、会话缓存/释放、握手正数 WANT/异常路径源码已核;完整 TLS 状态机、产品实际 provider 默认顺序/协商版本、BoringSSL RISC-V AES 选择/汇编未核 |
| D8-1 | 现有性能账本的 dataset/measure_kind/statistic/metric 口径与冻结身份 | 部分(账本已定位) | ch09 | 冻结包未绑定被测 commit/boot image/odex/native so |
| D8-2 | ByteBuffer putChar 真实 cell 的参数矩阵与 worst cell 定位 | 部分(账本已定位) | ch04/ch09 | DIRECT/HEAP 8192/8201,MAPPED 65536;历史 plan 8 组合(DIRECT/HEAP)×30=240,公开源码含 MAPPED 为 12 种可能组合;产物身份核验前不解释差异 |
| D8-3 | 产物身份/AOT-JIT/odex 获取项与离线核验路径 | 部分(缺口已记录) | ch09 | JAR/runner 与静态 DEX 方法集已取得;两个 exact odex/boot.art/aot_map 仍缺;dexdump 不证明 intrinsify |
| D8-4 | 正确性对拍计划(direct/heap/mapped × 大小端 × 对齐 × 边界值 × 不可访问 buffer) | 计划 | ch09/ch04 | 对拍用例集已列;未执行 |
| D8-5 | 计时与频率口径(Fixed-N cycles vs 正式 ns/op;geomean lower/median/upper 的解释边界) | 部分(口径已核) | ch09 | 新板测量需另行计划与授权 |
A.2 关键结论与源码依据
| 结论 | 依据(固定 pin) |
|---|---|
| String 运行时托管、count 压缩位、charAt native | libcore String.java:147-177/1209-1216/1270-1272;ART string.h:257-291/316-318、string-inl.h:44-56 |
| arraycopy 的 Java/native/intrinsic 分层 | System.java:467-499;ART java_lang_System.cc:50-165/243-251;object_array-inl.h:103-330;intrinsics_riscv64.cc:1634/2000、code_generator.cc:1704-1751 |
| CHM 迁移/TreeBin/删除 | ConcurrentHashMap.java:1022-1195/2312-2332/2384-2405/2445-2573/2794-3105 |
| AQS Condition/park | AbstractQueuedSynchronizer.java:1604-1902;LockSupport.java:177-459;ART thread.cc:352-466、jdk_internal_misc_Unsafe.cc:673-698 |
| BigInteger/BN 分派 | BigInteger.java:1594-1645/2339-2472;libcore_math_NativeBN.cpp:143-157/167-177;BoringSSL mul.cc.inc:149-195、div.cc.inc:156-340 |
| 反射/MH/序列化/Conscrypt | REFLECTION/方法链见 ch08;Conscrypt OpenSSLProvider/OpenSSLEvpCipher/NativeCrypto → BoringSSL cipher.cc.inc/e_aes.cc.inc |
附录 B:检查与验收清单
本节表格与结论的依据为 [S01](libcore 主 pin)和 [S02](ART 边界),跨仓代表链见 [S03][S04]。
| 检查项 | 状态 | 说明 |
|---|---|---|
| 固定四仓 pin 与独立身份 | 已核 | libcore/ART/Conscrypt/BoringSSL 分别记录,不比较 commit 先后 |
| 57 个来源文件 blob SHA1/SHA256 | 已核 | 固定 pin 清单的逐项记录 逐项记录 |
| 42 行问题矩阵 | 保留 | 未删行、未缩范围;见附录 A |
| 机制图 SVG+PNG | 4 张已生成 | 文章列宽重排;未做 1440/1280/390 三宽 HTML 验收 |
| 性能数据边界 | 公开稿不含内部数值 | 正文不以内部数字作结论 |
| 运行/设备/构建/反汇编 | 未执行 | 不据此宣称 intrinsify、provider、CPU 特性或吞吐 |
| 完整 TLS/BoringSSL/APEX 产物 | 未完成 | 见附录 A 各行具体缺口 |
本清单不是终验,也不替代独立公开复核与三宽渲染。
附录 C:研究方法与局限
C.1 方法
以固定 pin 的完整 blob 为单位读取方法体,先读类/方法边界再摘录行号;对 Android-changed/removed 注释块单独标注,避免把 RI 代码当活实现。先建立 42 行问题矩阵,再按章做调用链、状态/字段、正常/失败/回退、成本与可复制观察;证据分源码已回答、可复现未执行、产物/设备未验证三层。未使用全树 lazy fetch、未运行构建或设备。
C.2 性能口径与数据边界
本报告不包含内部研发测量值、私有数据集名、设备地址或旧产物哈希。正文只保留公开源码中的 cell 参数矩阵、harness 计时口径、方法学分层与产物缺口。历史 Fixed-N 与正式 native harness 分道;历史 plan 行数只作历史身份,不作为新目标平台的 eligible 或成绩。设备实验所需的镜像、频率、affinity、PMU 与产物身份字段在公开方法中列出;未执行项标记为 gap。
C.3 未完成与限制
- 未运行 Java/基准/设备,未构建 Android/APEX,未反汇编 odex;不能证明冻结调用点 intrinsify、inline 或实际 provider 顺序。
- JAR/runner 与静态 DEX 方法集已取得;两个 exact odex、
boot.art/aot_map/profile仍未取得,AOT/JIT 实际状态未知。 - Conscrypt 完整 TLS 状态机、TrustManager 全部分支、会话缓存运行行为与 BoringSSL RISC-V AES 实际选择未完成;只核到代表源码入口。
- Character/IO 仍有个别子路径未逐函数核;具体见附录 A 对应行。
- 图表为文章列宽示意,未做手机竖排替代与三宽 HTML 视觉验收。
- 独立 delta 的
Field.javablob 在本次复核缓存缺失(GIT_NO_LAZY_FETCH=1cat-file -erc=128),Field.get/set 本轮未复核;这是覆盖缺口,不改写既有 pin 结论,也不冒充复核通过。
附录 D:Benchmark 家族、执行模式与测量前置(公开增补)
- 状态:未公开候选专章(源码清点;未构建、未运行、未上板)。
- libcore pin:
4ebfb391ee0109d16abe6e2bc965724b940928c5(android17-release = tagandroid-17.0.0_r1)。 - ART 边界参考:
b753cf97923c3695338d21466fa14c57b480a59a;Conscrypt/BoringSSL 仅在实际基准触达时引用各自 pin:Conscryptfd1a430323b5ab9945a15fce6c4a824aeffff685、BoringSSL0cd1f6a94b670e6b61af95a06e71e690ae8e3262;Cipher padding 还绑定 Conscryptcommon/src/main/java/org/conscrypt/OpenSSLCipher.java(blob155d757bf26892f10e3c5e80fbd8e7fe24dd41b6,SHA2563119d41e54660c56d612b3814dfc6506ba6d5e89b1b7c159d8577a8fd1205eb9)。 - 目的:把 libcore benchmark suite 的真实源码、方法、参数、执行模式、失败/回退和测量前置讲清;不把历史 Fixed-N 最差格当 目标 RISC-V64 平台 结论,不用“源码有 intrinsic”冒充“本轮调用点已 intrinsify”。
11.0 范围层级与共同方法 R1(先读)
本章区分公开固定版本的源码枚举、历史测量上下文与未来设备的可运行范围。源码清点回答套件包含什么;实际运行范围和成绩必须由对应构建、harness 与设备记录确认。
| 层 | 内容 | 本范围处理 |
|---|---|---|
| L1 历史包/已测清单 | 既有测量包的用例清单、参数、版本与记录 | 只作历史身份;不写新 目标平台 scope、不写新 目标平台 成绩、不作为 eligible 数 |
| L2 固定 pin 源码枚举 | libcore 4ebfb391 的 benchmark 类/方法/参数;ART 仅 intrinsic/ABI 边界 | 本章实际清点对象;不是完整合格清单 |
| L3 未来 目标平台 新 run | 新 eligible manifest、harness、AOT/odex/image/runtime 门、实际 slot 数 | UNKNOWN;等新 run,不用历史数填充 |
Java VM 边界:libcore/Java 基准每个平台使用独立的 Java VM 进程;跨 ISA 对照对齐的是源码/版本/配置/参数,不是同一个二进制镜像。ART 与 Bionic 的 benchmark 范围分别见对应专题;本章仅覆盖 libcore suite 与跨仓调用边界。
11.1 两个真实 suite 与运行入口
libcore pin 的 benchmarks/ 有两个并行 suite,不能混成一个历史账本口径:
| suite | 源码/构建 | 真实运行入口 | 计时与限制 |
|---|---|---|---|
| Caliper/vogar(历史家族) | benchmarks/Android.bp 的 java_test { name:"benchmarks"; srcs:["src/**/*.java"]; sdk_version:"none"; system_modules:"core-all-system-modules"; libs:["caliper-api-target","android.test.base.stubs"]; static_libs:["mockwebserver","core-tests-support"] } | README:m vogar com.android.art.testing,adb root && adb remount,cd libcore/benchmarks/src,vogar --benchmark benchmarks/regression/<Class>.java;可用 adb shell stop 降噪 | warmup/reps/timing 由外部 Caliper/vogar 控制;timeX(int reps) 方法体只写循环,不含 warmup/统计 |
| AndroidX Benchmark(JUnit4) | benchmarks/Android.bp 的 LibcoreBenchmarkTests/LibcoreBenchmarkTestsLib,src_androidx/**/*.java,androidx.benchmark_benchmark-junit4;AndroidManifest debuggable=false | README:先按 Android 官方脚本锁频,再 atest LibcoreBenchmarkTests:libcore.benchmark.FormatterTest#stringFormatNumber_allLocales;AndroidTest.xml 使用 AndroidBenchmarkRunner,抑制 EMULATOR,LOW-BATTERY,UNLOCKED | BenchmarkRule/BenchmarkState.keepRunning() 控制采样;与 Caliper 的 reps 语义不同 |
清点范围:git ls-tree 得到 106 个 .java,其中 102 个在 src/(73 个 regression/、29 个根目录)、4 个在 src_androidx/。可复现脚本 bench-inventory.py 是正则静态清点,不是完整 Java parser/runner;未过滤注释、修饰符组合和 nested 声明边界;统计:time*/test* 声明(含裸 time,不去重嵌套类)730 个,其中按路径标签 src_legacy 724、AndroidX 6;另有 22 个 @Test 注解、64 个 @Param 声明。730 是静态声明数,不是 cell 数,也不是 “724 个 Caliper eligible cell”;717/735/750 是不同文本筛选口径,不作为 cell 总数。完整文件/方法与 @Param 静态清单在 benchmark 覆盖矩阵、公开证据记录 与可复现脚本 bench-inventory.py;候选 manifest 见 候选清单。所有 106 文件从固定 pin 读取,代表文件的 Git blob 在 固定 pin 清单 绑定。
11.2 家族范围表(实际类名,不硬凑 cell)
| 家族 | 类数 | 静态方法候选 | 代表类(均在固定 pin) |
|---|---|---|---|
| Buffer/Memory | 3 | 36 | ByteBufferBenchmark、ByteBufferBulkBenchmark、ByteBufferScalarVersusVectorBenchmark |
| String/Charset | 18 | 78 | StringBenchmark、StringEqualsBenchmark、StringToBytesBenchmark、CharsetUtf8Benchmark、CharacterBenchmark、StringTest |
| Collections/Concurrency | 18 | 91 | HashedCollectionsBenchmark、PriorityQueueBenchmark、ThreadLocalBenchmark、SystemArrayCopyBenchmark、ReferenceBenchmark |
| Math/Numeric/Format | 20 | 245 | MathBenchmark、StrictMathBenchmark、benchmarks.regression.BigIntegerBenchmark、DoubleBenchmark、FormatterBenchmark、DecimalFormatBenchmark |
| IO/NIO/Network | 9 | 14 | FileBenchmark、DnsBenchmark、SSLSocketBenchmark、URLConnectionBenchmark、ChecksumBenchmark |
| Time/Locale/ICU | 8 | 32 | LocaleDataBenchmark、TimeZoneBenchmark、DateFormatBenchmark、SimpleDateFormatBenchmark、CollatorBenchmark |
| Reflection/MH | 6 | 74 | ReflectionBenchmark、MethodInvocationBenchmark、FieldAccessBenchmark、MethodHandlesTest、ClassTest |
| Serialization/Zip | 8 | 43 | SerializationBenchmark、XmlSerializeBenchmark、ZipFileBenchmark、ZipFileReadBenchmark、JarFileBenchmark |
| Crypto/Security | 6 | 17 | CipherBenchmark、CipherInputStreamBenchmark、MessageDigestBenchmark、SignatureBenchmark、KeyPairGeneratorBenchmark |
| Other/misc benchmarks | 7 | 100 | ArrayCopyBenchmark、GetSystemPropertyBenchmark、InvokeInterface、DoPrivilegedBenchmark、EqualsHashCodeBenchmark、ExpensiveObjectsBenchmark、SchemePrefixBenchmark(均为真实 benchmark 类,100 个 time*) |
| Standalone/manual | 1 | 0 | benchmarks.BigIntegerBenchmark:根目录,注释自述 not structured as proper benchmark,main 用 System.nanoTime() 调 repeatInner/harmonic/toString/modPow/modInverse;不是 Caliper time*(reps) |
| Helpers | 2 | 0 | benchmarks.Foo、benchmarks.regression.R;Foo 是 helper 类,R 无 time*;pin 中不存在 used 类 |
注意:家族表为可读 simple name;唯一标识以 benchmark 覆盖矩阵 的 fqcn_class_list 为准,两个 BigIntegerBenchmark 必须用 FQCN 区分。
FQCN/文件边界(L1):固定 pin 同时存在 benchmarks.BigIntegerBenchmark(根目录,standalone/manual)与 benchmarks.regression.BigIntegerBenchmark(回归目录,Caliper 1024-bit 三方法),是两个 FQCN;106 个 Java 文件对应 106 个 unique FQCN,不能按 105 个简单类名合并。家族表和 manifest 使用完整 FQCN/路径;106 个文件中有 helper/standalone,不等于 106 个 Caliper 类。
L2 执行分类:src/ 的 102 个文件是 legacy 源码路径,不等于 102 个 Caliper 类或 724 个 eligible cell;regression/ 下多数是 Caliper time*(int reps),根目录含 standalone/manual(如根 BigIntegerBenchmark)与 helper;src_androidx/ 4 个类使用 @Test+BenchmarkRule。730 是静态声明数,不是 runner 格数。regression BigIntegerBenchmark 的 1024-bit 结论不外推到根 BigIntegerBenchmark。
范围边界:本章仅使用 libcore 固定 pin 的 suite 源码与相关跨仓调用边界。历史 Fixed-N 测量与正式 harness 结果分别记录,不能把历史清单直接当成新平台的可运行范围。
包含:src/benchmarks/**/*.java、src_androidx/**/*.java、benchmarks/Android.bp、AndroidTest.xml、AndroidManifest.xml 与 README。排除:外部 Caliper/vogar 实现源(不在 libcore pin)、历史未公开账本(不纳入本范围)、旧平台成绩、未执行的本轮设备测量。suite 分账:Caliper 的 time*(int reps) 与 AndroidX 的 @Test+BenchmarkRule 使用不同计时协议,家族表同时列出但不得合并为同一 cell 口径;StringTest、FormatterTest、MethodHandlesTest、ClassTest 属 AndroidX。
11.3 代表性深链(按真实源码与参数)
11.3.1 ByteBufferBenchmark.timeByteBuffer_putChar
源码:benchmarks/src/benchmarks/regression/ByteBufferBenchmark.java:276-284;参数定义 L42-49。
- 参数:
byteOrder ∈ {BIG,LITTLE}、aligned ∈ {true,false}、bufferType ∈ {DIRECT,HEAP,MAPPED};newBufferL51-78:size = aligned ? 8192 : 8192+8+1(即 8201),position(aligned?0:1)在 L76;MAPPED 分支 L61-73 执行raf.setLength(8192*8)并映射 65536 字节,不是 8192。 - 方法体:外层
reps,每次position(aligned?0:1),内层 1024 次putChar(' ');Caliper 只提供reps,warmup/统计在外部 harness。 - 调用链:
DirectByteBuffer.putCharL450/L457/L444 →Memory.pokeShortL365-372 →pokeShortNative→libcore_io_Memory.cpp:164-166或 ART intrinsic(RV64ShL378-381、arm64StrhL722-726)。Heap:HeapByteBuffer.putCharL508-522 直接写 Javabyte[];不是“必解释”,JIT/AOT 仍可优化。 - 正常/失败/回退:正常写 position 后前进;失败
BufferOverflowException、只读ReadOnlyBufferException、已释放IllegalStateException;无自动回退,Direct/Heap 是构造类型决定的路径。 - 执行模式:native JNI、ART intrinsic、AOT/JIT 或解释器都可能;源码具备 intrinsic ≠ 冻结调用点已 intrinsify。缺 odex/aot_map,不能判定该 benchmark 调用点实际路径。
- 成本:Direct 路径包含状态检查、
Memory.pokeShort的 swap 分支和 native/intrinsic 边界;Heap 路径是 Java 数组写。量级需 目标 RISC-V64 平台/Arm64 对照平台 实测与 PMU,不由源码常量推断。
11.3.2 StringEqualsBenchmark 的 8 个方法
源码:benchmarks/src/benchmarks/regression/StringEqualsBenchmark.java:197-266。
- 方法:
timeEqualsNull、timeEqualsShort、timeEqualsMedium、timeEqualsLong、timeEqualsVeryLong、timeEqualsNonWordAligned、timeEqualsEnd、timeEqualsNonString;输入由 L77-167 的短/中/长/超长/非字对齐/尾部/非 String 对象表提供,无@Param。 - 调用链:Caliper
time*→String.equals(libcoreString.java:1660-1688的 Java 循环)→length()/charAt()(charAt@FastNative);ART 可能用StringEqualsOptimizations/StringEqualsintrinsic(intrinsics_list.h:214、intrinsics_riscv64.cc:1141-1256)。是否 intrinsify 仍需产物。 - 正常/失败/回退:null 返回 false;非 String 返回 false;空/不同长度/不同字符走相应分支;没有异常回退。压缩/非压缩的
count差异只在 ART 压缩不变式(IsASCII1..0x7F、排除 NUL)成立时安全,Javaequals仍是最终语义。 - 执行模式:Java 循环 +
charAtnative/intrinsic;Caliper 负责 reps/warmup。timeEquals*的布尔返回值被丢弃、没有 sink/checksum;ARTStringEquals标为kReadSideEffects|kCanThrow,但无机器码不能判定 AOT/JIT 是否消除计算。若用产物解释 ns/op,需独立 checksum variant 或机器码/PMU 证明;不能悄悄改原 harness 仍称同 cell。该 benchmark 测equals家族,不是hashCode。
11.3.3 SystemArrayCopyBenchmark 的 8×18 参数矩阵
源码:benchmarks/src/benchmarks/SystemArrayCopyBenchmark.java:22-97。8×18 是源码参数轴(8 个 primitive 方法 × 18 个长度),不是未来 eligible cell 数;eligible 以未来 run 的 manifest/harness 为准。
- 参数:
arrayLength ∈ {2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144};8 个方法分别复制 char/byte/short/int/long/float/double/boolean 数组,全部是 primitive 数组,源和目标同长度、同类型、无重叠;没有Object[]/引用元素用例。 - 调用链:
System.arraycopy(Object,...)→ ART nativejava_lang_System.cc:50-165,或编译器 intrinsic:generic 对象数组kSystemArrayCopyThreshold=128;typedSystemArrayCopyByte/Char/Int的常量阈值是384/sizeof(element),变量长度运行期仍用 128(intrinsics_riscv64.cc:1634/2000、code_generator.cc:1704-1751)。Java char[] 专用方法的 32 阈值属于 libcore 内部专用方法,不是任意 public arraycopy 的阈值。 - 正常/失败/回退:null → NPE;越界 → AIOOBE;不兼容引用 → ArrayStoreException;同数组
src_pos<dst_pos在 intrinsic 门里保守转 slow(不证明区间实际相交);intrinsic 不适用时走 native。benchmark 本身不测重叠、负长度或跨类型。 - 执行模式:native memmove、Java 循环、ART intrinsic 或 AOT/JIT;实际选择需产物。成本:复制长度/类型与是否走 intrinsic 影响最大;这 8 个 primitive 方法只复制 primitive 元素,元素复制本身不存在 reference-element read barrier 或 card marking,不能用一般引用数组的屏障机制解释其成本。数组对象引用本身的读取/分配可能有 runtime 成本,但那是另一层,与元素引用复制屏障分开;引用数组(含
Object[]/跨类型ArrayStoreException)的屏障与检查只能另列用例讨论,不能套到本矩阵。也不能只看 128 这个阈值数字。
11.3.4 CipherBenchmark 的 provider×算法×模式矩阵
源码:benchmarks/src/benchmarks/regression/CipherBenchmark.java:51-80/134-144。
- 参数:
Algorithm=AES;Mode ∈ {CBC,CFB,CTR,ECB,OFB};Padding ∈ {NOPADDING,PKCS1PADDING};keySize ∈ {128,192,256};inputSize ∈ {16,32,64,128,1024,8192};Implementation ∈ {OpenSSL,BouncyCastle}。setUp用Cipher.getInstance(cipherAlgorithm, providerName)显式 provider,再init。 - 方法:
timeEncrypt/timeDecrypt在reps内cipher.doFinal(DATA,0,inputSize,output);DATA 为 8192 字节,IV 16 字节(L35-44)。 - 调用链:Cipher 选择(
Cipher.java:2800-2942)→ ConscryptOpenSSLEvpCipher→NativeCrypto.EVP_CipherInit_ex/EVP_CipherUpdate/EVP_CipherFinal_ex→ JNIcommon/src/jni/main/cpp/conscrypt/native_crypto.cc:4135/4188/4241(Conscrypt fd1a4303)→ BoringSSLcipher.cc.inc:101/638/666、e_aes.cc.inc:162-173、cbc.cc.inc:24-63/65-132(BoringSSL 0cd1f6a9)。跨仓行号以这两个 pin 的绑定为准;若绑定失效,本段降为跨仓 gap。BouncyCastle 是另一 provider,不走这条 Conscrypt 链。 - 正常/失败/回退:
NoSuchAlgorithm/NoSuchPadding(选择失败)、InvalidKey/InvalidAlgorithmParameter(init)、BadPadding/IllegalBlockSize(final),以及ShortBufferException。本 pin 的 Conscrypt 对PKCS1PADDING先拒绝:OpenSSLCipher.java:80-91只声明 NOPADDING/PKCS5/PKCS7,:203-214在 padding normalization 失败时抛NoSuchPaddingException;因此官方 CipherBenchmark 的 AES+PKCS1PADDING 在 Conscrypt 链上首先选择/配置失败,不能把它当 PKCS5 块填充计算 8208,也不能据此说会跑到 CBC/ECB ShortBuffer。保留事实:output固定 8192;若未来做 CBC+PKCS5+inputSize=8192 的独立诊断变体,才可能出现 8208 输出和 ShortBuffer 风险(不是官方当前 cell)。BouncyCastle 分支未核,不替 BC 断言。不能宣称所有笛卡尔格可执行;诊断用getOutputSize或独立 variant,不改官方 benchmark。provider 选择阶段可继续候选,已选 SPI 的 doFinal 不自动迁移;本 benchmark 显式指定 provider,不证明产品默认 provider 顺序。 - 执行模式:Java provider/SPI + JNI + BoringSSL native;AOT/JIT 可能优化 Java 层,但密码学主体在 native。成本包含 JNI、EVP 上下文、AES 实现选择;缺产物/CPU 特性,不做收益承诺。
11.3.5 BigIntegerBenchmark 的 1024-bit cell
源码:benchmarks/src/benchmarks/regression/BigIntegerBenchmark.java:23-47。
- 方法:
timeRandomDivision、timeRandomGcd、timeRandomMultiplication;每个方法用new BigInteger(1024, r)生成两个随机操作数,在reps内分别divide/gcd/multiply。 - 关键边界:1024 bit 最多 32 个 32-bit limb;乘法两侧都 <50 → Java
multiplyToLen;除法mag.length<40→divideKnuth。因此该 cell 不触发 BoringSSLBN_mul/BN_div的 ≥50/≥40 阈值,不能拿它推断 native 大数路径成本。 - 正常/失败/回退:除零 →
ArithmeticException(本基准随机数通常非零);无回退;BoringSSL 大数路径需要更大操作数或另行构造 cell。 - 执行模式:Java 大数算法 +
@IntrinsicCandidate的implMultiplyToLen/implSquareToLen(是否 intrinsify 由产物决定);没有 JNI,除非达到 BN 阈值。divide/gcd/multiply返回值也被丢弃、无 sink/checksum;无机器码不能判定 DCE,需独立 checksum variant 或产物证据。 - 成本:乘法 O(n·m)、Knuth 除法 O(n·m),还有
MutableBigInteger的分配/规范化;不做未测收益承诺。
11.3.6 其余代表性家族(只列入口与边界)
HashedCollectionsBenchmark:28-60:timeHashMapGet/timeHashMapGet_Synchronized/timeHashtableGet/timeLinkedHashMapGet/timeConcurrentHashMapGet;测 get 路径,不含并发写/扩容/CHM TreeBin。MessageDigestBenchmark:56-146:Algorithm/Provider+ 大数组/小 chunk/Heap/Direct ByteBuffer;Direct 路径会触达ByteBuffer与 native digest,不能用它推断所有MessageDigestprovider。SerializationBenchmark:36-150:timeRead/Write{IntArray,ArrayListInteger,String,ObjectStreamClass}、timeWriteEveryKindOfField;触达句柄/类描述符/字段块,不测过滤或恶意流。ReflectionBenchmark:24-143:timeMethod_invoke*、timeField_get/setInt、timeClass_newInstance等;MethodHandlesTest:65-164是 AndroidX JUnit4 的 MethodHandle 对照,ClassTest:33-50是 getSimpleName/isAnonymousClass;两者 suite/计时不同。LocaleDataBenchmark:33、TimeZoneBenchmark:22-55、DateFormatBenchmark、SimpleDateFormatBenchmark、CollatorBenchmark:Locale/ICU/tzdata 路径;数据版本仍由 ICU/tzdata 外部模块决定。
11.4 目标 RISC-V64 平台 vs Arm64 对照平台 测量前置(计划,未执行)
每个 cell 必须先绑定下表;缺失项按模式处理:full-AOT 必须有 exact odex/boot/JAR,JIT/nterp 不强求不存在的 odex 但必须有 DEX/runtime/模式身份及适用的机器码观测;缺频率或 PMU 只阻断对应比较/归因,不把所有模式一并判为不可比较。
| 维度 | 必填项 | 说明 |
|---|---|---|
| 平台镜像 | 目标 RISC-V64 平台的硬件配置/可重构平台版本、Arm64 对照平台 板/SoC、Android build fingerprint、内核版本 | 不能把旧平台或历史未公开成绩贴到目标平台 |
| 频率 | 目标 RISC-V64 平台 可重构平台 实际频率/时钟源、Arm64 对照平台 频率、是否固定频/锁频 | 可重构平台 频率归一化不能等同真实同频硬件;ns/op×GHz 只是周期代理 |
| hart/affinity | 目标 RISC-V64 平台 hart 数/绑核、Arm64 对照平台 CPU/affinity、IRQ/隔离 | 同 cell 参数与工作量对齐;记录线程数与绑定 |
| ISA/codegen | RV64 扩展/编译目标、AArch64 目标、ART 编译器后端 | 不能把 RV64 lr/sc/Sh 序列外推到 AArch64;实际 codegen 需产物 |
| runtime/boot | ART runtime 版本、boot image、odex/JAR、AOT/JIT/nterp 状态 | full-AOT 绑定适用 exact odex/boot/JAR;JIT/nterp 绑定 DEX/runtime/模式与适用观测,不把不存在的 odex 作为共同前置;不宣称调用点 intrinsify |
| GC/allocator | GC 类型/堆参数/allocator、是否 stop-the-world | 分配密集 cell 必须固定并记录 |
| 计时 | 标准原 harness(Caliper/AndroidX)的 ns/op 或 throughput、独立进程数、样本数、warmup、reps、统计量 | 标准原 harness 报告与 Fixed-N diagnostic 分表;实际独立进程/样本数与补样停止规则由新实验运行前计划确定,历史 3p/6p 只作方法示例;报告波动/不确定性,不新设达标阈值 |
| PMU | cycles/instructions、cache/TLB/branch、频率、multiplex/valid ratio、raw | 目标是避免 multiplex;无缩放且完整 runtime 时 cycles+instructions 可同采,cache/TLB/branch 按假设逐个增加;baseline subtraction 先验证可加性,不自动扣因果;time*live_GHz 是周期代理,cycles/instructions 是 CPI,均不是实测频率 |
运行顺序建议:先按执行模式核对产物——full-AOT 核对 exact odex/boot/JAR,JIT/nterp 核对 DEX/runtime/模式及适用观测(不要求不存在的 odex);再同 cell 参数对齐,再按运行前计划确定的独立 Java VM 进程数做 warmup/measure,最后才做 目标 RISC-V64 平台/Arm64 对照平台 对照。标准原 harness(Caliper/AndroidX)原始结果必须来自实际运行记录(自适应 N 可两端不同);libcore 的原始 harness 运行记录当前未取得。R1 只把历史 90%/10%/3p6p 作为历史执行记录,不设为所有新 目标平台 cell 的统一标准;新 目标平台 应先预定义比较目标/误差策略。Fixed-N 要求两端相同 class/method/params/thread/N,≥3 独立进程;1p 只探索、3p 全观测,是否扩展到 4–6 由新计划预定义,不能把旧阈值当通用目标。标准原 harness(Caliper/AndroidX 报告)与 Fixed-N 分轨,不是把 Java benchmark 说成 native 代码;full-AOT 需 exact odex/boot/JAR,JIT/nterp 不强求不存在的 odex,但需 DEX/runtime/模式身份及适用机器码观测。不得先看历史账本最差格再反推原因。
11.5 离线可复现检查与缺口
已执行(只读):
PIN=4ebfb391ee0109d16abe6e2bc965724b940928c5
git -C sources/libcore.git ls-tree -r --name-only $PIN benchmarks
git -C sources/libcore.git show $PIN:benchmarks/README.md
git -C sources/libcore.git show $PIN:benchmarks/Android.bp
git -C sources/libcore.git show $PIN:benchmarks/AndroidTest.xml
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/ByteBufferBenchmark.java | sed -n '40,80p;270,290p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/StringEqualsBenchmark.java | sed -n '190,270p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/SystemArrayCopyBenchmark.java | sed -n '18,100p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/CipherBenchmark.java | sed -n '30,155p'
git -C sources/libcore.git show $PIN:benchmarks/src/benchmarks/regression/BigIntegerBenchmark.java | sed -n '18,50p'
未执行:vogar --benchmark、atest LibcoreBenchmarkTests、任何设备/板卡运行、全树构建、odex/aot_map 获取、PMU 采集。缺口:Caliper/vogar 实际 warmup/reps 默认值、测试包/产物身份、目标 RISC-V64 平台/Arm64 对照平台的频率与 LSE/ISA 特性、实际 provider/AES 选择、APEX 内布局/odex。不得以静态源码或历史账本宣称 PASS。本文只给出固定 pin 的源码范围与测量前置条件,不以历史清单充当新平台范围,未执行项不能写为设备验收结果。