概述
JVM基础知识包含下面的内容:
- 常见的编程语言类型
- 关于跨平台、运行时(Runtime)与虚拟机(VM)
- 关于内存管理和垃圾回收(GC)
编程语言分类
- 机器语言:机器语言是直接给机器执行的二进制指令,每种 CPU 平台都有对应的机器语言。
- 汇编语言:相当于是给机器执行的指令,按照人可以理解的助记符表示,这样代码就非常长,但是性能也很好。
- 高级语言:是为了方便人来理解,进而快速设计和实现程序代码,一般跟机器语言和汇编语言的指令已经完全没有关系了,代码编写完成后通过编译或解释,转换成汇编码或机器码,之后再传递给计算机去执行。
高级语言为何高级
机器语言和汇编语言都是跟目标机器的 CPU 架构有直接联系,而高级语言一般就没有关系了,高级语言高级就高级在:不管是X86还是其他CPU,通过编译和解释过程之后都变成实际平台的目标代码,因此开发者不用关心目标平台的差异性
高级语言分类
- 按照虚拟机
- 有虚拟机:Java、Lua、Ruby…
- 无虚拟机:C、C++、C#、Golang…
- 按变量是否有确定的类型,还是类型可以随意变化
- 静态类型:Java、C、C++…
- 动态类型:所有的脚本类型的语言
- 按编译执行,还是解释执行
- 编译执行:C,C++,Golang,Rust,C#,Java,Scala,Clojure,Kotlin,Swift…
- 解释执行:JavaScript 的部分实现和 NodeJS,Python,Perl,Ruby…
- 按语言特点
- 面向过程:C,Basic,Pascal,Fortran…
- 面向对象:C++,Java,Ruby,Smalltalk…
- 函数式编程:LISP、Haskell、Erlang、OCaml、Clojure、F#…
注意:Java其实应该叫做半编译半解释语言,Java编译执行的一般步骤:.java文件经过编译器编译成.class文件 –> .class加载到JVM –> 在JVM上通过解释器,解释成机器代码在JVM上运行
其实Java还诞生了即时编译(JIT,just in time)的技术,也就是说对于不经常执行的语句,运行到时就解释执行,而对于热点代码就即时编译,这样能大大提高程序运行的性能
跨平台
为什么要跨平台
我们一般希望自己编写的程序,能在源代码级别或编译后能运行在不同的平台上,而不是面对不同的平台,都要编写不同的实现,同样我们希望自己编写的web程序,既能部署在Windows系统,又能部署到Linux平台,甚至时MacOS系统
通过跨平台技术,能够极大节省了开发和维护成本
现在的跨平台现状
一般来说解释型语言都是跨平台的,同一份脚本代码,可以由不同平台上的解释器解释执行,但是对于编译型语言,存在两种级别的跨平台: 源码跨平台和二进制跨平台。
- 源码跨平台(C++):
- 二进制跨平台(Java):
其中C++的模式要求每个平台都有对应的开发工具和编译器,而且在各个平台所依赖的开发库都需要是一致或兼容的,这样才能正常编译源码,本来C++的口号是:“一次编写,到处(不同平台)编译”,但实际情况上是一编译就报错,变成了“一次编写,到处调试,到处找依赖、改配置”。 大家可以想象,你编译一份代码,发现缺了几十个依赖,到处找还找不到,或者找到了又跟本地已有的版本不兼容,这是一件怎样令人绝望的事情。
而Java通过虚拟机解决了这个问题:源码只需要编译一次,然后把编译后的 class 文件或 jar 包,部署到不同平台,就可以直接通过安装在这些系统中的 JVM 上面执行,同时依赖问题也出现了Maven中央库用于下载和管理依赖,这样就实现了让同一个应用程序在不同的平台上直接运行的能力。
关于运行时(Runtime)与虚拟机(VM)
对于Java来说,JRE就是Java的运行时,包括虚拟机和核心依赖,可以说运行时提供了程序运行的基本环境,JVM 在启动时需要加载所有运行时的核心库等资源,然后再加载我们的应用程序字节码,才能让应用程序字节码运行在 JVM 这个容器里。
但一些语言是没有虚拟机的,编译打包时就把依赖的核心库和其他特性支持,一起静态打包或动态链接到程序中,比如 Golang 和 Rust,C#…,这样运行时就和程序指令组合在一起,成为了一个完整的应用程序,好处就是不需要虚拟机环境,坏处是编译后的二进制文件没法直接跨平台了。
关于内存管理和垃圾回收
内存资源总是有限而又宝贵的,只占用不释放,很快就会用完了。而程序得不到可用内存就会崩溃
内存管理
内存管理就是内存的生命周期管理,包括内存的申请、压缩、回收等操作,Java的内存管理就是GC,JVM的GC模块不仅管理内存的回收,也负责内存的分配和压缩整理。
JVM 在我们创建 Java 对象的时候去分配新内存,并使用 GC 算法,根据对象的存活时间,在对象不使用之后,自动执行对象的内存回收操作。
对于Golang和Rust这些语言,它们也有垃圾回收机制,但它们没有虚拟机,处理方式是怎么样的呢?
诀窍就在于运行时(Runtime),编译打包的时候,可以把内存使用分析的模块一起打包到应用程序中,在运行期间有专门的线程来分析内存使用情况,进而决定什么时候执行 GC,把不再使用的内存回收掉。 这样就算是没有虚拟机,也可以实现 GC。
Rust则更进一步:直接在语言规范层面限制了所有变量的生命周期,如果超出了一个明确的范围,就会不可用,这样在编译期就能直接知道每个对象在什么时候应该分配内存,什么时候应该销毁并回收内存,做到了很精确并且很安全的内存管理。