Android libcore 端到端机制与 Benchmark 前置研究(公开版)

由 libcore-runtime-mechanism-research.md 生成
字段值
文档类型技术调研报告
版本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/板卡

摘要

背景与现状

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

本章目的:用一个真实存在、口径可查的性能 cell(ByteBufferBenchmark.timeByteBuffer_putChar)把「Java 库状态机 → native 边界 → ART intrinsic 选择 → 产物身份缺口」串成一条可复述、可验证的链,并明确哪些结论现在能下、哪些不能下。

2026-10-20T23:07:46.810925 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/

手机可横向滑动查看本图

图 2:putChar 的 Direct/Heap 路径;Memory→ART 选择条件区分调用点 intrinsic 与 JNI native callee;未核的 callee AOT body 单列;Heap 也可能被 JIT/AOT 优化。来源:ch01/ch04、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(' ');
        }
    }
}

要点:

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/markjava/nio/Buffer.java 及其子类经典 NIO 状态;putChar 一次前进 2 字节(Character.BYTES)
nextPutIndex(int nb)Buffer.java:741-748limit - p < nb → BufferOverflowException;否则 position += nb
ByteBuffer.nativeByteOrderByteBuffer.java:2264当前 ByteOrder 是否等于硬件原生序;order() 维护(L2294-2302)
ByteOrder.nativeOrder()ByteOrder.java:78运行平台原生序(RV64/AArch64 均为小端)
DirectByteBuffer.memoryRef.isAccessibleDirectByteBuffer.java:451内存段可访问性;不可访问抛 IllegalStateException("buffer is inaccessible")
isReadOnlyDirectByteBuffer.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 失败、边界与回退

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'

未执行(可复现条件不足,如实标注):

1.7 缺口与下一步

  1. 产物身份(阻断“是否 intrinsify”结论):JAR/runner 与 30 方法静态集合已取得;仍需两个 exact benchmark/runner odex、被测镜像 boot.art/boot.oat、aot_map/profile、dalvik.vm.*;拿到后先离线 oatdump/反汇编。
  2. 基准约定:reps 取值与 JVM 参数(AOT/JIT 开关)需从测试包/CI 记录补齐;当前只知方法体结构。
  3. 正确性对拍计划(未执行):direct/heap/mapped × 大小端 × 对齐/非对齐 × char 边界值(0、0x7F、0x80、0xFF、0x100、0xFFFF)× 越界/只读/非法访问,逐字节对照 Memory.pokeShort 参考实现。
  4. 性能解释:在产物身份核验前,不把该历史统计差异归因于 intrinsic 缺失、JNI 往返或硬件差距中的任何一项;后续需 PMU + 机器码 + 对照实验三方依据。

1.8 与其它章节的边界

第 2 章:libcore 总体架构——仓库/模块、构建与 APEX、类库↔VM↔内核的分界

2026-10-20T23:07:46.424404 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/

手机可横向滑动查看本图

图 1:libcore 源码到 ART APEX/bootclasspath 与运行时边界;红框为产物/设备缺口。来源:ch02、ART 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" ]

由此可下的结论与不能下的结论(按依据分层):

正常/失败/回退(构建层,源码可见部分):

2.3 类库 ↔ VM 的分工:注解与 native 声明

libcore 通过两种显式接口与 ART 交互:

  1. @FastNative / @CriticalNative 注解(dalvik/annotation/optimization/):标记可被 ART 特殊调用约定处理的 native 方法。例:Memory.pokeShortNative(luni/.../Memory.java:372,ch01 已给链);VMRuntime.is64Bit()(libart/.../VMRuntime.java:315-316)。
  2. native 声明本身:libcore 声明 native,实现由 JNI(本仓 luni/src/main/native/*.cpp)或 VM 内置实现(ART 侧)提供。例:VMRuntime 的 bootClassPath()(L268)、vmVersion()(L282)、properties()(L261)。

选择点模型(重画,三个维度不是互斥的三类):

  1. 编译器机制有两种相关但不同的形式:①调用点替换/内联——普通 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;不能拿 native charAt 当“普通 Java 方法可被 intrinsify”的例子,String.length() 是 Java 方法这一事实保持;System.arraycopy 是 @FastNative 声明 + SystemArrayCopyOptimizations(intrinsics.h:244)。
  2. @FastNative/@CriticalNative:它们描述调用约定/入口形态,不保证该调用点一定被 intrinsify;native 方法还可以有 AOT intrinsic callee body。
  3. 可用代码与实际调用点: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 中已有的编译代码(是否覆盖本调用点未知)

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;

2.5 失败、边界与成本(本章范围内)

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 缺口与下一步

  1. bootclasspath/APEX 装配链:装配主干已按 ART 公共源码核到(com.android.art-base-defaults.bootclasspath_fragments → art-bootclasspath-fragment.contents → dex_preopt.profile);仍待核的是 APEX 内文件布局、odex/oat 参数与设备安装态(需产物/设备)。
  2. JNI 注册表:libcore native 的静态注册(register_*)与动态查找(dlsym)细节需逐文件核对(ch06/ch08 展开时补齐)。
  3. 模块边界验证:mmodules/intracoreapi/api/intra/current.txt 与实际 core-oj/core-libart 类清单的对应关系可用构建产物核对(未执行)。

第 3 章:String、字符编码与数组复制——运行时托管的字符数据

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,本批补核):

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)

机制结论:

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

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.
                ...

机制要点:

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)按以下顺序执行:

  1. 空指针与数组性:src==null/dst==null → NPE(消息 src == null/dst == null);不是数组 → ArrayStoreException(“source/destination ... is not an array”)。
  2. 边界:负的 srcPos/dstPos/count,或 srcPos > src.length-count、dstPos > dst.length-count → ArrayIndexOutOfBoundsException,消息包含五个参数。
  3. 同组件类型:srcComponentType == dstComponentType 时按 primitive 宽度分组做 Memmove——boolean/byte 按 1 字节、char/short 按 2 字节、int/float 按 4 字节、long/double 按 8 字节;引用数组走 AssignableMemmove。分组是“同宽度位拷贝”,不改变元素类型语义。
  4. 组件类型不同:若目标组件是 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):

RV64 编译期 intrinsic(compiler/optimizing/intrinsics_riscv64.cc):

3.4 Arrays:排序与查找的委托结构

3.5 纠正两个常见误述

  1. “String 内部就是一个 byte[] value(Java 字段)”:在本 pin 不成立——value 已注释,字符数据由运行时托管(§3.1)。描述压缩表示时必须标注“ART 原生侧布局,跨仓”。
  2. “解释执行时 String/数组操作一律走 JNI”:不成立——equals/hashCode 是 Java 循环 + charAt native/@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 缺口与下一步

  1. String 压缩与 intern:count 的压缩位/长度编码、CharAt 分支、intern() 的 InternWeak→InternTable::Insert 已按 ART 参考 pin 核;仍未做运行态压缩选择、GC 弱表清理与并发行为验证。
  2. arraycopy 的产物形态:native/Java 快路径/RV64 intrinsic 三条实现已按源码核;冻结产物实际选择哪条、是否内联,需要 odex/aot_map/反汇编(缺)。
  3. 字符集覆盖:UTF-8/ISO-8859-1/US-ASCII/UTF-16BE 以外的字符集走通用 encoder;ICU 数据版本已在 ch07 标明为运行时/外部模块属性。

第 4 章:Buffer、Memory、Unsafe 与 VarHandle——状态、视图与生命周期

4.1 Buffer 状态模型与视图

Buffer 的经典状态(position/limit/capacity/mark)与检查逻辑见 Buffer.java(nextPutIndex(int) L741-748 → BufferOverflowException;checkIndex 越界 → IndexOutOfBoundsException)。视图的关键在于共享 vs 复制:

状态/字段速查:

字段/方法位置(pin)语义
byte[] hbHeapByteBuffer堆内存储;视图共享
MemoryRef memoryRefDirectByteBuffer L99共享内存引用(地址/可访问性/释放标志)
Cleaner cleanerDirectByteBuffer L98仅外部地址路径创建(见 §4.3)
isReadOnlyDirect/Heap只读视图写 → ReadOnlyBufferException
memoryRef.isAccessible/isFreedDirectByteBuffer释放后写入 → IllegalStateException("buffer is inaccessible")

状态迁移(Buffer.java,pin 内逐字行):

方法行号迁移
mark()L417mark = position
reset()L433mark < 0 → InvalidMarkException;否则 position = mark
clear()L458position=0; limit=capacity; mark=-1(不擦数据)
flip()L486limit=position; position=0; mark=-1
rewind()L508position=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 非移动数组(无 Cleaner,GC 回收);② JNI 外部地址(无 Cleaner,creator 所有,originalBufferObject 只用于共享生命周期);③ fd/unmapper(Cleaner 负责 unmap)。三者共享 Java 语义,但释放责任与失败模式不同,不能合并成“Direct 都靠 Cleaner”。

4.3 Memory / Unsafe / VarHandle:三代访问面的分层

接口位置(pin)典型能力边界
libcore.io.Memoryluni/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.Unsafeojluni/src/main/java/sun/misc/Unsafe.javaallocateMemory/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.VarHandleojluni/src/main/java/java/lang/invoke/VarHandle.java签名多态访问器:get/set(L496/L522)及 volatile/acquire/release/opaque 等模式;标注 @IntrinsicCandidate标准 Java 面;访问方法可被 VM intrinsic 化,冻结产物是否 intrinsify 需产物依据

分层语义:

4.4 生命周期与 Cleaner

4.5 正常/失败/回退(本章范围)

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 缺口与下一步

  1. VarHandle → VM 实现的映射:@IntrinsicCandidate 访问器在 ART 中的生成路径需按 ART 参考 pin 逐项核对(跨仓,未做)。
  2. Memory 的 unsafeBulkGet/Put 实现:native 侧(libcore_io_Memory.cpp)与 GC 屏障的关系待核。
  3. Mapped 文件语义:FileChannel.map 的 unmap 时机与 Cleaner 交互属系统行为,需运行观察(未做)。

第 5 章:集合与并发——容器结构、线程池状态机与 JMM 边界

2026-10-20T23:07:47.112617 image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/

手机可横向滑动查看本图

图 3:putVal 的表未初始化/空桶 CAS/MOVED/桶锁条件;transfer→split→last-migrator 链;TreeBin 删除/balanceDeletion 为独立删除分支。来源:ch05、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 必删除。

图4 AQS Condition:等待者与 signal / 释放参与者 signal 只清 COND/enqueue,不表示立即获锁;unpark 由 signal 或释放/队列推进发起,park 本身不调用 unpark。 等待线程:await / park await() 预检查中断 newConditionNode()(OOME 慢路) enableWait() COND | WAITING,并 release 锁 等待循环 canReacquire() 被唤醒后先 reacquire,再按 cancelled/interrupted 返回或抛出 LockSupport.park() ART Thread::Park → park_state_ / futex timed boolean 方法才看 timeout/OOME-null;void await 不套用 中断恢复与异常抛出在 reacquire 之后判定 唤醒后重试 持锁 signal / 释放线程 signal() / doSignal()(持锁) 清 COND 状态 enqueue 到同步队列 等待者从条件队列转入 CLH 队列 条件性 LockSupport.unpark(thread) 仅唤醒,不授予锁;等待者仍要竞争 unlock() / release() 成功后 signalNext(head) signalNext 清 WAITING 后 unpark(s.waiter) signal 不等于立即获锁;是否立即 unpark 取决于节点状态与队列推进 不能画成 park → unpark 唤醒等待者(虚线) CLH 同步 队列 ART park/unpark 的实现分支(编译期,不是运行期降级) #if ART_USE_FUTEXES:park_state_ 0=许可可用 / 1=无许可 / 2=等待者已入 futex; park 用 FUTEX_WAIT_PRIVATE,unpark 用 exchange(0)+FUTEX_WAKE。 #else:noop + sched_yield(源码 #warning),不是同一套阻塞/许可语义;不能把 futex 行为写成所有构建的既成事实。 依据:ch05、AbstractQueuedSynchronizer.java:1604-1902、LockSupport.java:177-459、ART thread.cc:352-466、jdk_internal_misc_Unsafe.cc:673-698。 timed boolean 方法的 timeout/OOME-null 返回只适用于对应 timed 方法,不概括 void await;signal 不保证立即获锁或立即 unpark。

手机可横向滑动查看本图

图 4:AQS 等待线程(await/release/wait/reacquire)与持锁 signal/释放线程分栏;signal 清 COND 并 enqueue 只让等待者可重新竞争,不授予锁;unpark 来自 signal/doSignal 的条件唤醒或 release 成功后 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)行为要点
HashMaptransient Node<K,V>[] table(L389)、loadFactor(L427)、TREEIFY_THRESHOLD=8(L259)、putVal(L631)、resize(L683)、treeifyBin(L761)数组 + 链表/红黑树:putVal 做哈希桶插入,resize 扩容,treeifyBin 在桶内节点过多时树化;并发写不安全
ArrayListtransient Object[] elementData(L146)、add(L502)动态数组;扩容复制;按索引访问 O(1)
ConcurrentHashMaptransient 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);扩容机制见下

CHM transfer 的并发迁移(L2445-2573 逐段):

  1. 分段领取:先算 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)。
  2. 逐桶迁移:空桶用 casTabAt(tab, i, null, fwd) 直接放 ForwardingNode(L2503);已是 MOVED 则跳过(L2505)。普通链表桶在 synchronized (f) 内先扫描 lastRun 复用尾部链,再按 (hash & n) 把前缀节点分到 ln/hn,写入 nextTab[i] 与 nextTab[i+n],最后把旧桶置为 fwd(L2506-2545)。
  3. TreeBin 桶的拆分(补足细节):TreeBin 继承 Node 且 hash 为 TREEBIN,自身持有 root/first/waiter/lockState(L2794-2802):写树结构用 lockRoot 的 LOCKSTATE CAS(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)。
  4. 最后迁移者提交:所有区间完成后,线程对 SIZECTL 做 CAS sc-1;若 (sc-2) != resizeStamp(n)<<RESIZE_STAMP_SHIFT 说明仍有线程在迁移,直接返回;否则最后线程置 finishing=true、i=n 复核(L2482-2497),随后 nextTable=null; table=nextTab; sizeCtl=(n<<1)-(n>>>1) 完成提交。
  5. 失败/回退:初始化 OOME → sizeCtl=MAX 拒绝后续扩容;CAS 领取/提交失败 → 重试或退出;迁移期间对旧桶的读写看到 ForwardingNode 后经 helpTransfer/新表继续,未领取到区间的线程也可协助迁移。
  6. 正确性边界(收紧):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] 的 LongCacheLong.valueOf(long)(L1231-1238)
Short[-128,127] 的 ShortCacheShort.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)
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):

  1. key/value 为 null → NPE;spread(hashCode())。
  2. 表为空 → initTable()。
  3. 目标桶为空 → casTabAt(tab,i,null,new Node),空桶无锁;CAS 失败就重读。
  4. 桶头 hash == MOVED → helpTransfer(tab,f),拿到 nextTab 后重试。
  5. onlyIfAbsent 且首节点 hash/key 命中且 value 非空 → 直接返回旧值,不取桶锁(这是 putIfAbsent 的无锁快路径)。
  6. 否则 synchronized(f),进入后必须再 tabAt(tab,i)==f(防止锁前桶已被迁移/替换);fh>=0 走链表插入/覆盖,f instanceof TreeBin 走 putTreeVal,ReservationNode 抛 IllegalStateException("Recursive update")。
  7. 锁内若命中已有键: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)的额外分支:

这些分支的共同正确性约束:桶锁只保护当前桶对象,迁移用 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)。

5.3 AQS 与 LockSupport:等待/唤醒的底层链

5.3.1 线程池相关:FutureTask 与阻塞队列

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;

5.5 正常/失败/回退总表(本章)

机制正常失败回退
容器读写约定内操作越界/CME/OOME无(改用并发容器=设计决策)
线程池提交→执行拒绝异常/任务异常/OOME拒绝策略(CallerRuns 等)
AQS/park获取→等待→唤醒中断(可中断版本)无(上层循环处理虚假唤醒)
atomic/VarHandleCAS/访问模式语义无(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 缺口与下一步

  1. AArch64 对照:RV64 的 lr/sc 与 AArch64 的 ldxr/stxr/LSE cas 已对照;两者语义一致、指令不同;仍未在设备上确认 CPU 特性/产物选择。
  2. AQS 定时/中断运行验证:源码分支已按完整方法核读(预检查、等待中取消、reacquire 后抛/恢复);真实中断时序与 managedBlock 拒绝路径仍需要运行用例,源码不能替代调度结果。
  3. 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 与失败传播

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.

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();
        }
    }
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 → BindExceptionSocketException
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 失败→IOExceptionIOException
IoBridge.isConnected(L319-360)poll(POLLOUT) 返回 0→false(超时);getsockopt(SO_ERROR) 非 0→按 errno;fd 已关闭→SocketException("Socket closed");ETIMEDOUT→SocketTimeoutExceptionConnectException
IoBridge.sendto(L679-718)datagram ECONNREFUSED→PortUnreachableException;stream EAGAIN→0(非阻塞无写入);其余→IOExceptionIOException
IoBridge.recvfrom(L749-810)isRead 读路径 EAGAIN→0,其余→SocketException;非 read 路径 connected+ECONNREFUSED→PortUnreachableException,EAGAIN→SocketTimeoutExceptionSocketException
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)在系统调用包装层插入策略回调:

因此 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)分开

6.5 DNS 与网络地址:Java 缓存仍在,resolver 走 Libcore.os

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 缺口与下一步

  1. EINTR/重试语义:read/write 包装、IO_FAILURE_RETRY/NET_FAILURE_RETRY 与按 API 的 errno→异常分派已按源码核(见 §6.2/§6.2.1);仍未做真实信号/异步 close 的运行验证,其它 syscall 包装未逐行。
  2. DNS native 实现:Java 侧 AddressCache(16 项/2s/netId)与 Libcore.os.android_getaddrinfo 入口已核;最终 bionic getaddrinfo 实现未跨仓核,不要把 Java 缓存当成 native 解析逻辑。
  3. epoll 细节:EPollPort 作为异步 ChannelGroup 的创建/唤醒/事件队列已定位;fd 注册/注销与事件分发未逐行展开。

第 7 章:数学、数值转换、时间与 Unicode/ICU 边界

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);

7.2 数值转换:Integer/Long/Double 的 parse/toString

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)

SimpleDateFormat / DateFormat(java.text):

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)   { ... }

7.4 java.time 与时间/时区

7.5 Locale / Unicode / ICU:跨仓职责表

luni/src/main/java/libcore/icu/ 下的桥接类(pin 内):

文件职责数据/实现来源
ICU.javaLocale/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

7.6 正常/失败/回退(本章)

机制正常失败回退
Math 函数返回规范结果NaN/Inf 按规范传播无
解析/格式化返回数值/字符串NumberFormatException无
Formatter/SimpleDateFormatspecifier/pattern 解析后输出IllegalFormatException/IllegalArgumentException/ParseExceptionDateFormat 工厂对 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 缺口与下一步

  1. Double/Float 对拍:格式化/解析算法与 ties-to-even 分支已按源码核;仍需用规范边界集运行对拍,尤其次正规、-0.0、十六进制浮点、超长数字。
  2. BigInteger/BoringSSL 内部:Java 侧 multiplyToLen/squareToLen/divideKnuth/divideMagnitude 已核;BoringSSL BN_mul(comba8/schoolbook,无一般 Karatsuba)与 BN_div(Knuth Algorithm D,非常数时间)已核到算法族。仍未核的是 BoringSSL 在 RISC-V 上的具体汇编/分派与性能,不宣称收益。
  3. Formatter/SimpleDateFormat/Locale:Formatter 手写 specifier 解析与日期/数字分派、SimpleDateFormat pattern 编译/ICU 月份星期/时区名/LocaleData 数据源已按源码核;仍未做 Locale 矩阵与错误输入的运行对拍,也没有绑定 ICU/CLDR/tzdata 的具体版本。
  4. ICU/tzdata 版本:u_getVersion/u_getUnicodeVersion/ulocdata_getCLDRVersion 是运行时查询;tzdata 的 ZoneInfoData 模块不在 libcore pin 内,具体版本需按外部模块与构建产物独立取证。

第 8 章:反射、MethodHandle、序列化与加密边界

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 的活代码与注释块
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):

  1. 栈/初始化:检查 native 栈余量,不足抛 StackOverflowError;确保声明类已初始化,失败则保留初始化异常。
  2. String 构造器特例:String.<init> 替换为 StringFactory 调用(m = StringInitToStringFactory(m),receiver 必须为 null)。
  3. receiver 类型:非静态方法先 VerifyObjectIsClass(receiver, declaring_class),不匹配抛 IllegalArgumentException(“Expected receiver of type ...”);静态方法 receiver 可为 null。
  4. 虚分派:receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(m, kPointerSize),得到实际实现。
  5. 参数个数:CheckArgsForInvokeMethod 用方法签名的参数个数与 Object[] 长度比较,不等抛 IllegalArgumentException(“Wrong number of arguments; expected N, got M”)。
  6. 访问检查:若 executable->IsAccessible() 为 false,调用 VerifyAccess;失败抛 IllegalAccessException(消息带 caller、访问标志、方法名和声明类)。
  7. 参数解包/调用:ArgArray::BuildArgArrayFromObjectArray 做拆箱/加宽/引用检查,失败抛 IllegalArgumentException;InvokeWithArgArray 调 ArtMethod::Invoke。
  8. 异常包装:目标方法抛出的任何异常被包成 InvocationTargetException(ActiveTransaction 的事务中止异常除外);创建包装异常本身失败则保留该异常。
  9. 返回值:primitive 通过 BoxPrimitive 装箱,引用直接返回。

VerifyAccess 的规则(reflection.cc:1014-1057):

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+)
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)
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)的规则:

这条链完全在 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)

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)的顺序:

  1. subs.lookup(obj) 处理替换表;null → writeNull()。
  2. 若 !unshared 且 handles.lookup(obj) != -1 → writeHandle(h)(回引用)。
  3. ObjectStreamClass.lookup(cl, true);若 hasWriteReplaceMethod(),调用 invokeWriteReplace(obj)。Android 改为只做一次替换(L1155-1187 的注释保留原 for(;;) 循环),替换结果类变化时重新取 descriptor;替换为 null 则写 null。
  4. 若 enableReplace,再调用 replaceObject;替换结果与 orig 不同则 subs.assign(orig, obj) 并重新做 null/回引用检查。
  5. 按类型分派: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):

  1. 读 class descriptor,desc.checkDeserialize();拒绝 String/Class/ObjectStreamClass 的 ordinary object descriptor。
  2. desc.newInstance()(可序列化类走第一个非可序列化父类的无参构造器;Externalizable 走 public 无参构造器),失败包成 InvalidClassException("unable to create instance")。
  3. 先 handles.assign 占位(passHandle),再读 record/Externalizable/普通字段;因此循环引用在字段读取时能通过 TC_REFERENCE 找到尚未填完的对象。
  4. handles.finish(passHandle) 关闭依赖;若 hasReadResolveMethod() 且无异常,调用 invokeReadResolve(obj);若返回不同对象,handles.setObject(passHandle, rep)。unshared 且返回数组时还要 cloneArray,避免把流内共享数组泄漏成外部共享。
  5. 读侧 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 原语

8.4 加密:libcore 只提供 Java API 与 provider 选择框架

8.4.1 libcore 内的选择逻辑
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
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)

具体锚点:

8.5 正常/失败/回退(本章)

机制正常失败回退
反射 invoke目标调用 + 装箱返回IllegalAccess/IllegalArgument/InvocationTarget/StackOverflow无
反射 setAccessible置 overrideSecurityException(Class/Method/Field 构造器)无
MethodHandleexact 类型匹配后分派;inexact 先 asTypeWrongMethodType/ClassCast/目标异常无(invoke 的 asType 是语义,不是失败回退)
序列化句柄/替换/字段顺序按规范NotSerializable/InvalidClass/ClassNotFound/WriteAborted无自动过滤;ObjectInputFilter 在本 pin 被移除
zip/deflateJava 解析 + zlibZipException/DataFormatException无
Cipherprovider 实现算法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 缺口与下一步

  1. 构造器成员级访问检查:本 pin Constructor_newInstance0 只看到类级 CanAccess;需用运行用例确认 libcore/ART 是否在别处补成员级检查,不能仅凭这段源码下安全结论。
  2. 序列化过滤:ObjectInputFilter 在本 pin 被移除,若后续平台版本恢复该机制,需按新 pin 重新取证;本稿不把 JDK 的过滤语义当成 Android 行为。
  3. MH/反射产物:invoke-polymorphic 的链接/内联、Transformer 的调用点优化、反射调用的 JIT 形态需 odex/aot_map/机器码(缺)。
  4. crypto 内部:SSLContext→OpenSSLContextImpl→SSL_CTX_new、AES provider→JNI→EVP→CBC 实现选择已核;Conscrypt 完整 TLS 状态机、产品实际 provider 默认顺序、BoringSSL 实际 RISC-V AES 实现仍未逐文件/设备核;默认 TrustManager、证书链、会话缓存与握手 WANT/异常返回源码已补。本稿只给代表链和已核边界。

第 9 章:测试与性能——账本口径、cell 映射与产物身份

9.1 账本口径分层(先标口径,再谈数字)

口径用途边界
Fixed-N diagnostic研发归因/A/B:固定次数/固定取样下的相对比较不能替代正式原生 benchmark 的 ns/op 结果;绝对 elapsed 与频率代理本身可以记录,不因“诊断性质”否定测量
正式 benchmark(ns/op/score)对外口径与验收需要正式测试包/环境/统计流程
历史统计区间(lower/median/upper)记录当时的统计分布是历史统计区间记录;未在新硬件配置/新平台复测,不据此断言当前稳定性,也不作为根因结论

本报告的分工原则:

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(' ')

映射规则:

  1. class/method/params 必须与账本字段逐项对应;同名方法不同参数组不可合并统计。
  2. 参数含义要回到源码确认(例如 aligned=false 是 1 字节 position 偏移,不是硬件对齐属性;ch01 已证)。
  3. 每个 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.ByteBufferBenchmarktimeByteBuffer_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.StringBenchmarktimeHashCode(L40)StringLengths∈{EMPTY,SHORT,80,8192}(L21-29)ch03逐 charAt 的 Java hashCode;不覆盖 equals
regression.StringEqualsBenchmarktimeEqualsNull/Short/Medium/Long/VeryLong/NonWordAligned/End/NonString(L197-260)短/中/长/超长/非字对齐/尾部/非 String 对象;含 new String(...) 与 intern 的假设检查(L180-195)ch03覆盖 String.equals 的快速路径与长度分支
SystemArrayCopyBenchmarktimeSystem{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.RealToStringBenchmarktimeFloat_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.IntegralToStringBenchmarktime_IntegerToString[_2/_10/_16]_{small,medium,large}、time_IntegerToBinaryString、time_IntegerToHexString(L25-131)int 常量 12/12345/12345678(L21-23);radix 10/16/2ch07覆盖 Integer.toString(int,radix) 的进制分派
regression.BigIntegerBenchmarktimeRandomDivision(L23)、timeRandomMultiplication(L41)、timeRandomGcd(L32)1024-bit 随机操作数(L25-26/L43-44)ch071024 bit 最多 32 个 32-bit limb(随机高位可能为 0,实际 ≤32):乘法两侧都 <50 → multiplyToLen,除法 mag.length ≤32 < 40 → divideKnuth;这些 cell 不会进入 BoringSSL BN_mul/BN_div 阈值路径
regression.ReflectionBenchmarktimeMethod_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.CharsetUtf8Benchmarktime_ascii/bmp2/bmp3/supplementary(L54-68)new String(byte[], UTF_8) 的 ASCII/BMP/补充平面输入ch03覆盖 UTF-8 解码快路径,不是 String.getBytes
regression.SerializationBenchmarktimeRead/Write{IntArray,ArrayListInteger,String,ObjectStreamClass}、timeWriteEveryKindOfField(L36-150)int[256]、ArrayList<Integer>、String、ObjectStreamClass、各 primitive/reference 字段ch08覆盖句柄/字段块/类描述符;不是过滤或恶意流测试
regression.CipherBenchmarktimeEncrypt/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 源码参数决定ch07native/Java/intrinsic 边界,需逐 cell 核参数
并发/IO 类 cell按账本实际类需按账本 cell 名核对ch05/ch06本轮未全量映射;不把“类存在”当“账本已覆盖”

9.3 产物身份:为什么它阻断性能解释

现有冻结包的已知缺口(性能项目内部记录,公开稿不引用其数值/哈希):

最小获取项(拿到即可离线核验,不需要先上板):

  1. 两个 exact benchmark/runner odex 与对应 boot/aot 产物;JAR/runner 和 30 方法静态集合已取得,五方法+newBuffer 静态 DEX 侧车已接受;
  2. 被测镜像的 boot.art/boot.oat 或 dalvik.vm.* 编译配置;
  3. 该 cell 的 aot_map/profile 与运行日志。

9.4 正确性对拍计划(未执行)

对 putChar 与 arraycopy 两类高频路径,先做语义对拍、后谈性能:

目标用例维度参考实现
ByteBuffer.putChardirect/heap/mapped × BIG/LITTLE × aligned/非 aligned × char 边界值(0/0x7F/0x80/0xFF/0x100/0xFFFF)× 越界/只读/已释放Memory.pokeShort 逐字节结果;HeapByteBuffer.putCharUnchecked 分支
System.arraycopyprimitive/引用数组 × 重叠(前向/后向)× 零长度 × 越界 × nullJava char[] 快路径与 native 实现对照

规则:对拍通过只证明语义一致,不证明性能;性能结论需要独立测量(§9.5)。

9.5 计时与频率口径

9.6 优化候选地图(方法,不给收益承诺)

源码机制(章节)可观察热点(待测)候选方向需要的前置依据
Direct ByteBuffer 写路径(ch01/ch04)putChar/putInt 等 cell产物 intrinsify 状态核验→再评估odex/aot_map/oatdump
String equals/hashCode(ch03)字符串密集 cellintrinsic 触发情况核验产物 + 机器码
System.arraycopy(ch03)数组复制 cellintrinsic/快路径选择核验产物 + 对拍
并发容器/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 缺口与下一步

  1. 产物身份核验(离线优先)。
  2. reps/N/JVM 参数与 provenance:优先从已有精确 cell provenance/账本读取;缺则保留最小获取项,不广扫全盘、不自行租板。
  3. cell 覆盖边界:公开源码里的 BigIntegerBenchmark 用 1024-bit 随机操作数(最多 32 limb),乘法两侧都 <50、除法 mag.length<40,因此只覆盖 Java multiplyToLen/divideKnuth,不会触发 BoringSSL BN_mul/BN_div 阈值;若要把大数 native 路径纳入性能结论,需要另选更大操作数并重新绑定 cell,不能拿该 cell 推断 BN_* 成本。
  4. 对拍与频率口径落地后,才进入“性能解释/优化 A/B”。

总结与展望

本稿已把源码机制链、状态/分派、正常与失败/回退、成本边界和可复制观察整合为可评审正文;十章机制保持,42 行矩阵保留在附录 A。仍未运行/构建/反汇编,产物与设备项只在附录 A/C 列为缺口,不据此宣告终验或发布。[S01][S02]

第 10 章:结论、缺口与优化地图

10.1 五条主结论

  1. libcore 是“Java API + 运行时托管实现”的混合体:String 的字符数据由 ART 运行时管理(value 字段已在源码中注释,count 编码长度/压缩位;charAt 为 @FastNative native);把 String 描述成“Java 字段 byte[] value”在本 pin 不成立(ch03)。
  2. 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)。
  3. 交付边界在构建/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 参数与设备安装态仍未验证。
  4. 并发与 IO 的“语义在 Java、机制在 VM/内核”:AQS/LockSupport 的等待唤醒最终落到 VM/内核;VarHandle/Unsafe 的 fence 语义由规范定义、指令由编译器/架构生成;IO 的 errno→IOException、EOF/EAGAIN、短写处理在 IoBridge 有明确映射(ch05/ch06)。
  5. 性能结论必须与产物身份同层:源码具备 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 缺口清单(按影响排序)

  1. 产物身份缺口(阻断性能解释):冻结包的 odex/oat/aot_map/boot image 未绑定;最小获取项见 ch09。影响:无法判定 intrinsic/内联状态与 cell 差异成因。
  2. 跨仓机制缺口(已核/未核分列):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-CBC cipher_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→NativeBN JNI 桥(libcore_math_NativeBN.cpp:143-157/167-177,静态链 libcrypto_static)、NioUtils.freeDirectBuffer 清理链(L52-64)、NativeBN 与 Conscrypt 的构建链接(NativeCode.bp / Conscrypt Android.bp)。
  3. 构建/APEX 缺口:装配主干已核(fragment contents + boot image profile);仍缺 APEX 内文件布局、odex 参数与设备安装态(需产物/设备)。
  4. 仍需继续研究的源码/验证项:AArch64 的设备 CPU 特性(LSE 等)与产物选择;Constructor 成员级访问检查需运行用例确认;AQS 定时/中断时序与 managedBlock 拒绝路径需运行验证;CHM compute/computeIfAbsent/merge 与 TreeBin 删除/平衡已展开;serialization 的过滤在本 pin 已移除(不是“未展开”,而是事实),但循环/兼容/代理用例未运行;zip 损坏输入未运行;BoringSSL 的 RISC-V 汇编/AES 轮函数未逐文件。实测未做项(与源码缺口分开):DNS 缓存行为、Cleaner 触发时机、epoll 事件循环、EINTR 实际信号场景。
  5. 数据版本缺口:ICU/tzdata/Unicode 数据版本在外部仓与构建产物中;libcore 只能证明 u_getVersion/u_getUnicodeVersion/ulocdata_getCLDRVersion 运行时查询与 ZoneInfo→ZoneInfoData 委托,不能绑定实际版本。

10.4 优化候选地图(方法,不承诺收益)

优先级线索依据(章节)下一步(建议)前置依据
Direct Buffer 写路径ch01/ch04:intrinsic 机制存在、产物状态未知离线 oatdump 判定调用点;再决定是否 A/Bodex/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 交付与复核路径

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/libcore4ebfb391ee0109d16abe6e2bc965724b940928c5(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(仅边界参考)b753cf97923c3695338d21466fa14c57b480a59aruntime/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/conscryptfd1a430323b5ab9945a15fce6c4a824aeffff685common/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/boringssl0cd1f6a94b670e6b61af95a06e71e690ae8e3262src/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-1libcore 仓库的模块构成与职责边界(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-3libcore 的 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-1String 的内部存储(运行时托管:count 编码长度/压缩位;charAt 为 @FastNative native,length() 是 Java 方法;不是 Java 字段 byte[] value)与构造路径部分(源码确证,未运行)ch03压缩谓词/布局已核(含 NUL 非压缩边界);运行态压缩选择/产物未测
D2-2String.equals/compareTo/hashCode 的实现与快捷路径部分(源码确证,未运行)ch03compareTo 活入口为 @FastNative native → ART String_compareTo → mirror::String::CompareTo;hashCode 无对应 RV64 intrinsic;StringEquals/compareTo 依赖压缩/前缀不变式;未构造反例运行验证;产物是否 intrinsify 未核
D2-3intern 的宿主(StringTable/StringPool)与 native 边界部分(源码确证,未运行)ch03入口/插入策略已核;运行态 GC 弱表清理、并发与产物未测
D2-4编码解码/Charset 层(Charset/CharsetDecoder/ICU 边界)部分(源码确证,未运行)ch03/ch07非快路径字符集的数据来源/ICU 版本需外部仓与产物核;运行对拍未做
D2-5System.arraycopy 的语义与实现选择(范围检查/重叠/引用屏障/primitive 类型边界)部分(源码确证,未运行)ch03冻结产物实际走 Java/native/intrinsic 哪条、是否内联未核(需 odex/aot_map);常量/变量门与 typed 阈值已分清
D2-6Arrays 工具方法(sort/binarySearch/equals/copyOfRange)的算法与边界部分(源码确证,未运行)ch03Arrays.equals 的 primitive 向量循环/Object[] 逐元素/范围重载已核;DualPivotQuicksort 内部阈值/插入排序与部分类型内部未展开;运行对拍未做
D3-1ByteBuffer 状态模型(position/limit/capacity/mark;order)与状态机部分(源码确证,未运行)ch04—
D3-2Direct/Heap/Mapped ByteBuffer 的实现分派与 putChar 路径差异部分(源码确证,未运行)ch04—
D3-3putChar 端到端链路与 intrinsic 边界:源码具备 intrinsic ≠ 冻结调用点已 intrinsify部分(源码确证,未运行)ch04(纵切)冻结产物身份/调用点 intrinsify 状态未知(odex/aot_map 缺);未反汇编
D3-4ByteOrder/swap 语义(nativeByteOrder、LITTLE/BIG、Short.reverseBytes 触发条件)部分(源码确证,未运行)ch04(纵切)—
D3-5slice/duplicate/asReadOnlyBuffer 的共享 vs 复制语义与视图状态部分(源码确证,未运行)ch04视图与 Cleaner 生命周期的运行验证未做
D3-6Memory/Unsafe/VarHandle 的分层与公开边界(libcore Memory 内部 API vs jdk.internal.misc.Unsafe vs VarHandle)部分(源码确证,未运行)ch04RV64/AArch64 源码指令已对照;设备 CPU 特性(LSE 等)与冻结调用点 intrinsic 状态未核
D3-7DirectByteBuffer 生命周期与 Cleaner/引用队列的回收路径部分(源码确证,未运行)ch04MemoryRef.free 的其它调用入口/别名安全/设备行为未核;三种所有权与 Cleaner 语义已核
D3-8对齐/非对齐语义(Java 层无对齐要求;benchmark aligned=false 用 position=1;硬件表现差异归性能分析)部分(源码确证,未运行)ch04(纵切)—
D4-1常用集合实现(HashMap/ArrayList 等)的结构与扩容语义部分(源码确证,未运行)ch05—
D4-2并发容器(ConcurrentHashMap/BlockingQueue)的并发协议部分(源码确证,未运行)ch05CHM 主要写路径、TreeBin 拆分/删除/balanceDeletion 源码已核;并发运行压力测试未做
D4-3线程池/Future/Executor 的状态机(runState、队列、拒绝策略)部分(源码确证,未运行)ch05—
D4-4锁/AQS/park/unpark 的入口与 JMM 边界部分(源码确证,未运行)ch05AQS Condition 源码与 RV64/AArch64 对照已核;定时/中断运行验证、设备/产物及非 futex 构建未测
D4-5atomic/fence 与 RV64/AArch64 架构入口(通用 Java 实现 vs 架构相关屏障)部分(源码确证,未运行)ch05RV64/AArch64 源码指令序列已对照;设备 CPU 特性与产物 intrinsic 状态未核
D4-6装箱/缓存(Integer/Long cache 等)与分配热点部分(源码确证,未运行)ch05未运行分配/对拍;产物逃逸分析/标量替换未核
D5-1java.io 文件流栈与 libcore 原生边界(IoBridge/BlockGuard)部分(源码确证,未运行)ch06open/read/write/close 与 socket errno 分派已核;其它 JNI 实现逐函数/设备行为未核
D5-2NIO Channel/Selector 与 epoll 事件循环边界部分(源码确证,未运行)ch06native dispatcher 与运行态事件循环未核
D5-3网络/DNS 解析与 InetAddress/Resolver 边界部分(源码确证,未运行)ch06resolver 最终 native 实现未逐行
D5-4异常传播与 errno→IOException 映射、可观测失败(超时/断连/部分读写)部分(源码确证,未运行)ch06其他 syscall 包装与部分错误分支未逐行;运行态信号/中断未测
D6-1Math/StrictMath/数值转换(parse/toString)的实现与边界部分(源码确证,未运行)ch07Long/Short/Byte 等同类 API 未逐条展开
D6-2BigInteger/BigDecimal 的算法与边界部分(源码确证,未运行)ch07Java 算法、阈值、JNI 桥与 BoringSSL BN_mul/BN_div 算法族已核;RISC-V 具体汇编/性能未核;不宣称具体实现或收益
D6-3格式化/解析(Formatter、SimpleDateFormat 等)与 Locale 依赖部分(源码确证,未运行)ch07Formatter/SimpleDateFormat/Locale 源码链已核;运行对拍(Double/Float/日期/Locale)未做;ICU/CLDR/tzdata 具体版本未绑定;DoubleToDecimal 调用者仍未绑定
D6-4时间/日期(java.time、ZoneId)与 ICU 数据版本、跨仓职责部分(源码确证,未运行)ch07ZoneInfoData/tzdata 模块不在 libcore pin 内,具体版本需外部模块与产物核
D7-1反射(java.lang.reflect)实现与 native/VM 边界部分(源码确证,未运行)ch08Constructor 成员级检查的源码观察需运行用例确认;Field 外层门/IsUnmodifiable(含 IsMonotonic 前置)已核;产物优化未核
D7-2MethodHandle/动态调用(java.lang.invoke)与 intrinsic/VM 边界部分(源码确证,未运行)ch08invoke-polymorphic 产物内联/链接状态未核
D7-3serialization/zip 等核心库分组行为与安全边界部分(源码确证,未运行)ch08过滤在本 pin 已移除;zip 损坏输入与运行对拍未做
D7-4JCA/crypto API(libcore)↔ Conscrypt provider ↔ BoringSSL 原生 的真实边界(代表链)部分(源码确证,未运行)ch08provider 选择、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-2ByteBuffer putChar 真实 cell 的参数矩阵与 worst cell 定位部分(账本已定位)ch04/ch09DIRECT/HEAP 8192/8201,MAPPED 65536;历史 plan 8 组合(DIRECT/HEAP)×30=240,公开源码含 MAPPED 为 12 种可能组合;产物身份核验前不解释差异
D8-3产物身份/AOT-JIT/odex 获取项与离线核验路径部分(缺口已记录)ch09JAR/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 nativelibcore 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/parkAbstractQueuedSynchronizer.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/序列化/ConscryptREFLECTION/方法链见 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+PNG4 张已生成文章列宽重排;未做 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 未完成与限制

附录 D:Benchmark 家族、执行模式与测量前置(公开增补)

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=falseREADME:先按 Android 官方脚本锁频,再 atest LibcoreBenchmarkTests:libcore.benchmark.FormatterTest#stringFormatNumber_allLocales;AndroidTest.xml 使用 AndroidBenchmarkRunner,抑制 EMULATOR,LOW-BATTERY,UNLOCKEDBenchmarkRule/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/Memory336ByteBufferBenchmark、ByteBufferBulkBenchmark、ByteBufferScalarVersusVectorBenchmark
String/Charset1878StringBenchmark、StringEqualsBenchmark、StringToBytesBenchmark、CharsetUtf8Benchmark、CharacterBenchmark、StringTest
Collections/Concurrency1891HashedCollectionsBenchmark、PriorityQueueBenchmark、ThreadLocalBenchmark、SystemArrayCopyBenchmark、ReferenceBenchmark
Math/Numeric/Format20245MathBenchmark、StrictMathBenchmark、benchmarks.regression.BigIntegerBenchmark、DoubleBenchmark、FormatterBenchmark、DecimalFormatBenchmark
IO/NIO/Network914FileBenchmark、DnsBenchmark、SSLSocketBenchmark、URLConnectionBenchmark、ChecksumBenchmark
Time/Locale/ICU832LocaleDataBenchmark、TimeZoneBenchmark、DateFormatBenchmark、SimpleDateFormatBenchmark、CollatorBenchmark
Reflection/MH674ReflectionBenchmark、MethodInvocationBenchmark、FieldAccessBenchmark、MethodHandlesTest、ClassTest
Serialization/Zip843SerializationBenchmark、XmlSerializeBenchmark、ZipFileBenchmark、ZipFileReadBenchmark、JarFileBenchmark
Crypto/Security617CipherBenchmark、CipherInputStreamBenchmark、MessageDigestBenchmark、SignatureBenchmark、KeyPairGeneratorBenchmark
Other/misc benchmarks7100ArrayCopyBenchmark、GetSystemPropertyBenchmark、InvokeInterface、DoPrivilegedBenchmark、EqualsHashCodeBenchmark、ExpensiveObjectsBenchmark、SchemePrefixBenchmark(均为真实 benchmark 类,100 个 time*)
Standalone/manual10benchmarks.BigIntegerBenchmark:根目录,注释自述 not structured as proper benchmark,main 用 System.nanoTime() 调 repeatInner/harmonic/toString/modPow/modInverse;不是 Caliper time*(reps)
Helpers20benchmarks.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。

11.3.2 StringEqualsBenchmark 的 8 个方法

源码:benchmarks/src/benchmarks/regression/StringEqualsBenchmark.java:197-266。

11.3.3 SystemArrayCopyBenchmark 的 8×18 参数矩阵

源码:benchmarks/src/benchmarks/SystemArrayCopyBenchmark.java:22-97。8×18 是源码参数轴(8 个 primitive 方法 × 18 个长度),不是未来 eligible cell 数;eligible 以未来 run 的 manifest/harness 为准。

11.3.4 CipherBenchmark 的 provider×算法×模式矩阵

源码:benchmarks/src/benchmarks/regression/CipherBenchmark.java:51-80/134-144。

11.3.5 BigIntegerBenchmark 的 1024-bit cell

源码:benchmarks/src/benchmarks/regression/BigIntegerBenchmark.java:23-47。

11.3.6 其余代表性家族(只列入口与边界)

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/codegenRV64 扩展/编译目标、AArch64 目标、ART 编译器后端不能把 RV64 lr/sc/Sh 序列外推到 AArch64;实际 codegen 需产物
runtime/bootART runtime 版本、boot image、odex/JAR、AOT/JIT/nterp 状态full-AOT 绑定适用 exact odex/boot/JAR;JIT/nterp 绑定 DEX/runtime/模式与适用观测,不把不存在的 odex 作为共同前置;不宣称调用点 intrinsify
GC/allocatorGC 类型/堆参数/allocator、是否 stop-the-world分配密集 cell 必须固定并记录
计时标准原 harness(Caliper/AndroidX)的 ns/op 或 throughput、独立进程数、样本数、warmup、reps、统计量标准原 harness 报告与 Fixed-N diagnostic 分表;实际独立进程/样本数与补样停止规则由新实验运行前计划确定,历史 3p/6p 只作方法示例;报告波动/不确定性,不新设达标阈值
PMUcycles/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 的源码范围与测量前置条件,不以历史清单充当新平台范围,未执行项不能写为设备验收结果。