百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

Kotlin Multiplatform 原理深入分析

liuian 2025-10-02 03:18 3 浏览

什么是 KMP

KMP(Kotlin multiplatform)是 Kotlin 语言的一项重要特性,允许将 kotlin 代码运行在不同平台上,通过『一码多端』的方式来节省成本。

而与诸如 Java / React 这类跨端方案不同,KMP 没有采用所谓的虚拟机的思路,而是选择直接将 kotlin 源码编译成目标平台代码运行的方案。

KMP 的优势和限制

KMP 的优势:

相较传统的跨平台框架而言,由于 Kotlin 会将代码编译成目标平台原生代码执行(可以简单理解为将 Kotlin 源码翻译成 java/c++/js 代码),其最大的优势在于进行 FFI(跨语言调用)时几乎没有性能折损,并且执行性能接近于原生系统。

KMP 的限制:

由于早期的 kotlin 是基于 java / android 平台,这些 kotlin 二/三方库在设计时候也不可能考虑过跨平台。考虑到这些情况,kotlin 在编译时使用了 Target Platform概念,即 kotlin 每个类 / 方法 都是有对应平台的,早期的的 java / android 二三方库只属于 jvm 平台,意味只能在 java / android 平台调用,在其他平台上调用会编译报错。

而对于系统接口,在 KMP 下也是有对应平台限制的,一个简单的判定方法如下:

  1. 所有 kotlin.*、kotlinx.*包名的接口,都是跨平台的。

  2. 所有 java.*、sun.* 包名的接口,只能在 jvm 平台使用。

  3. 所有 android.*包名的接口,只能在 android 平台使用。

  4. androidx.*比较特殊,部分库可以(比如 Room),部分不行,需要自行查看文档判断。

因此,如果想要将 Android 代码通过 KMP 直接编译成其他平台产物,那基本上是不可能直接成功的。如果没有提前设计隔离层的话,工程中二、三方依赖,以及源码中几乎不可避免的含有 Android / jvm 的平台接口,你很可能需要进行大量的抽象改造才可以完成。

KMP 实现原理

跨平台概述

前面提到,KMP 核心思路,是直接将 kotlin 源码编译成目标平台代码运行。而实现这一能力的关键就是 Kotlin 编译器,其核心职责就是将源码翻译成目标平台代码。

在实现上,kotlin 编译器使用了前后端分离的思路。

简单来说,前端负责语法解析&代码分析、后端负责将前端产物翻译成目标平台代码,二者职责清晰。未来如果如果需要支持一个新平台,添加一个新后端即可。

至于 Optimizer,由于不同目标平台的优化方式不同,在 kotlin 编译器中被放在了后端中。

Kotlin Native 编译器

由于 Kotlin Jvm 大家相对比较熟悉,而 Kotlin JS 笔者还没有看,因此本文只着重介绍 Kotlin Native 的相关分析

编译流程

Kotlin Native 编译入口通常为 Gradle Task 或者命令行(konanc),二者最终执行代码是共通的,最终会根据根据产物类型不同执行不同逻辑。

产物分为四类:

  • Klib:Kotlin Native Library,可以简单理解为 Kotlin Native 版本的 jar / aar,只保存了 kotlin ir 信息。

  • ObjCFramework:给 iOS 使用的 .framework.

  • Binary:缓存 / 可执行文件。

  • CLibrary:动态库 / 静态库。

produce(Klib)

Compile 作用是将 kt 编译成 Klib(可以类比为 aar)。

Klib 解开后结构如下:

所有 KN 模块在编译阶段都会先编译成 Klib,在 link 阶段才会调用 c++ 工具链处理。

produce(Binary/CLibrary/ObjCFramework)

这三个基本流程都差不多,都是将多个 Klib 聚合编译成一个二进制库(类似于 C 的 link、或者 android 的打 apk),区别在于产物不同。核心为编译器后端处理,用于将 kotlin ir 转换为目标平台的二进制库,核心流程如下:

各步骤说明:

1. Add entry points:如果编译可执行文件,就加一个入口文件的 ir file,比较简单。

2. Lowering module && dependencies:将所有依赖库合并,并针对合并后的每个 ir 文件(包括依赖的库的 ir)执行 Lowerings(对 Ir 进行前置优化,比如内联,语法糖处理),每个 lowering 文件需要执行 51 步,每一步都可以在 NativeLoweringPhases.kt 中找到对应的定义。

3. Run after lowering:即真正的 Native 编译流程,主要通过 llvm 将 kotlin IR 翻译为二进制产物,主要步骤:

  1. CodeGen:将 kotlin ir 『翻译』成 llvm IR,这部分主要通过调用 llvm 的 c 函数实现

  2. Generate Export Api + Compile Export Api:生成一个对外 api 的 c++ 接口文件并编译,用于暴露接口给外部调用。

  3. Post Processing :在和底层依赖库(Runtime)的 bit code 链接前,做一些优化工作,比如去除无用代码。

  4. Write BitCode:将所有 bitcode 链接完毕后,生成 out.bc

  5. Compile and link:

  1. 调用 clang 将 .bc 编译成 .o,这里会根据 debug / release 添加不同编译参数。

  2. 调用 lld 将 .o 文件 link 成目标平台汇编代码

IR 转换

假设有如下源码:

package com.demo.kmp
classHelloWorld{ funhelloFun1(a: Int, b: Int): Int { return a + b }}

其编译后的 llvm ir 长这样:

除开一些流转指令、调试指令外、其翻译回 C / C++ 代码大概是这样。

// 没错这个函数名就是这么长int"kfun:com.demo.kmp.HelloWorld#helloFun1(kotlin.Int;kotlin.Int){}kotlin.Int"(*struct.ObjHeader this,int a,int b) {    return a + b;}

可以看出和用 C / C++ 写的代码基本上差不多,所以执行效率是非常高的(相当于写 C / C++ 代码去运行)。

其主要的『翻译』逻辑如下:

  1. kotlin 基础类型会『翻译』为对应的 C 的基本数据类型,如: int / float / double / short / long / double。

  2. Kotlin 类会『翻译』成 llvm typeInfo 形式,用来记录类名等信息。

  3. Kotlin 对象会『翻译』成 ObjHeader + 一段内存空间形式,前者用于记录 typeinfo,后者用来存放所有的类字段。

  4. Kotlin 函数会『翻译』成 C 函数,差别在于会多一个 ObjHeader* 参数,用作 $this 指针。

  5. Kotlin 属性会『翻译』成 Get/Set 函数,这个跟 java 是一致的。

  6. Kotlin 运算符会『翻译』成对应的 operator 函数(举例来说,加号(+)会翻译成 add 函数),一些类型(比如基础类型)会进一步通过内联翻译成 C 的运算符。

  7. 其余类型则不再赘述,有兴趣可以自行参考源码(位于ir2bitcode.kt)实现。

Kotlin Native 运行时

为了实现内存的自动回收,在 Kotlin Native 平台上,会打包一套 Kotlin Runtime到最终产物中,包含异常处理、线程管理、内存管理等常规能力。

运行时包括如下几个部分,创建线程或者已存在的线程都可以 initRuntime

  • SetKonanTerminateHandler 为线程设置异常处理Handler,这样可以捕获kotlin excepiton

  • globalData 初始化全局变量

  • theaddata 初始化线程内存分配器

  • workInit 初始化线程消息队列,用于执行协程

和 android 相比,kmp 运行时不支持 synchronized 关键字,可以使用 atomicFu 来解决。

内存管理

Kotlin Native 有 3 种内存分配器:

  • custom:kotlin 自己开发的内存分配器,也是默认的内存分配器

  • std:标准库内存分配器,在鸿蒙上是 jemalloc

  • mimalloc:微软开源的 native 分配器

目前 std/mimalloc 在最新版本已经去掉了,kmp 未来会持续优化 custom 内存分配器

custom 内存分配器是 kotlin 自己实现的内存分配器,包括几个部分

  • Safealloc mmap 虚拟内存,每次大小256k,分配后检查是否需要触发 alloc gc

  • CreateObject 分配对象,每个对象额外增加16字节内存,包括 objectData/objectHeader

  • CreateObject 分配对象时,如果类(typeInfo)加了 TF_HAS_FINALIZER 标记,会通过 extraObject 增加对象弱引用,gc 后调用对象 finialize 方法,objectHeader 指向 extraObject

  • CreateArray 分配 array,每个 array 额外增加24字节内存,包括 objectData/ArrayAHeader,ArrayHeader 12字节按照8字节对齐到16字节

和 android 相比,有 3 点不同:

  • Kotlin Native 只支持 Weakreference,不支持 SoftReference

  • Kotlin Native 对象分配支持逃逸分析,除了在堆上分配,还可以在编译时通过静态代码分析决定哪些变量在栈上分配

  • Kotlin Native 把 Array 类型单独拿出来了,Android 认为所有类型都是 Object

基础类型

基础类型包括
Byte/Short/Int/Float/String 等,和 android 一致

对象类型

class 包括几部分

  • instanceSize_:对象大小,如果是 array,instanceSize_ 为每个元素大小

  • superType: 父类

  • objOffsets:成员变量 offset 数组,根据 offset 查找成员变量

  • objOffsetCount_:成员变量数量

  • interfaceTableSize:interface 数量

  • interfaceTable:interface 表,指向 interface 实现

和 android 相比,Kotlin Native 将 interface 方法和 abstract 方法都通过 interfacetable 存储,android 是分开存储的

内存回收(GC)

GC 有三种类型,默认 pcms,cms 需要手动配置

  • cms 是并发标记的,只在遍历 gc root 时暂停线程,性能最好

  • Stms 需要 stop the world 暂停线程,性能很差

  • 默认 pcms 可以支持多线程 gc,也会 stop the world 暂停线程

由于 cms 性能最好,目前 KMP 项目里面默认使用 cms

cms 类型主要包括几个功能,在在 gc root 收集完成后,会 resume the world 唤醒线程

  • StopTheWord 所有线程将线程暂停执行

  • collectRootSet 收集 gc root

  • resumeTheWorld 唤醒线程

  • Mark 会根据 gc root 标记存活对象

  • processWeaks 处理 weakReference

  • heap.Sweep 释放非存活对象

  • finalizerProcessor 调用对象 finialize 方法,之前会收集所有线程的 finalize 对象

和 android 相比

  • heap 默认10M,android 是大对象/小对象各512M,导致比较容易触发 alloc gc,目前已经优化

  • concurrent gc 通过定时10s触发实现,在空闲时容易造成 cpu 浪费,目前已经优化

  • cms 目前不会做内存碎片整理,会导致内存占用过高,目前在优化中

  • cms mark 阶段产生的对象都是存活对象

  • gc 不支持分代,目前已经优化

小结

Kotlin Multiplatform 在经历了这么多年迭代后,目前现在已经是一个相对成熟的解决方案了。虽然在内存管理方案还有一些瑕疵,但其『IR 翻译成 Native』设计理念使得整个系统的性能上限很高,理论上能达到接近原生的执行性能。而 Jetbrain 的号召力也使得整个研发生态非常有想象力,目前 androidx 已经在开始逐步适配 KMP 中,可以预见的将来会非常有潜力。

相关推荐

教你把多个视频合并成一个视频的方法

一.情况介绍当你有一个m3u8文件和一个目录,目录中有连续的视频片段,这些片段可以连成一段完整的视频。m3u8文件打开后像这样:m3u8文件,可以理解为播放列表,里面是播放视频片段的顺序。视频片段像这...

零代码编程:用kimichat合并一个文件夹下的多个文件

一个文件夹里面有很多个srt字幕文件,如何借助kimichat来自动批量合并呢?在kimichat对话框中输入提示词:你是一个Python编程专家,完成如下的编程任务:这个文件夹:D:\downloa...

Java APT_java APT 生成代码

JavaAPT(AnnotationProcessingTool)是一种在Java编译阶段处理注解的工具。APT会在编译阶段扫描源代码中的注解,并根据这些注解生成代码、资源文件或其他输出,...

Unit Runtime:一键运行 AI 生成的代码,或许将成为你的复制 + 粘贴神器

在我们构建了UnitMesh架构之后,以及对应的demo之后,便着手于实现UnitMesh架构。于是,我们就继续开始UnitRuntime,以用于直接运行AI生成的代码。PS:...

挣脱臃肿的枷锁:为什么说Vert.x是Java开发者手中的一柄利剑?

如果你是一名Java开发者,那么你的职业生涯几乎无法避开Spring。它如同一位德高望重的老国王,统治着企业级应用开发的大片疆土。SpringBoot的约定大于配置、SpringCloud的微服务...

五年后,谷歌还在全力以赴发展 Kotlin

作者|FredericLardinois译者|Sambodhi策划|Tina自2017年谷歌I/O全球开发者大会上,谷歌首次宣布将Kotlin(JetBrains开发的Ja...

kotlin和java开发哪个好,优缺点对比

Kotlin和Java都是常见的编程语言,它们有各自的优缺点。Kotlin的优点:简洁:Kotlin程序相对于Java程序更简洁,可以减少代码量。安全:Kotlin在类型系统和空值安全...

移动端架构模式全景解析:从MVC到MVVM,如何选择最佳设计方案?

掌握不同架构模式的精髓,是构建可维护、可测试且高效移动应用的关键。在移动应用开发中,选择合适的软件架构模式对项目的可维护性、可测试性和团队协作效率至关重要。随着应用复杂度的增加,一个良好的架构能够帮助...

颜值非常高的XShell替代工具Termora,不一样的使用体验!

Termora是一款面向开发者和运维人员的跨平台SSH终端与文件管理工具,支持Windows、macOS及Linux系统,通过一体化界面简化远程服务器管理流程。其核心定位是解决多平台环境下远程连接、文...

预处理的底层原理和预处理编译运行异常的解决方案

若文章对您有帮助,欢迎关注程序员小迷。助您在编程路上越走越好![Mac-10.7.1LionIntel-based]Q:预处理到底干了什么事情?A:预处理,顾名思义,预先做的处理。源代码中...

为“架构”再建个模:如何用代码描述软件架构?

在架构治理平台ArchGuard中,为了实现对架构的治理,我们需要代码+模型描述所要处理的内容和数据。所以,在ArchGuard中,我们有了代码的模型、依赖的模型、变更的模型等,剩下的两个...

深度解析:Google Gemma 3n —— 移动优先的轻量多模态大模型

2025年6月,Google正式发布了Gemma3n,这是一款能够在2GB内存环境下运行的轻量级多模态大模型。它延续了Gemma家族的开源基因,同时在架构设计上大幅优化,目标是让...

比分网开发技术栈与功能详解_比分网有哪些

一、核心功能模块一个基本的比分网通常包含以下模块:首页/总览实时比分看板:滚动展示所有正在进行的比赛,包含比分、比赛时间、红黄牌等关键信息。热门赛事/焦点战:突出显示重要的、关注度高的比赛。赛事导航...

设计模式之-生成器_一键生成设计

一、【概念定义】——“分步构建复杂对象,隐藏创建细节”生成器模式(BuilderPattern):一种“分步构建型”创建型设计模式,它将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建...

构建第一个 Kotlin Android 应用_kotlin简介

第一步:安装AndroidStudio(推荐IDE)AndroidStudio是官方推荐的Android开发集成开发环境(IDE),内置对Kotlin的完整支持。1.下载And...