`
IcyFenix
  • 浏览: 362877 次
  • 性别: Icon_minigender_1
  • 来自: 珠海
文章分类
社区版块
存档分类
最新评论

历史篇:Java虚拟机家族考

阅读更多
  声明:本文为笔者原创,但首发于InfoQ中文站,详见文末声明。

  说起Java虚拟机,许多Java程序员都会潜意识地把它与Sun[注1] HotSpot虚拟机等同看待,也许还有一些程序员会注意到BEA JRockit和IBM J9,但大多数人对JVM的认识都仅限于此了。
  从1996年初Sun发布的JDK 1.0中所包含的Sun Classic VM算起,Java虚拟机已经发展了15个年头,沧海桑田一瞬间,15年转眼而过,这期间曾经涌现、湮灭过许多或经典或优秀或有特色的虚拟机实现,在《Java虚拟机专栏》的第1篇中,我们先暂且把代码与技术放下,一起来回顾一下Java虚拟机家族的发展轨迹和历史变迁。

虚拟机始祖:Sun Classic / Exact VM
  以今天的视角来看,Sun Classic VM的技术可能很原始,这款虚拟机的使命也早已终结。但仅凭它 “世界上第一款商用Java虚拟机”的头衔,就足够有令历史有记住它的理由。
  1996年1月23日,Sun发布JDK 1.0,Java语言首次拥有了商用的正式运行环境,这个JDK中所带的虚拟机就是Classic VM。这款虚拟机只能使用纯解释器方式来执行Java代码,如果要使用JIT编译器那就必须进行外挂,但是假如外挂了JIT编译器,JIT编译器就完全接管了虚拟机的执行系统,解释器便不再工作了。用户在这款虚拟机上执行java –version命令,将会看到类似下面这行的输出:
java version "1.2.2"
Classic VM (build JDK-1.2.2-001, green threads, sunwjit)
  其中的“sunwjit”就是Sun提供的外挂编译器,其他类似的外挂编译器还有Symantec JIT和shuJIT等。由于解释器和编译器不能配合工作,这就意味着如果要使用编译器执行,编译器就不得不为对每一个方法,每一行代码都进行编译,而无论它们执行的频率是否具有编译的价值。基于程序响应时间的压力,这些编译器根本不敢应用编译耗时稍高的优化技术,因此这个阶段的虚拟机即使用了JIT编译器输出本地代码,执行效率也和传统的C/C++程序有很大差距,“Java语言很慢”的形象就是在这时候开始在用户心中树立起来的。
  Sun的虚拟机团队努力去解决Classic VM所面临的各种问题,提升运行效率,在JDK 1.2时,曾在Solaris平台上发布过一款名为Exact VM的虚拟机,它的执行系统已经具备现代高性能虚拟机雏形:如两级即时编译器、编译器与解释器混合工作模式等。Exact VM因它使用准确式内存管理(Exact Memory Management,也可以叫Non-Conservative/Accurate Memory Management)而得名,即虚拟机可以知道内存中某个位置的数据具体是什么类型。譬如内存中有一个32bit的整数123456,它到底是一个reference类型指向123456的内存地址还是一个数值为123456的整数,虚拟机将有能力分辨出来,这样才能在GC的时候准确判断堆上的数据是否还可能被使用。由于使用了准确式内存管理,Exact VM可以抛弃掉以前Classic VM基于handler的对象查找方式(原因是GC后对象将可能会被移动位置,如果地址为123456的对象移动到654321,在没有明确信息表明内存中哪些数据是reference的前提下,那虚拟机是不敢把内存中所有为123456的值改成654321的,所以要使用句柄来保持reference值的稳定),这样每次定位对象都少了一次间接查找的开销,提升执行性能。
  虽然Exact VM的技术相对Classic VM来说先进了许多,但是它命运显得十分英雄气短,在商业应用上只存在了很短暂的时间就被更为优秀的HotSpot VM所取代,甚至还没有来得及发布Windows和Linux平台下的商用版本。而Classic VM的生命周期则相对长了许多,它在JDK 1.2之前是Sun JDK中唯一的虚拟机,在JDK 1.2时,它与HotSpot VM并存,但默认是使用Classic VM(用户可用java –hotspot参数切换至HotSpot VM),而在JDK 1.3时,HotSpot VM成为默认虚拟机,它仍作为虚拟机的“备用选择”发布(使用java –classic参数切换),直到JDK 1.4的时候,Classic VM才正式退出商用虚拟机的历史舞台,与Exact VM一起进入了Sun Labs Research VM之中。

武林盟主:Sun HotSpot VM
  HotSpot VM相信所有Java程序员都知道,它是Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机。但不一定所有人都知道的是,这个目前看起来“血统纯正”的虚拟机在最初并非由Sun公司开发,而是由一家名为“Longview Technologies”的小公司设计的;甚至这个虚拟机最初并非是为Java语言而开发的,它来源于Strongtalk VM,而这款虚拟机中相当多的技术又是来源于一款支持Self语言实现“达到C语言50%以上的执行效率”的目标而设计的虚拟机,Sun公司注意到了这款虚拟机在JIT编译上有许多优秀的理念和实际效果,在1997年收购了Longview Technologies公司,从而获得了HotSpot VM。
  HotSpot VM既继承了Sun之前两款商用虚拟机的优点(如前面提到的准确式内存管理),也有许多自己新的技术优势,如它名称中的HotSpot指的就是它的热点代码探测技术(这里的描写带有“历史由胜利者书写”的味道,其实两个VM基本上是同时期的独立产品,HotSpot还稍早一些,HotSpot一开始就是准确式GC,而Exact VM之中也有与HotSpot几乎一样的热点探测,为了Exact VM和HotSpot VM哪个成为Sun主要支持的产品VM,在Sun公司内部还大吵过一场,HotSpot打败Exact并不能算技术上的胜利),HotSpot VM的热点代码探测能力可以通过执行计数器找出最具优编译价值的代码,然后通知JIT编译器以方法为单位进行编译。如果一个方法被频繁调用,或方法中有效循环次数很多,将会分别触发标准编译和OSR(栈上替换)编译动作。通过编译器与解释器恰当地协同工作,可以在最优化的程序响应时间与最佳执行性能中取得平衡,而且无需等待本地代码输出才能执行程序,即时编译的时间压力也相对减小,这样有助于引入更多的代码优化技术,输出质量更高的本地代码。
  2006年的JavaOne大会上,Sun宣布最终会把Java开源,并在随后的一年,陆续地将JDK的各个部分(其中当然也包括了HotSpot VM)在GPL协议下公开了源码,并在此基础上建立了OpenJDK。这样,HotSpot VM便成为了Sun JDK和OpenJDK两个实现极度接近的JDK项目的共同虚拟机。
  在2008年和2010年,Oracle分别收购了BEA和Sun公司,这样Oracle就同时拥有了这个星球上最优秀的两款Java虚拟机:JRockit VM和HotSpot VM。Oracle宣布在不久的将来(大约应在JDK 8的时候)会完成这两款虚拟机的整合工作,使之优势互补。整合的方式大致上是在HotSpot的基础上,移植JRockit的优秀特性,譬如使用JRockit的垃圾回收器与MissionControl服务,使用HotSpot的JIT编译器与混合的运行时系统。当HotSpot吸收了JRockit的全部功力之后,能否一统虚拟机的江湖,成为真正的武林盟主,我们拭目以待。

少数派:Sun Mobile-Embedded VM / Meta-Circular VM
  Sun公司所研发的虚拟机可不仅有前面介绍到的服务器、桌面领域的商用虚拟机,除此之外,Sun面对移动和嵌入式市场,也发布过虚拟机产品,另外还有一类虚拟机,在设计之初就没有抱着商用的目的,仅仅是用于研究、验证某种技术和观点,又或者是作为一些规范的标准实现。这些虚拟机对于大部分不从事相关领域开发的Java程序员来说可能比较陌生,Sun公司发布的其他Java虚拟机[注2]有:
  KVM
  KVM中的K是“Kilobyte”的意思,它强调简单,轻量,高度可移植,但是运行速度比较慢。在Androd、iOS等智能手机操作系统出现前曾经在手机平台上得到非常广泛应用。
  CDC/CLDC HotSpot Implementation
  CDC/CLDC全称是Connected(Limited)Device Configuration,在JSR-139/JSR-218规范中进行定义,它希望在手机、电子书、PDA等设备上建立统一的Java编程接口,而CDC-HI VM和CLDC-HI VM则是它们的一组参考实现。CDC/CLDC是整个Java ME的重要支柱,但从目前Android和Apple iOS二分天下的移动数字设备市场看来,在这个领域中,Sun的虚拟机所面临的局面远不如服务器和桌面领域乐观。
  Squawk VM
  Squawk VM是由Sun开发,运行于Sun SPOT(Sun Small Programmable Object Technology,一种手持的Wifi设备),也曾经运用于Java Card。这是一个Java代码比重很高的嵌入式虚拟机实现,其中诸如类加载器、字节码验证器、垃圾收集器、解释器、编译器和线程调度都是Java语言本身所完成的,仅仅靠C语言来编写设备I/O和必要的本地代码。
  JavaInJava
  JavaInJava是Sun公司1997年~1998年间所研发的一个实验室性质的虚拟机,从名字就可以看出,它试图以Java语言来实现Java语言本身的运行环境,既所谓的“元循环”(Meta-Circular,是指使用语言自身来实现其运行环境)。它必须运行在另外一个宿主虚拟机之上,内部没有JIT编译器,代码只能以解释模式执行。在上世纪末主流Java虚拟机都未能很好解决性能问题的时代,开发这种项目,其执行速度大家可想而知。
  Maxine VM
  Maxine VM和上面的JavaInJava非常相似,它也是一个几乎全部以Java代码实现(只有用于启动JVM的加载器使用C语言编写)的元循环Java虚拟机。这个项目于2005年开始,到现在仍然在发展之中,比起JavaInJava,Maxine VM就显得“靠谱”很多,它有先进的JIT编译器和垃圾收集器(但没有解释器),可在宿主模式或独立模式下执行,其执行效率已经接近了HotSpot Client VM的水平。

百家争鸣:BEA JRockit / IBM J9 VM
  前面介绍了Sun公司的各种虚拟机,除了Sun公司以外,其他组织、公司也研发过不少虚拟机实现,其中规模最大、最著名的就是BEA和IBM公司了。
  JRockit VM曾经号称“世界上速度最快的Java虚拟机”(广告词,貌似J9 VM也这样说过),它是BEA公司在2002年从Appeal Virtual Machines公司收购获得的虚拟机。BEA将其发展为一款专门为服务器硬件和服务端应用场景高度优化的虚拟机,由于专注于服务端应用,它可以不太关注于程序启动速度,因此JRockit内部不包含解析器实现,全部代码都靠即时编译器编译后执行。除此之外,JRockit的垃圾收集器和MissionControl服务套件等部分的实现,在众多Java虚拟机中也一直处于领先水平。
  IBM J9 VM并不是IBM公司唯一的Java虚拟机,不过是目前IBM主力发展的Java虚拟机,J9原本是内部开发代号,正式名称是“IBM Technology for Java Virtual Machine”,简称IT4J,只是这个名字太拗口了一点,普及程度不如J9。J9 VM最初是由IBM Ottawa实验室一个SmallTalk的虚拟机扩展而来的,当时这个虚拟机有一个bug是因为8k值定义错误引起,工程师们花了很长时间终于发现并解决了这个错误,此后这个版本的虚拟机就被称为K8了,后来扩展出支持Java的虚拟机就被称为J9了。与BEA JRockit专注于服务端应用不同,IBM J9的市场定位与Sun HotSpot比较接近,它是一款设计上从服务端到桌面应用再到嵌入式都全面考虑的多用途虚拟机,J9的开发目的是作为IBM公司各种Java产品的执行平台,它的主要市场在和IBM产品(如IBM WebSphere等)搭配以及在IBM AIX和z/OS这些平台上部署Java应用。
  除了BEA和IBM外,其他一些大公司如HP、SAP等也号称有自己的专属JDK和虚拟机,但是它们是通过从Sun购买版权的方式获得的,并非自己独立开发。

最终兵器:Azul VM / BEA Liquid VM
  我们平时所提及的“高性能Java虚拟机”一般是指HotSpot、JRockit、J9这类在通用平台上运行的商用虚拟机,但其实Azul VM和BEA Liquid VM这类特定硬件平台专有的虚拟机才是“高性能”的最终兵器。
  Azul VM是Azul Systems 公司在HotSpot基础上进行大量改进,运行于Azul Systems 公司的专有硬件Vega系统上的Java虚拟机,每个Azul VM实例都可以管理至少数十个CPU和数百GB的内存的硬件资源,并提供在巨大内存范围内实现可控的GC时间的垃圾收集器、为专有硬件优化的线程调度等优秀特性。在2010年,Azul开始从硬件转向软件,发布了自己的Zing JVM,可以在通用x86平台上提供接近于Vega系统的特性。
  Liquid VM即是现在的JRockit VE(Virtual Edition),它是BEA公司开发的,可以直接运行在自家Hypervisor系统上的JRockit VM的虚拟化版本,Liquid VM不需要操作系统的支持,或者说它自己本身实现了一个专用操作系统的必要功能,如文件系统、网络支持等。由虚拟机越过通用操作系统直接控制硬件可以获得很多好处,如在线程调度时,不需要再进行内核态/用户态的切换等,这样可以最大限度地发挥硬件的能力,提升Java程序的执行性能。

挑战者:Apache Harmony / Google Android Dalvik VM
  这节介绍的Harmony VM和Dalvik VM只能称作“虚拟机”,而不能称作“Java虚拟机”,但是这两款虚拟机(以及所代表的技术体系)对最近三年的Java世界产生了非常大的影响和挑战,甚至有悲观的评论家认为成熟的Java生态系统有崩溃的可能。
  Apache Harmony是一个Apache软件基金会旗下以Apache License协议开源的实际兼容于JDK 1.5和JDK 1.6的Java程序运行平台,这个介绍相当拗口。它包含自己的虚拟机和Java库,用户可以在上面运行Eclipse、Tomcat、Maven等常见的Java程序,但是……它没有通过TCK认证,所以我们不得不用那么一长串拗口的语言来介绍它,而不能用一句“Apache的JDK”来说明。如果一个公司要宣布自己的运行平台“兼容于Java语言”,那就必须要通过TCK(Technology Compatibility Kit)的兼容性测试,Apache基金会曾要求Sun公司提供TCK的使用授权,但是一直遭到拒绝,直到Oracle收购了Sun公司之后,双方关系越闹越僵,最终导致Apache愤然退出JCP(Java Community Process)组织,这是近代Java社区最严重的一次分裂。
  在Sun把JDK开源形成OpenJDK之后,Apache Harmony开源的优势被极大地削弱,甚至连Harmony项目的最大参与者IBM公司也宣布辞去Harmony项目管理主-席的职位,参与OpenJDK项目的开发。虽然Harmony没有真正大规模商业运用过,但是它的许多代码(基本上是Java库部分的代码)被吸纳进IBM的JDK7实现以及Google Android SDK之中,尤其是对Android的发展起了很大推动作用。
  说到Android,这个时下最热门的移动数码设备平台在最近3年间的发展所取得的成果已经远远超越了Java ME在过去十多年所获得的成果,Android让Java语言真正走进了移动数码设备领域,只是走的并非Sun公司原本想象的那一条路。
  Dalvik VM是Android平台的核心组成部分之一,它名字来源于冰岛一个名为Dalvik的小渔村。Dalvik VM并不是一个Java虚拟机,它没有遵循Java虚拟机规范,不能直接执行Java的class文件,使用寄存器架构而不是JVM中常见的栈架构。但是它与Java却又有着千丝万缕的联系,它执行dex(Dalvik Executable)文件可以通过class文件转化而来,使用Java语法编写应用程序,可以直接使用大部分的Java API等等。目前Dalvik VM随着Android一起处于迅猛发展阶段,在Android 2.2中已提供即时编译器实现,执行性能有了很大的提高。

没有成功,但并非失败:Mircosoft JVM及其他
  在十几年的Java虚拟机发展历程中,除去上面介绍那些被大规模商业应用过的Java虚拟机外,还有许多虚拟机是不为人知或者曾经绚丽过但最终湮灭的。我们以其中Mircorsoft公司的JVM来介绍一下。
  也许Java程序员听起来可能会觉得惊讶,微软曾经是Java技术的铁杆支持者。在Java语言诞生的初期(1996年~1998年,以JDK1.2发布之前为分界),它的主要的应用之一是在浏览器中运行Java Applets程序,微软为了在IE3中支持Java Applets应用而开发了自己的Java虚拟机,虽然这款虚拟机只有Windows平台的版本(这很正常吧?),但却是当时Windows下性能最好的Java虚拟机,它在1997和1998连续两年获得了《PC Magazine》杂志的“编辑选择奖”。但好景不长,在1997年10月,Sun公司正式以侵犯商标、不正当竞争等罪名控告微软,在随后对微软公司的垄断调查之中,这款虚拟机也曾作为证据之一被呈送法庭。这场官司的结果是微软赔偿2000万美金给Sun,承诺终止其Java虚拟机的发展,并逐步在产品中移除Java虚拟机相关功能。
  我们试想一下,如果当年Sun没有起诉微软公司,微软继续保持着对Java技术的热情,那Java的世界会变得更好还是更坏?.NET技术是否会发展起来?但历史是没有如果的。其他在本文中没有介绍到的Java虚拟机还包括有(当然,应该还有很多笔者所不知道的):
参考资料
  本文撰写时主要参考了以下资料:
声明:
  本文已经首发于InfoQ中文站,版权所有,原文为《Java虚拟机家族考》,如需转载,请务必附带本声明,谢谢。
  InfoQ中文站是一个面向中高端技术人员的在线独立社区,为Java、.NET、Ruby、SOA、敏捷、架构等领域提供及时而有深度的资讯、高端技术大会如QCon 、线下技术交流活动QClub、免费迷你书下载如《架构师》等。

脚注:
  注1:Sun与BEA分别在2010、2008年被Oracle公司收购,由于本文涉及到大量历史事件,为了避免混乱,依然保留Sun和BEA的名称。
  注2:这里把JavaME里面的虚拟机列为“少数派”是从大多数Java程序员的了解程度出发,从虚拟机部署数量来讲,JavaME远比JavaEE虚拟机多,毕竟服务器应用无法在数量上和移动、嵌入式设备比较的。
分享到:
评论
2 楼 chenzehe 2012-10-12  
好文,JVM的前生今世
1 楼 limcosln1andx 2011-10-06  
好文章啊

相关推荐

    深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)1

    Java虚拟机家族的演变反映了Java技术的发展历程。例如,HotSpot VM因其优秀的性能优化成为了主流,而其他如BEA JRockit、IBM J9等也各有特色。随着技术的进步,Java正逐渐摆脱对特定语言的依赖,新一代即时编译器...

    JavaSE程序设计课件:L01-Fundamentals of Java Programming - 1.pdf

    Java平台家族包括几个主要的组件: 1. Java Enterprise Edition(Java EE):面向企业级应用,提供服务器端的框架和服务,如Servlet、JSP、EJB等。 2. Java Standard Edition(Java SE):适用于桌面应用和服务器...

    java教案PPT(基础篇)

    Java由Sun Microsystems的詹姆斯·高斯林于1995年推出,它的主要特点是“一次编写,到处运行”,得益于Java虚拟机(JVM)。Java具有安全性、健壮性和高效性,这使得它在企业级应用、移动开发(尤其是Android)、网络...

    HCNA-HNTD V2.1入门培训教材.pdf

    在Java虚拟机家族的介绍中,作者列举了不同的虚拟机实现,包括但不限于Sun Classic/ExactVM、HotSpot VM、BEA JRockit/IBM J9 VM、BEA Liquid VM/Azul VM、Apache Harmony以及Google Android Dalvik VM等。...

    《Java程序设计》教案

    在Java程序设计中,首先会介绍Java的历史背景和特点,例如它的跨平台性(基于“Write Once, Run Anywhere”理念的Java虚拟机JVM)和面向对象的特性。接着,会讲解Java的基础语法,包括变量、数据类型、运算符、流程...

    java面试题及解答.rar

    - JVM(Java虚拟机):解释其工作原理,如何加载类,垃圾回收机制。 - 类与对象:定义、创建和实例化,封装、继承和多态的概念。 2. **语法特性** - 静态与非静态:理解两者的区别,何时使用静态成员和方法。 -...

    c++--++==java

    描述中提到的“这是一个关于Java发展历程的程序”,可能是指一个展示Java历史的项目或者教程,也可能是一个用Java编写的程序,演示了语言的演进过程。Java从最初的1.0版本发展至今,经历了多个重要版本,例如Java 2 ...

    java大纲资料.txt

    Java的特点包括跨平台性,它通过Java虚拟机(JVM)的机制实现了在不同的硬件和操作系统上运行相同的字节码。此外,Java是面向对象的,这表示它支持封装、继承和多态等面向对象的概念。Java还有垃圾回收机制,可以...

    用Java语言描述面向对象

    Java程序的源代码首先被编译成一种中间字节码,然后由Java虚拟机(JVM)解释执行。JVM负责管理内存、线程和其他系统级的任务。Java运行时环境(JRE)包括JVM和Java程序运行所需的类库。而Java开发工具包(JDK)包括...

    jsub:一种基于Java的编程语言

    4. **编译与运行**:由于基于Java,jsub的代码可能需要通过JVM(Java虚拟机)来编译和运行,遵循Java的"一次编写,到处运行"原则。 5. **开源文化**:作为一个开源项目,jsub可能有活跃的社区支持,用户可以通过...

    jdk1.8.0_181.zip

    此版本的JDK是Oracle公司发布的,为开发人员提供了完整的Java运行时环境和一组开发工具,包括Java编译器(javac)、Java虚拟机(JVM)、Java文档生成器(javadoc)以及Java调试器(jdb)等。 在Java开发环境中,JDK...

    J2ME及其移植2011

    - 手机需要支持一个能够正常运行的JVM(Java虚拟机)。 - 必须能够支持符合CLDC 1.1与MIDP 2.0规范的应用程序。 - 需要具备下载和安装应用程序的能力,以及管理已安装应用程序的功能,包括添加、删除、启动和查看...

    jdk-6u2-windows-i586-p.zip

    在使用JDK 6u2时,开发者可以利用其强大的javac编译器将源代码编译成字节码,然后通过Java虚拟机执行。JDK还提供了javadoc工具用于生成API文档,jdb用于调试Java程序,jar工具则用于打包和管理类文件。此外,JDK 6u2...

    JavaScript基础.pdf

    3. **执行环境**:JavaScript主要在浏览器中运行,而Java可以在多种平台上运行,包括通过Java虚拟机(JVM)在服务器端执行。 4. **类型系统**:JavaScript是弱类型语言,变量类型可变;Java是强类型语言,变量类型在...

    clojure in action

    Clojure是一种运行在Java虚拟机(JVM)上的函数式编程语言,它继承了Lisp家族的强大表达能力和灵活性,同时又充分利用了JVM平台的优势。Clojure的设计理念强调简洁性、可读性和高效性,这使得它成为了一个非常适合现代...

    开源项目-kode4food-sputter.zip

    Clojure是Lisp家族的一员,但它是为现代多核处理器和Java虚拟机(JVM)设计的。Clojure强调并发编程和内存安全,同时保持了Lisp的特性,如动态类型、宏系统和符号计算。 3. **Go语言介绍**: Go,也被称为Golang...

    基于J2ME的移动终端系统的分析与实现

    J2ME是Java家族的重要成员之一,它源自Java的核心理念——“一次编写,到处运行”。J2ME的设计初衷是为了满足资源受限的设备上的软件开发需求,如手机、PDA和其他嵌入式设备。它的架构主要由三部分组成:虚拟机...

    突发事件预案库管理系统_外文翻译.doc

    Java的虚拟机(JVM)允许代码在任何支持Java的平台上运行,极大地扩大了其应用范围。尽管C#和Java在语法和设计理念上有许多相似之处,但C#的诞生部分原因是微软对Java的回应,希望提供一个与.NET框架更紧密集成的、...

    Enigma-Machine:基于终端的应用程序,用于模拟二战期间使用的Enigma Machine的行为(基于Enigma Machine模型“ Enigma I”)

    这个目录可能包含了项目的所有源代码文件、资源文件、构建脚本等,用户可能需要使用Java编译器来编译这些源码,然后通过Java虚拟机执行,从而在自己的计算机上运行Enigma模拟器。 总的来说,Enigma-Machine项目是一...

    JavaEE技术之Linux - 副本.pptx

    文件还涵盖了Linux的发展历史,如Unix与Linux的关系,Linux家族的不同版本,以及Linux在不同计算时代的角色。此外,还提到了Linux系统安装、文件目录结构、用户与权限管理,以及常用命令和工具的使用。 【标签】: ...

Global site tag (gtag.js) - Google Analytics