前言
Java crashesThe virtual machine is responsible for emulating a CPU, managing memory and devices, just like the operating system does for native applications (MS Office, web browsers etc).
轮廓
- class文件
- 类加载
- oop-kclass模型,gc
- 并发实现,同步器,unsafe
- 一般Linux进程与jvm进程
jvm 在体系中的位置
程序设计语言的发展就是一个“逐步远离计算机硬件,向着待解决的问题靠近”的过程。
jdk 安装目录含义
Class loaders are responsible for loading Java classes during runtime dynamically to the JVM (Java Virtual Machine). Also, they are part of the JRE (Java Runtime Environment). Hence, the JVM doesn’t need to know about the underlying files or file systems in order to run Java programs thanks to class loaders. 潜台词:Class loaders 是jre 类库的一部分但不是JVM 的一部分
逐步“消亡的”JVM
java编译器只是将java 代码编译为字节码,然后交给jvm解释执行(当然也有一部分的JIT)。
- 使得任何时候运行java 代码都需要装一个jdk
- 解释执行速度偏慢
阿里、华为(方舟)、GraalVM将 Java 程序编译为本地代码,在运行时无需传统 Java 虚拟机和运行时环境,只需操作系统类库支持即可。jvm 不再成为运行java 代码的标配。
更快的垃圾回收 ZGC
java 不再是那个熟悉的java
重塑云上的 Java 语言 阿里jvm团队做的一些工作
- ElasticHeap, Java 常因为耗资源而受诟病,其中最显著一点就是 Heap 对内存的占用,即便没有请求在处理也没有对象分配,进程仍然会保留完整的堆内存空间,保障 GC 进行分配内存和操作内存的快速敏捷。ElasticHeap 优化了Heap 的管理,让java 更“轻”一点
-
Wisp2,Java 同样可以拥有高性能的协程
- 在整个Java runtime中支持了协程调度,线程(比如 Socket.getInputStream().read() )阻塞会变成更轻量的协程切换。
- 完全兼容 Thread API ,在开启 Wisp2 的 JDK 中,Thread.start() 实际创建的是一个协程
- 支持 work stealing
- Grace,一个jvm监控、debug平台
云原生时代的java
云原生时代,Java危矣?Java与云原生的矛盾,来源于Java诞生之初,植入到它基因之中的一些基本的前提假设已经逐渐开始被动摇,甚至已经不再成立。
- 每一位Java的使用者都听说过“一次编写,到处运行”(Write Once, Run Anywhere)这句口号。面对这个问题,Java通过语言层虚拟化的方式,令每一个Java应用都自动取得平台无关(Platform Independent)、架构中立(Architecture Neutral)的先天优势,让同一套程序格式得以在不同指令集架构、不同操作系统环境下都能运行且得到一致的结果。面对相同的问题,今天的云原生选择以操作系统层虚拟化的方式,通过容器实现的不可变基础设施去解决。不可变基础设施的内涵已不再局限于方便运维、程序升级和部署的手段,而是升华一种为向应用代码隐藏环境复杂性的手段,是分布式服务得以成为一种可普遍推广的普适架构风格的必要前提。PS:字字珠玑
- 具备弹性与韧性、随时可以中断重启的微型服务的确已经形成了一股潮流,在逐步蚕食大型系统的领地。在微服务的背景下,提倡服务围绕业务能力而非技术来构建应用,不再追求实现上的一致,一个系统由不同语言,不同技术框架所实现的服务来组成是完全合理的。同时,微服务又对应用的容器化亲和性,譬如镜像体积、内存消耗、启动速度,以及达到最高性能的时间等方面提出了新的要求。这些却正好都是Java的弱项,如Scala语言编写的边车代理Linkerd,作为服务网格概念的提出者,却最终被Envoy所取代,其主要弱点之一也是由于Java虚拟机的资源消耗所带来的劣势。
- 由于Java“一切皆为对象”的前提假设,导致在处理一系列不同类型的小对象时,内存访问性能非常拉垮,这点是Java在游戏、图形处理等领域一直难有建树的重要制约因素。计算机硬件经过25年的发展,内存与处理器虽然都在进步,但是内存延迟与处理器执行性能之间的冯诺依曼瓶颈(Von Neumann Bottleneck)不仅没有缩减,反而还在持续加大,“RAM Is the New Disk”已经从嘲讽梗逐渐成为了现实。编译器的确在努力减少内存访问,从JDK 6起,HotSpot的即时编译器就尝试通过逃逸分析来做标量替换(Scalar Replacement)和栈上分配(Stack Allocations)优化,基本原理是如果能通过分析,得知一个对象不会传递到方法之外,那就不需要真实地在堆中创建完整的对象布局,完全可以绕过对象标识符,将它拆散为基本的原生数据类型来创建,甚至是直接在栈内存中分配空间,方法执行完毕后随着栈帧一起销毁掉。不过,逃逸分析是一种过程间优化(Interprocedural Optimization),非常耗时。
- Java语言抽象出来隐藏了各种操作系统线程差异性的统一线程接口,这曾经是它区别于其他编程语言(C/C++表示有被冒犯到)的一大优势,不过,统一的线程模型不见得永远都是正确的。 Java目前主流的线程模型是直接映射到操作系统内核上的1:1模型,这对于计算密集型任务这很合适,既不用自己去做调度,也利于一条线程跑满整个处理器核心。但对于I/O密集型任务,譬如访问磁盘、访问数据库占主要时间的任务,这种模型就显得成本高昂,主要在于内存消耗和上下文切换上:64位Linux上HotSpot的线程栈容量默认是1MB,线程的内核元数据(Kernel Metadata)还要额外消耗2-16KB内存,所以单个虚拟机的最大线程数量一般只会设置到200至400条,当程序员把数以百万计的请求往线程池里面灌时,系统即便能处理得过来,其中的切换损耗也相当可观。
体会
就像数据库系统百花齐放但SQL语法却宝刀不老一样。java 能保留的,最终也可能只剩下语法,通过对编译器、jvm等进行改造使上层代码“永葆青春”。
学习程序设计语言其实就是要学习语言提供的编程模型,不提供新编程模型的语言是不值得刻意学习的。
- C 语言提供了对汇编指令直接的封装。
- C++ 先是提供了面向对象,后来又提供了泛型编程。
- Java 把内存管理从开发者面前去掉了,后来引入的 Annotation 可以进行声明式编程。
- Ruby 提供了动态类型,以及由 Ruby on Rails 引导出的 DSL 风格。
- Scala 和 Clojure 提供了函数式编程。
- Rust 提供了新的内存管理方式,而 Libra 提供的 Move 语言则把它进一步抽象成了资源的概念。
一旦对于程序设计语言的模型有了新的认识,你就能理解一件事:一切语法都是语法糖。
- 类型是一种对内存的解释方式。
- class/struct 是把有相关性的数据存放到一起的一种数据组织方式。
- Groovy、Scala、Kotlin、Clojure 等 JVM 上的新语言,提供了一种不同于 Java 的封装 JVM 的方式。