Java虚拟机生态技术及其7种编程语言探秘(上)
【引言】
Java虚拟机(JVM)使计算机能够运行Java程序以及其他语言编写的程序,这些程序被编译成Java字节码。
JVM由一个规范来描述JVM实现中所需要的内容。
JVM规范可以确保Java程序在不同实现之间的互操作性,因此使用Java开发工具包(JDK)的程序作者不必担心底层硬件平台的特殊性。
JVM参考实现是由OpenJDK项目以开放源码的形式开发的,包括一个名为HotSpot的JIT编译器。Oracle公司提供的商业上支持的Java版本都是基于OpenJDK运行库。Eclipse OpenJ9是OpenJDK项目的另一个开源的JVM。
【JVM规范】
Java虚拟机是一个抽象的虚拟计算机, 它需要遵循一些JVM规范。
其中所使用的垃圾收集算法和Java虚拟机指令的任何内部优化如翻译成机器代码都没有被事先指定。
这样做的主要原因是为了避免对实现者造成不必要的约束。任何Java的应用程序只能在Java虚拟机的内部运行,这个虚拟机必须遵守这些的规范。
从Java平台标准版(J2SE)5.0开始,在Java社区进程下, JVM规范的修改作为JSR 924开发, 截止到2006年,为支持类文件格式(JSR 202)提出的修改作为JSR 924的维护版本进行。
发布的JVM规范蓝皮书前言中说:
“我们的意图是,此规范应该充分地描述Java虚拟机,以便使兼容的无尘室实现成为可能。Oracle提供了验证Java虚拟机实现是否正确的测试。”
Oracle的一个JVM被命名为HotSpot,另一个是JRockit,是从BEA Systems继承的。
无尘室的Java实现包括Kaffe、OpenJ9和Skelmir的CEE-J。
Oracle拥有Java商标,并该商标来认证实现套件是否完全符合Oracle的规范。
【类加载器】
JVM 字节代码的组织单位之一是类。一个类加载器实现必须能够识别和加载任何符合Java类文件格式的内容。除了必须要识别类文件之外,也可以自由得添加识别其他二进制形式的实现。
类加载器有三个基本任务,它们按照严格的顺序执行:
1. 加载:找到并导入类型的二进制数据
2. 连接:执行核查、准备和可选的引用解析。
a) 验证:确保输入类型的正确性。
b) 准备工作:为类变量分配内存,并将内存初始化为默认值。
c) 解析:将类型中的符号引用转换为直接引用。
3. 初始化:调用Java代码,将类变量初始化为合适的起始值。
一般来说,类加载器有两种类型:bootstrap类加载器和用户定义的类加载器。
每个Java虚拟机实现都必须有一个bootstrap类加载器,能够加载可信类。Java虚拟机规范并没有规定类加载器应该如何定位类。
【虚拟机架构】
JVM操作的是原始数据(整数和浮点数)和引用。
JVM从根本上来说是一个32位的机器。
Long和double类型是64位的,支持原生的,但由于每个单元都是32位,所以在一个帧的本地变量或操作符堆栈中消耗了两个存储单元。
boolean是作为8位字节值来操作的,0代表false,1代表true(虽然自从Java虚拟机规范第二版澄清了这个问题之后,boolean就被视为一种类型,但是在编译和执行的代码中,除了方法签名中的名字和boolean数组的类型外,boolean和字节之间没有什么区别。
布尔数组的类型是boolean[],但每个元素使用8位,JVM没有将boolean打包到位数组中的内置能力,所以除了类型不同外,它们的执行和行为与字节数组相同。在所有其他的使用中,布尔类型实际上对JVM来说是未知的,因为所有对布尔类型进行操作的指令也都是用来对字节进行操作的。
JVM有一个垃圾收集堆,用于存储对象和数组。代码、常量和其他类数据都存储在 "方法区"中。方法区在逻辑上是堆的一部分,但具体的实现可能会将方法区与堆分开处理,因为一般不会对“方法区”进行垃圾收集。
每个JVM线程也有自己的调用堆(为了清楚起见,称为"Java虚拟机堆"),它存储的是帧。每次调用一个方法时都会创建一个新的帧,当该方法退出时,该帧会被销毁。
每个帧提供了一个"操作符栈"和一个"本地变量"的数组。操作符栈用于管理计算的操作符以及接收被调用方法的返回值,而局部变量的作用与寄存器相同,也用于传递方法参数。因此,JVM既是一个栈机,又是一个寄存器机。
【字节码指令】
JVM有以下几组任务的指令:
1. 加载和存储
2. 算术
3. 类型转换
4. 对象创建和操作
5. 操作数栈管理(push/pop)
6. 控制传输(分支)
7. 方法调用和返回
8. 抛出异常
9. 基于监控器的并发性
其目的是二进制的兼容性。每个特定的主机操作系统都需要有自己的JVM和运行时的实现。这些JVM在语义上对字节码的解释方式是一样的,但具体实现可能不同。
比模拟字节码更复杂的是,必须兼容并有效地实现必须映射到每个主机操作系统的Java核心API。
这些指令是操作的是一组通用的抽象数据类型,而不是任何特定指令集架构的本机数据类型。
【JVM编程语言】
JVM编程语言是指任何具有可以用Java虚拟机托管的有效类文件来表达功能的语言。类文件包含Java虚拟机指令(Java字节码)和符号表,以及其他辅助信息。类文件格式是一种与硬件和操作系统无关的二进制格式,用于表示已编译的类和接口。
有几种JVM编程语言,既有移植到JVM的老语言,也有完全新的语言。
JRuby和Jython可能是现有语言中最著名的老语言移植,即Ruby和Python的移植。
在新语言中,Clojure、Apache Groovy、Scala和Kotlin是最受欢迎的语言。JVM语言的一个显著特点是它们之间是相互兼容的,因此,Scala库可以与Java程序一起使用,反之亦然。
Java 7 JVM在Java平台上实现了JSR 292:支持动态类型化语言,这是一个在JVM中支持动态类型化语言的新功能。该功能是在达芬奇机器项目中开发的,其任务是扩展JVM,使其支持Java以外的语言。
【JRuby】
JRuby类似于标准的Ruby解释器,区别只是用Java语言来编写。 JRuby与Ruby有一些相同的概念,包括面向对象编程和动态类型。其关键的区别在于,JRuby与Java紧密结合,可以直接从Java程序中调用它。
安装
l 下载:https://www.jruby.org/download
l 将JRuby提取到一个目录中。
l 将该目录的bin子目录添加到你的路径。
l 测试一下:jruby -v
JRuby调用Java
JRuby的一个强大功能是它能够调用Java平台的类。要做到这一点,首先必须通过调用 "require 'java'"来加载JRuby的Java支持。下面的例子创建了一个带有JLabel的Java JFrame:
require 'java'
frame = javax.swing.JFrame.new
frame.getContentPane.add javax.swing.JLabel.new('Hello, World!')
frame.setDefaultCloseOperation javax.swing.JFrame::EXIT_ON_CLOSE
frame.pack
frame.set_visible true
JRuby还允许用户使用更类似于Ruby的下划线方法命名来调用Java代码,并将JavaBean属性作为属性来引用。
frame.content_pane.add label
frame.visible = true
从Java中调用JRuby
JRuby可以很容易地被Java调用,这可以通过使用JSR 223 Scripting for Java 6或Apache Bean Scripting框架来做到。
// 使用JSR 233脚本编写Java 6的例子
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine rbEngine = mgr.getEngineByExtension("rb");
try {
rbEngine.eval("puts 'Hello World!'");
} catch (ScriptException ex) {
ex.printStackTrace();
}
【Jython】
Jython是Python编程语言的一个实现,运行在Java平台上。在1999年之前被称为JPython。
Jython程序可以导入和使用任何Java类。除了一些标准模块外,Jython程序使用的是Java类而不是Python模块。Jython几乎包含了标准Python编程语言发行版中的所有模块,只是缺少一些最初在C语言中实现的模块。Jython可以按需或静态地将Python源代码编译成Java字节码。
安装
下载:https://www.jython.org/download
java -jar D:/tool/jython-standalone-2.7.2.jar
在Java应用程序中运行Python代码
import org.python.util.PythonInterpreter;
public class JythonHelloWorld {
public static void main(String[] args) {
try(PythonInterpreter pyInterp = new PythonInterpreter()) {
pyInterp.exec("print('Hello Python World!')");
}
}
}
在Python代码中运行Java
from java.lang import System # Java import
print('Running on Java version: ' + System.getProperty('java.version'))
print('Unix time from Java: ' + str(System.currentTimeMillis()))
【Clojure】
Clojure是Java平台上的Lisp编程语言的一种现代、动态、功能化的实现。
和其他Lisp编程语言一样,Clojure把代码当作数据来处理,并有一个Lisp宏系统。
目前的开发过程是由社区驱动的,由Rich Hickey监督。
Clojure提倡不可变性,使用不可变的数据结构,鼓励程序员对身份及其状态进行显式管理。
这种注重用不可变值和显式时间递进构造进行编程的做法是为了开发出更健壮的程序,特别是在开发并发程序时追求程序简单、快速。
它的类型系统完全是动态的,最近在努力寻求渐进式类型化的实现.
Clojure的商业支持是由Cognitect提供的,Clojure会议每年都会在全球范围内举办,其中最著名的是Clojure/conj。
安装
步骤:https://clojure.org/guides/getting_started
示例1:
user=> (+ 3 4)
7
user=> (+ 10 *1)
17
user=> (+ *1 *2)
24
示例2:
(ns clojure.examples.hello
(:gen-class))
(defn hello-world []
(println "Hello World"))
(hello-world)
示例3:
(ns reader.tasklist
(:gen-class ; <label id="code.tasklist.genclass"/>
:extends org.xml.sax.helpers.DefaultHandler ; <label id="code.tasklist.extends"/>
:state state ; <label id="code.tasklist.state"/>
:init init) ; <label id="code.tasklist.init"/>
(:use [clojure.java.io :only (reader)])
(:import [java.io File]
[org.xml.sax InputSource]
[org.xml.sax.helpers DefaultHandler]
[javax.xml.parsers SAXParserFactory]))
【Groovy】
Apache Groovy是一种与Java语法兼容的面向对象编程语言,适用于Java平台。它既是一种静态语言,也是一种动态语言,具有类似于Python、Ruby和Smalltalk的特性。
它既可以作为Java平台的编程语言,也可以作为Java平台的脚本语言,被编译成Java虚拟机(JVM)字节码,并可与其他Java代码和程序库无缝互操作。
Groovy使用一种类似于Java的卷标式语法。Groovy支持闭合(Closure)、多行字符串和嵌入字符串中的表达式。
Groovy的一部分强项在于它的AST转换,通过注解触发。
功能
大多数有效的Java文件也是有效的Groovy文件。虽然这两种语言很相似,但Groovy代码可以更紧凑,因为它不需要Java所要求的所有元素,这使得Java程序员可以从熟悉的Java语法开始,逐步学习Groovy,然后再掌握更多的Groovy编程习惯用语。
Groovy有如下Java没有的功能: 静态和动态键入(使用关键字def)、操作符重载、列表和关联数组(map)的原生语法、正则表达式的原生支持、多态迭代、字符串插值、增加了辅助方法,以及安全导航操作符?.来自动检查空指针(例如,variable.method(),或variable.field)。
从第2版开始,Groovy还支持模块化(能够根据项目的需要,只提供所需的jar包,从而减少了Groovy的程序库大小)、类型检查、静态编译、Project Coin语法增强、多语言块和使用Java 7中引入的invokedynamic指令的持续性能增强。
Groovy通过内嵌式文档对象模型(DOM)语法,为各种标记语言(如XML和HTML)提供了本地支持。这个功能可以用统一而简洁的语法和编程方法来定义和操作许多类型的异构数据资产。
与Java不同,Groovy源代码文件可以作为(未编译的)脚本执行。
如果它包含任何类定义之外的代码,比如一个有主方法的类,或者是一个Runnable或GroovyTestCase,那么它就可以作为一个(未编译的)脚本执行。Groovy 脚本在执行之前会被完全解析、编译并生成(类似于 Python 和 Ruby)。这些都是在引擎下进行的,编译后的版本不会作为过程的工件保存。
安装
步骤:https://groovy-lang.org/install.html
示例
GroovyBeans, 属性
GroovyBeans是JavaBeans的Groovy版本。Groovy隐式地生成了getter和setter。在下面的代码中,setColor(String color)和getColor()是隐式生成的。最后两行看似直接访问颜色,实际上是在调用隐式生成的方法。
class AGroovyBean {
String color
}
def myGroovyBean = new AGroovyBean()
myGroovyBean.setColor('baby blue')
assert myGroovyBean.getColor() == 'baby blue'
myGroovyBean.color = 'pewter'
assert myGroovyBean.color == 'pewter'
Groovy为处理列表和映射(Map)提供了与Java类似的简单、一致的语法。
def movieList = ['Dersu Uzala', 'Ran', 'Seven Samurai'] // 看起来像一个数组,其实是一个列表
assert movieList[2] == 'Seven Samurai'
movieList[3] = 'Casablanca' // 添加一个元素到列表中
assert movieList.size() == 4
def monthMap = [ 'January' : 31, 'February' : 28, 'March' : 31 ] //声明一个映射(Map)
assert monthMap['March'] == 31 // 访问数据项
monthMap['April'] = 30 // 添加一个数据项
assert monthMap.size() == 4
原型扩展
Groovy通过ExpandoMetaClass、Extension Modules(仅在Groovy 2中)、类似Objective-C的分类表和DelegatingMetaClass提供了对原型扩展的支持。
ExpandoMetaClass提供了一种特定领域的语言(DSL)来方便地表达类中的变化,类似于Ruby的开放类概念:
Number.metaClass {
sqrt = { Math.sqrt(delegate) }
}
assert 9.sqrt() == 3
assert 4.sqrt() == 2
Groovy通过原型修改的代码,这在Java中是看不到的。只有这些属性或者方法通过元类注册表注册以后才能从Java中访问更改后的代码。
Groovy还允许重载方法,如getProperty()、propertyMissing()等,使开发者能够以简化的以方面为导向的方式拦截对对象的调用,并为它们指定一个动作。下面的代码使类java.lang.String能够响应hex属性:
enum Color {
BLACK('#000000'), WHITE('#FFFFFF'), RED('#FF0000'), BLUE('#0000FF')
String hex
Color(String hex) {
this.hex = hex
}
}
String.metaClass.getProperty = { String property ->
def stringColor = delegate
if (property == 'hex') {
Color.values().find { it.name().equalsIgnoreCase stringColor }?.hex
}
}
assert "WHITE".hex == "#FFFFFF"
assert "BLUE".hex == "#0000FF"
assert "BLACK".hex == "#000000"
assert "GREEN".hex == null
Grails框架广泛使用元编程来实现GORM动态查找器,比如User.findByName('Josh')等。
点和括号
Groovy的语法允许在某些情况下省略括号和点。以下是groovy的代码:
take(coffee).with(sugar, milk).and(liquor)
可写成:
take coffee with sugar, milk and liquor
这使得特定领域语言(DSL)的开发看起来像普通英语一样。
功能型编程
虽然Groovy主要是一种面向对象的语言,但它也提供了功能型编程的特性。
闭合(Closures)
根据Groovy的文档。Groovy中闭合的工作原理类似于'方法指针',使代码可以在以后的时间点修改和运行。Groovy的闭合支持自由变量,即没有作为参数显式传递但在声明的上下文中存在的变量,如部分应用(它称之为 "curry"),委托、隐式、类型化和非类型化参数
当对一个确定类型的集合进行处理时,可以推断出传给集合上的操作的闭合:
list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
/*
* 非零数被强制为真,所以当 it % 2 ==0 (偶数)时,它是假的。
* 隐含的 "it"参数的类型可以被IDE推断为整数。
* 它也可以写成:
* list.findAll { Integer i -> i % 2 }。
* list.findAll { i -> i % 2 }
*/
def odds = list.findAll { it % 2 }
assert odds == [1, 3, 5, 7, 9]
可以将一组表达式写在一个闭合块中,而不需要引用其实现,那么在稍后的时候可以使用委托的方式分配响应的对象:
// 这段代码包含的表达式没有指向一个实现。
def operations = {
declare 5
sum 4
divide 3
}
/*
* 这个类将处理一些操作,它可以在上述闭合中使用。另一个类也
* 可以声明相同的方法,例如在计算中使用webservice操作。
*/
class Expression {
BigDecimal value
/*
* 在调用declare方法时如果传递了一个Integer作为参数,但它被强制转化为BigDecimal。如果这个类有一个'declare(Integer)'方法,怎会首先使用这个方法。
*/
def declare(BigDecimal value) {
this.value = value
}
def sum(BigDecimal valueToAdd) {
this.value += valueToAdd
}
def divide(BigDecimal divisor) {
this.value /= divisor
}
def propertyMissing(String property) {
if (property == "print") println value
}
}
// 这里定义的是上面的代码块中的表达式。
operations.delegate = new Expression()
operations()
部分应用-Curry
Curry通常被称为部分应用,这个Groovy的特性允许将闭合的参数设置为默认参数中的任何一个参数,用绑定的值创建一个新的闭合。
向curry()方法提供一个参数将修改一个参数。提供N个参数将修改参数1 ....N。
def joinTwoWordsWithSymbol = { symbol, first, second -> first + symbol + second }
assert joinTwoWordsWithSymbol('#', 'Hello', 'World') == 'Hello#World'
def concatWords = joinTwoWordsWithSymbol.curry(' ')
assert concatWords('Hello', 'World') == 'Hello World'
def prependHello = concatWords.curry('Hello')
//def prependHello = joinTwoWordsWithSymbol.curry(' ', 'Hello')
assert prependHello('World') == 'Hello World'
Curry也可以用rcurry()进行反向使用(将参数N修改为N - 1)。
def power = { BigDecimal value, BigDecimal power ->
value**power
}
def square = power.rcurry(2)
def cube = power.rcurry(3)
assert power(2, 2) == 4
assert square(4) == 16
assert cube(3) == 27
Groovy还支持惰性评估(Lazy Evaluation),减少(Reduce)/折叠(Fold),无限结构和不变性等等。
JSON和XML处理
在JavaScript Object Notation(JSON)和XML处理上,Groovy采用了Builder模式,使得数据结构的创建没有那么繁琐。例如,下面的XML:
<languages>
<language year="1995">
<name>Java</name>
<paradigm>object oriented</paradigm>
<typing>static</typing>
</language>
<language year="1995">
<name>Ruby</name>
<paradigm>functional, object oriented</paradigm>
<typing>duck typing, dynamic</typing>
</language>
<language year="2003">
<name>Groovy</name>
<paradigm>functional, object oriented</paradigm>
<typing>duck typing, dynamic, static</typing>
</language>
</languages>
可以通过以下Groovy代码生成:
def writer = new StringWriter()
def builder = new groovy.xml.MarkupBuilder(writer)
builder.languages {
language(year: 1995) {
name "Java"
paradigm "object oriented"
typing "static"
}
language (year: 1995) {
name "Ruby"
paradigm "functional, object oriented"
typing "duck typing, dynamic"
}
language (year: 2003) {
name "Groovy"
paradigm "functional, object oriented"
typing "duck typing, dynamic, static"
}
}
并且还可以通过StreamingMarkupBuilder以流媒体的方式进行处理。要想生成JSON,可以将MarkupBuilder换成JsonBuilder。
要解析数据并搜索,可使用Groovy的findAll方法:
def languages = new XmlSlurper().parseText writer.toString()
//这里使用了Groovy的regex正则表达式语法,用了匹配器(=~),该匹配器执行后返回一个布尔类型值:如果该值包含我们的字符串,则为true,否则为false。
def functional = languages.language.findAll { it.paradigm =~ "functional" }
assert functional.collect { it.name } == ["Groovy", "Ruby"]
字符串插值
在Groovy中,字符串可以通过使用GStrings对变量和表达式进行插值:
BigDecimal account = 10.0
def text = "The account shows currently a balance of $account"
assert text == "The account shows currently a balance of 10.0"
包含变量和表达式的GStrings必须用双引号声明。
一个复杂的表达式必须用大括号括起来:
BigDecimal minus = 4.0
text = "The account shows currently a balance of ${account - minus}"
assert text == "The account shows currently a balance of 6.0"
// 如果没有括号来隔离表达式,结果是这样的:
text = "The account shows currently a balance of $account - minus"
assert text == "The account shows currently a balance of 10.0 - minus"
表达式评估可以通过使用箭头语法进行延迟:
BigDecimal tax = 0.15
text = "The account shows currently a balance of ${->account - account*tax}"
tax = 0.10
//税额是在GString申报后改变的。
//只有当表达式被实际执行时,变量才会被绑定。
assert text == "The account shows currently a balance of 9.000"
抽象语法树转换
根据Groovy自己的文档, “当Groovy编译器编译Groovy脚本和类时,在这个过程中的某个时刻,源码最终会以具体语法树的形式在内存中,然后转化为抽象语法树。
AST 转换的目的是让开发人员能够在编译过程中定位AST, 以便在AST转化成JVM运行的字节码之前,能够对其进行修改。
AST 转换为Groovy提供了改进的编译时刻元编程能力,使其在语言层面上有了强大的灵活性,同时又不会对运行时的性能造成影响。”
Groovy中的AST的例子有:
l 类别和Mixin转换
l 不可变的AST宏
l 新化改造
l 单一实例转换
等等。
Traits
根据Groovy的文档,“Traits是语言的一种结构化构造,它允许:行为的组成、接口的运行时实现、行为重写以及与静态类型检查/编译的兼容性。”
Traits可以被看作是携带默认实现和状态的接口。一个trait是用trait关键字定义的:
trait FlyingAbility { /*声明trait */
String fly() { "I'm flying!" } /* 声明trait的方法 */
}
然后,它可以像普通的接口一样使用关键字implements:
class Bird implements FlyingAbility {} /* 将FlyingAbility属性添加到Bird类的功能中 */
def bird = new Bird() /* 实例化一个新的Bird */
assert bird.fly() == "I'm flying!" /* 鸟类自动获取FlyingAbility trait的行为 */
Traits的应用范围很广,从应用开发到测试都可以用到。
产品案例
采用Groovy的著名例子包括:
l Adaptavist ScriptRunner,嵌入了一个Groovy实现,用于自动化和扩展Atlassian工具,全球超过20000个组织在使用。
l Apache OFBiz,开源的企业资源规划(ERP)系统,使用Groovy开发。
l Eucalyptus作为一个云管理系统,使用了大量的Groovy代码。
l Gradle是一个热门的构建自动化工具,使用Groovy开发。
l LinkedIn在他们的一些子系统中使用了Groovy和Grails。
l Jenkins,一个用于持续集成的平台。从版本2开始,Jenkins提供了一个Pipeline插件,允许用Groovy编写构建指令。
l Liferay, 在他们的kaleo工作流程中使用了groovy。
l Sky.com使用Groovy和Grails来服务海量的在线媒体内容。
l SmartThings是一个面向智能家居和消费类物联网的开放平台,它使用了Groovy的面向安全的子集。
l SoapUI用Groovy作为Webservice测试开发语言。
l Survata是一家市场研究初创公司,它使用Groovy和Grails。
l 欧洲专利局(EPO)在Groovy基础上开发了一种数据流编程语言,"利用了每个国家专利局沟通过程中的相似之处,从而将其转化为一个单一的、通用的过程"。
l Groovy可以集成到任何JVM环境中,JBoss Seam框架除了Java之外,还提供了Groovy作为一种开发语言,开箱即用。
l vCalc.com在其数学包引擎中使用Groovy。
l Wired.com使用Groovy和Grails开发了网站的独立产品评论部分。
l XWiki SAS在其合作的开源产品中使用Groovy作为脚本语言。
开发工具IDE支持
许多集成开发环境(IDE)和文本编辑器都支持Groovy:
l Android Studio,用于制作Android应用程序的IDE
l Atom Editor
l Eclipse,通过Groovy-Eclipse插件
l Emacs,通过groovy-emacs-mode项目的groovy-mode。
l IntelliJ IDEA社区版,Grails/Griffon仅在终极版中可以使用。
l JDeveloper,与Oracle ADF配合使用
l jEdit,一个Java平台的高级文本编辑器。
l Kate是KDE的高级文本编辑器,支持Groovy和其他200多种文件格式。
l NetBeans,从6.5版本开始支持Groovy
l Notepad++,一个Microsoft Windows上的高级文本编辑器。
l Sublime Text,一个跨平台文本编辑器。
l TextMate
l Visual Studio Code
l UltraEdit,通用程序编辑器
- 点赞
- 收藏
- 关注作者
评论(0)