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

Kotlin Multiplatform 原理深入分析

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

什么是 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 中,可以预见的将来会非常有潜力。

相关推荐

搭建一个20人的办公网络(适用于20多人的小型办公网络环境)

楼主有5台机上网,则需要一个8口路由器,组网方法如下:设备:1、8口路由器一台,其中8口为LAN(局域网)端口,一个WAN(广域网)端口,价格100--400元2、网线N米,这个你自己会看了:)...

笔记本电脑各种参数介绍(笔记本电脑各项参数新手普及知识)

1、CPU:这个主要取决于频率和二级缓存,频率越高、二级缓存越大,速度越快,现在的CPU有三级缓存、四级缓存等,都影响相应速度。2、内存:内存的存取速度取决于接口、颗粒数量多少与储存大小,一般来说,内...

汉字上面带拼音输入法下载(字上面带拼音的输入法是哪个)

使用手机上的拼音输入法打成汉字的方法如下:1.打开手机上的拼音输入法,在输入框中输入汉字的拼音,例如“nihao”。2.根据输入法提示的候选词,选择正确的汉字。例如,如果输入“nihao”,输...

xpsp3安装版系统下载(windowsxpsp3安装教程)

xpsp3纯净版在采用微软封装部署技术的基础上,结合作者的实际工作经验,融合了许多实用的功能。它通过一键分区、一键装系统、自动装驱动、一键设定分辨率,一键填IP,一键Ghost备份(恢复)等一系列...

没有备份的手机数据怎么恢复

手机没有备份恢复数据方法如下1、使用数据线将手机与电脑连接好,在“我的电脑”中可以看到手机的盘符。  2、将手机开启USB调试模式。在手机设置中找到开发者选项,然后点击“开启USB调试模式”。  3、...

电脑怎么激活windows11专业版

win11专业版激活方法有多种,以下提供两种常用的激活方式:方法一:使用激活密钥激活。在win11桌面上右键点击“此电脑”,选择“属性”选项。进入属性页面后,点击“更改产品密钥或升级windows”。...

华为手机助手下载官网(华为手机助手app下载专区)

华为手机助手策略调整,已不支持从应用市场下载手机助手,目前华为手机助手是需要在电脑上下载或更新手机助手到最新版本,https://consumer.huawei.com/cn/support/his...

光纤线断了怎么接(宽带光纤线断了怎么接)

宽带光纤线断了可以重接,具体操作方法如下:1、光纤连接的时候要根据束管内,同色相连,同芯相连,按顺序进行连接,由大到小。一般有三种连接方法,分别是熔接、活动连接和机械连接。2、连接的时候要开剥光缆,抛...

深度操作系统安装教程(深度操作系统安装教程图解)
  • 深度操作系统安装教程(深度操作系统安装教程图解)
  • 深度操作系统安装教程(深度操作系统安装教程图解)
  • 深度操作系统安装教程(深度操作系统安装教程图解)
  • 深度操作系统安装教程(深度操作系统安装教程图解)
win7旗舰版和专业版区别(win7旗舰版跟专业版)

1、功能区别:Win7旗舰版比专业版多了三个功能,分别是Bitlocker、BitlockerToGo和多语言界面; 2、用途区别:旗舰版的功能是所有版本中最全最强大的,占用的系统资源,...

万能连接钥匙(万能wifi连接钥匙下载)

1、首先打开wifi万能钥匙软件,若手机没有开启WLAN,就根据软件提示打开WLAN开关;2、打开WLAN开关后,会显示附近的WiFi,如果知道密码,可点击相应WiFi后点击‘输入密码’连接;3、若不...

雨林木风音乐叫什么(雨林木风是啥)

雨林木风的创始人是陈年鑫先生。陈年鑫先生于1999年创立了雨林木风公司,其初衷是为满足中国市场对高品质、高性能电脑的需求。在陈年鑫先生的领导下,雨林木风以技术创新、产品质量和客户服务为核心价值,不断推...

aics6序列号永久序列号(aics6破解序列号)

关于AICS6这个版本,虽然是比较久远的版本,但是在功能上也是十分全面和强大的,作为一名平面设计师的话,AICS6的现有的功能已经能够应付几乎所有的设计工作了……到底AICC2019的功能是不是...

win7正在启动windows 卡住(win7正在启动windows卡住了 进入安全模式)
  • win7正在启动windows 卡住(win7正在启动windows卡住了 进入安全模式)
  • win7正在启动windows 卡住(win7正在启动windows卡住了 进入安全模式)
  • win7正在启动windows 卡住(win7正在启动windows卡住了 进入安全模式)
  • win7正在启动windows 卡住(win7正在启动windows卡住了 进入安全模式)
手机可以装电脑系统吗(手机可以装电脑系统吗怎么装)

答题公式1:手机可以通过数据线或无线连接的方式给电脑装系统。手机安装系统需要一定的技巧和软件支持,一般需要通过数据线或无线连接的方式与电脑连接,并下载相应的软件和系统文件进行安装。对于大部分手机用户来...