Java虚拟机生态技术及其7种编程语言探秘(下)
【引言】
Java虚拟机(JVM)使计算机能够运行Java程序以及其他语言编写的程序,这些程序被编译成Java字节码。
JVM由一个规范来描述JVM实现中所需要的内容。
JVM规范可以确保Java程序在不同实现之间的互操作性,因此使用Java开发工具包(JDK)的程序作者不必担心底层硬件平台的特殊性。
JVM参考实现是由OpenJDK项目以开放源码的形式开发的,包括一个名为HotSpot的JIT编译器。Oracle公司提供的商业上支持的Java版本都是基于OpenJDK运行库。Eclipse OpenJ9是OpenJDK项目的另一个开源的JVM。
替代性实现
Groovy有一个替代性的实现:
l Grooscript将Groovy代码转换为JavaScript代码。 虽然Grooscript与Apache Groovy相比有一些局限,但它可以在服务器和客户端使用领域类。它还提供了Grails 3.0版本的插件支持,以及在线代码转换。
【Scala】
Scala是一种通用编程语言,它提供了对功能型编程的支持和强大的静态类型系统。Scala的设计是简洁的,它的许多设计旨在解决针对Java的一些批评。
Scala的源码最终被编译成Java字节码,产生的可执行代码就可以在Java虚拟机上运行。
Scala提供了与Java的语言互操作性,用两种语言编写的程序库可以相互在Scala或Java的代码中调用。
与Java一样,Scala也是面向对象的,它也是使用一种大括号来标识程序内部的Scope,这是C家族编程语言的特点。
与Java不同的是,Scala具有Scheme、标准ML和Haskell等功能型编程语言的许多特性,包括卷积、不变性、惰性评估和模式匹配。
它还有一个高级类型系统,支持代数数据类型、协方差和反方差、高阶类型(Higher-Order)和匿名类型。
Scala的其他特征如运算符重载、可选参数、命名参数和原始字符串, 是Java中没有的。
相反,Scala中没有异常检查,这在Java中是存在的,这一点是非常有争议的。
Scala这个名字是可扩展性和语言的合成,寓意是它的设计会随着用户的需求而改进。
安装
步骤:https://docs.scala-lang.org/getting-started/index.html
示例
Java与Scala的函数例子对比
// Java:
int mathFunction(int num) {
int numSquare = num*num;
return (int) (Math.cbrt(numSquare) +
Math.log(numSquare));
}
// Scala: 从Java直接转换
// 不需要导入; scala.math
// 已经作为 "math "导入
def mathFunction(num: Int): Int = {
var numSquare: Int = num*num
return (math.cbrt(numSquare) + math.log(numSquare)).
asInstanceOf[Int]
}
另一种写法:
// Scala:
// 使用类型推理,省略了 "return "语句。
// 使用 "toInt"方法,声明numSquare是不可变的。
import math._
def mathFunction(num: Int) = {
val numSquare = num*num
(cbrt(numSquare) + log(numSquare)).toInt
}
一些语法差异:
l Scala不需要用分号来结束语句。
l 值类型用大写:Int, Double, Boolean,而不是int, double, boolean。
l 参数和返回的类型跟在Pascal一样在后面,而不是像C语言那样在前面。
l 方法前必须加上def。
l 本地变量或类变量必须在前面加上val表示不可变的变量或var表示可变的变量。
l 返回操作符在函数中是不必要的,虽然允许返回;最后执行的语句或表达式的值通常是函数的值。
l Scala用foo.asInstanceOf[Type]代替Java的 cast 运算符(Type)foo,或者专门的函数,如toDouble或toInt。
l Java使用import foo.*;,Scala使用的是import foo._。
l 函数或方法foo()也可以作为单纯的foo调用;方法 thread.send(signo)也可以作为单纯的线程发送signo调用;方法foo.toString()也可以作为单纯的foo toString调用。
这些不同是为了特定领域语言的支持。
其他一些基本的语法差异:
l 数组引用被写成函数调用,例如: array(i) 而不是 array[i]。(在Scala内部,前者会扩展成array.apply(i),返回引用)
l 通用类型被写成例如List[String]而不是Java的List<String>。
l Scala用的是实际的单实例类Unit,而不是伪类型void。
类的例子对比
下面的例子Java和Scala中的类的定义的对比。
// Java:
public class Point {
private final double x, y;
public Point(final double x, final double y) {
this.x = x;
this.y = y;
}
public Point(
final double x, final double y,
final boolean addToGrid
) {
this(x, y);
if (addToGrid)
grid.add(this);
}
public Point() {
this(0.0, 0.0);
}
public double getX() {
return x;
}
public double getY() {
return y;
}
double distanceToPoint(final Point other) {
return distanceBetweenPoints(x, y,
other.x, other.y);
}
private static Grid grid = new Grid();
static double distanceBetweenPoints(
final double x1, final double y1,
final double x2, final double y2
) {
return Math.hypot(x1 - x2, y1 - y2);
}
}
// Scala
class Point(
val x: Double, val y: Double,
addToGrid: Boolean = false
) {
import Point._
if (addToGrid)
grid.add(this)
def this() = this(0.0, 0.0)
def distanceToPoint(other: Point) =
distanceBetweenPoints(x, y, other.x, other.y)
}
object Point {
private val grid = new Grid()
def distanceBetweenPoints(x1: Double, y1: Double,
x2: Double, y2: Double) = {
math.hypot(x1 - x2, y1 - y2)
}
}
上面的代码显示了Java和Scala在定义类的时候的一些概念上的差异:
l Scala没有静态变量或方法。相反,它有单实例对象,本质上是只有一个实例的类。单实例对象是用object而不是类来声明的。通常把静态变量和方法放在一个与类名相同的单实例对象中,这个单实例对象就被称为同伴对象。
l 对应构造函数参数,Scala有类参数,类似于函数的参数。当使用val或var修改器声明时,也会定义为相同的名称的成员变量,并从类参数中自动初始化。
l Scala中的默认可见性是public。
语法的灵活性
如上所述,与Java相比,Scala在语法上有很大的灵活性。下面是一些灵活性的例子:
l 分号不是必须的;
l 任何方法都可以用作信息操作符。如:"%d苹果".format(num)和"%d苹果 "format num是等价的。
l apply和update方法有语法短式。foo()=42 是foo.update(42)的语法短式。
l Scala区分了no-parens(def foo = 42)和 empty-parens(def foo() = 42)方法。
l 以冒号(:)结尾的方法名称参数在左侧,而接收方在右侧。比如, 4 :: 2 :: Nil 跟 Nil.::(2).::(4)相同。
l 类成员变量可以作为独立的getter和setter方法透明地实现。
l 允许在方法调用中使用大括号代替括号。
l For-expressions可以容纳任何定义单元方法的类型,如map、flatMap和filter等。
就其本身而言,这些例子有的看起来是有问题的,但总的来说,它们的目的是允许在Scala定义特定领域,而不需要扩展编译器。例如,Erlang的特殊语法,即actor ! 消息可以在Scala库中实现,而不需要语言扩展。
统一的类型系统
Java对原始类型(如int和boolean)和引用类型(任何类)进行了明确的区分。只有引用类型才允许继承,基类是java.lang.Object。
在Scala中,所有的类型可以继承,基类是Any,其直接的子类是AnyVal(值类型,类似Int和Boolean)和AnyRef(引用类型,类似Java中的引用类型)。
这意味着,在Scala中不存在像Java中对底层类型和盒装类型(例如int vs. Integer)的区分;盒装和解盒对用户来说是完全透明的。Scala 2.10允许用户定义新的值类型。
For表达式
Scala 有 for-expressions,它类似于 Haskell 等语言中的 list comprehensions,或 Python 中的 list comprehensions 和 generator expressions 的组合,而不是 Java 的 "foreach "循环,用于在迭代器中循环。使用 yield 关键字的 for-expressions 允许通过迭代一个现有的集合来生成一个新的集合,返回一个相同类型的新集合。
它们被编译器翻译成一系列的map、flatMap和filter调用。如果不使用 yield,代码会翻译成 foreach。
val s = for (x <- 1 to 25 if x*x > 50) yield 2*x
运行它的结果是:
Vector(16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50)
一个比较复杂的例子是:
// 给出一个映射,指定一组推文中提到的Twitter用户,
//和每个用户被提及的次数,查询用户的次数
///从已知政客的映射中返回一个新的民主党政客的映射。
val dem_mentions = for {
(mention, times) <- mentions
account <- accounts.get(mention)
if account.party == "Democratic"
} yield (account, times)
Expression ( mention, times) <- mentions是模式匹配的一个例子。迭代映射会返回一组键-值的图元组,而模式匹配可以很容易地将这些图元组分解成键和值的独立变量。同样的,理解的结果也会返回键-值tuple,因为源对象(来自于变量 mentions)是一个map,所以这些tuple会被自动构建成map。需要注意的是,如果 mentions 是一个列表、集、数组或其他图元组的集合,那么上面完全相同的代码将产生一个相同类型的新集合。
功能型倾向
在支持Java中所有的面向对象特性的同时, Scala还以各种方式增强了这些特性,Scala还提供了大量通常只有在功能型编程语言中才有的功能。这些特性加在一起,使得Scala程序可以用几乎完全的功能型风格来编写,同时也允许功能型和面向对象的风格混合使用。
例如:
l 不区分语句和表达式
l 类型推理
l 具有捕捉语义的匿名函数(即闭合)。
l 不可变的变量和对象
l 惰性评价
l 有限的连续(自2.8以来)
l 高阶函数
l 嵌套函数
l Currying
l 模式匹配
l 代数数据类型(通过案例类)
l Tuples
一切都是表达式
这方面与C语言或Java不同,与Lisp等语言却类似,Scala不区分语句和表达式。
实际上,所有的语句都是求值的表达式。
在C语言或Java中被声明为返回void的函数,以及像while这样逻辑上不返回值的语句,在Scala中被认为是返回Unit类型,这是一个单实例类型,只有一个对象。
从逻辑上来说,从来没有返回的函数和操作符(例如throw操作符或使用异常退出的函数),逻辑上都有返回类型Nothing,这是一个不包含任何对象的特殊类型;也就是说,有这么一个底层类型,即每个可能类型的子类。(这反过来又使Nothing类型与每一个类型兼容,使类型推理能够正常运行。)
同样的,一个if-then-else"语句 "实际上是一个表达式,它产生一个值,也就是评估选取两个分支中的一个的结果。
这意味着,这样的代码块可以插入到任何需要表达式的地方,省去客三元运算符。
// Java:
int hexDigit = x >= 10 ? x + 'A' - 10 : x + '0';
// Scala:
val hexDigit = if (x >= 10) x + 'A' - 10 else x + '0'
出于类似的原因,在Scala中,返回语句不是必须的,事实上也不鼓励使用。就像Lisp中一样,代码块中的最后一个表达式就是该代码块的值,如果该代码块是函数的主体,那么它将被函数返回。
为了说明所有的函数都是表达式,即使是返回Unit的方法也要写成等号:
def printValue(x: String): Unit = {
println("I ate a %s".format(x))
}
或等同于(用类型推理,省略了不必要的括号):
def printValue(x: String) = println("I ate a %s" format x)
类型推理
由于类型推理的关系,通常可以省略变量的类型、函数的返回值和许多其他表达式,因为编译器可以推导出来。
例如val x = "foo"(对于一个不可变的常量或不可变的对象)或var x = 1.5(对于一个以后可以改变值的变量)。
Scala 中的类型推理基本上是局部的,这与 Haskell、ML 和其他更纯粹的功能语言中使用的 Hindley-Milner 算法不同。
这样做是为了方便面向对象编程。
其结果是,某些类型仍然需要声明(最明显的是,函数参数和递归函数的返回类型),如:
def formatApples(x: Int) = "I ate %d apples".format(x)
或(为递归函数声明的返回类型)
def factorial(x: Int): Int =
if (x == 0)
1
else
x*factorial(x - 1)
匿名函数
在 Scala 中,函数就是对象,有一种方便的语法用于指定匿名函数。一个例子是表达式 x => x < 2,它指定了有一个参数的函数,用来比较参数是否小于2。 它相当于Lisp中的(lambda (x) (< x 2))。
注意,无论是x的类型还是返回类型都不需要显式指定,一般可以通过类型推理来推断;但也可以显式指定,例如:(x: Int) => x < 2或(x: Int) => (x < 2):Boolean。
匿名函数的行为就像真正的闭合一样,它们自动捕获任何在闭合函数里面可用的变量。这些变量即使在闭合函数返回后也是可用的,而且不像Java的匿名内类那样,不需要声明为final变量。
如果这些变量是可突变的,甚至可以修改这些变量,而且在下次调用匿名函数时,修改后的值将是可用的。
还有一种更简短的匿名函数的形式是使用占位符变量。例如,如下所示:
list map { x => sqrt(x) }
可以简明扼要地写成:
list map { sqrt(_) }
或者:
list map sqrt
不变性
Scala对不可变和可变变量进行了区分。可变变量是用var关键字声明的,而不可变值是用val关键字声明的。使用val关键字声明的变量不能被重新赋值,就像在Java中使用final关键字声明的变量不能被重新赋值一样。但是,需要注意的是,val只具有浅层次的不可变性,也就是说,被val引用的对象本身并不保证是不可变的。
然而,不可变类是被鼓励的,Scala标准库提供了丰富的不可变集合类。Scala提供了大多数集合类的mutable和immutable变体,除非mutable版本被显式地导入,否则总是使用immutable版本。
immutable变体是一种持久化的数据结构,它总是返回一个旧对象的更新副本,而不是在原地破坏性地更新旧对象。一个例子是不可变的链接式列表,在这个例子中,将一个元素预加到列表中,通过返回一个新的列表节点,该节点由该元素和对列表尾部的引用组成。将一个元素添加到列表中,只能通过将旧列表中的所有元素预置到一个新的列表中,并只将新元素预置到新的列表中来完成。
同样的,在列表中间插入一个元素,会复制列表的前半部分,但保留对列表后半部分的引用。这就是所谓的结构共享。
这种机制是为了更好的支持并发。因为没有共享对象。
惰性(非严格)评估
默认情况下,评估是严格的。换句话说,Scala会在表达式可用的时候就对其进行评估,而不是根据需要进行评估。但是,可以用lazy关键字声明一个变量的惰性属性,这意味着在变量第一次被引用之前,产生该变量值的代码不会被评估。
各种类型的惰性集合也是存在的(如类型为Stream的惰性的链接列表),任何集合都可以用view方法使之成为惰性的。惰性的集合提供了一个很好的语义契合度,比如服务器生成的数据,只有在实际需要元素的时候,才会对代码进行评估,以生成后面的列表元素。
Trail递归
功能型编程语言通常提供Trail调用优化,以便在不出现堆栈溢出问题的情况下广泛使用递归。
Java字节码中的限制使JVM上的Trail调用优化变得复杂化。一般来说,Trail调用自己的函数可以进行优化,但相互递归的函数不能进行优化。
Trampolines被认为是一种解决方法。自Scala 2.8.0(2010年7月14日发布)以来,Scala库中的对象scala.utilator.control.TailCalls就提供了对Trampolines的支持。一个函数可以选择用 @tailrec 注解,在这种情况下,除非它是Trail递归,否则不会编译。
案例类和模式匹配
Scala内置了对模式匹配的支持,它可以被认为是一个更复杂的、可扩展的switch语句的版本,可以匹配任意的数据类型(而不仅仅是整数、布尔和字符串等简单类型),包括任意嵌套。
还提供了一种被称为case类的特殊类型的类,它自动支持模式匹配,可以用来模拟许多函数式编程语言中使用的代数数据类型。
从Scala的角度来看,case类只是一个普通的类,编译器会自动为其添加某些行为,而这些行为也可以手动提供,例如,定义提供深度比较和散列的方法,以及在模式匹配过程中根据构造函数参数对case类进行解构。
一个使用模式匹配的quicksort算法的例子:
def qsort(list: List[Int]): List[Int] = list match {
case Nil => Nil
case pivot :: tail =>
val (smaller, rest) = tail.partition(_ < pivot)
qsort(smaller) ::: pivot :: qsort(rest)
}
部分函数
在上面的模式匹配示例中,匹配操作符的主体是一个部分函数,它由一系列的case表达式组成,以第一个匹配表达式为准,类似于开关语句的主体。
部分函数也用于try语句中的异常处理部分:
try {
...
} catch {
case nfe:NumberFormatException => { println(nfe); List(0) }
case _ => Nil
}
部分函数可以单独使用,对它的调用相当于在它上面做了一个匹配。
前面的quicksort代码可以这样写:
val qsort: List[Int] => List[Int] = {
case Nil => Nil
case pivot :: tail =>
val (smaller, rest) = tail.partition(_ < pivot)
qsort(smaller) ::: pivot :: qsort(rest)
}
面向对象的扩展
Scala是一门纯粹的面向对象的语言,每个值都是一个对象。对象的数据类型和行为由类和特征来描述。类的抽象可以通过子类和灵活的基于mixin的组合机制来进行扩展,从而避免了多重继承的问题。
Traits是Scala对Java的接口的替代。Java 8以下版本中的接口是高度限制的,只能包含抽象函数声明。
Traits类似于mixin类,除了缺少类参数,它拥有普通抽象类的所有能力。
super运算符在traits中表现得特别好,除了继承之外,还可以使用组合方式进行连锁。下面的例子是一个简单的窗口系统:
// 抽象
def draw()
}
class SimpleWindow extends Window {
def draw() {
println("in SimpleWindow")
// 画一个简单的窗口
}
}
trait WindowDecoration extends Window {}
trait HorizontalScrollbarDecoration extends WindowDecoration {
// 为了让 "super()"起作用,需要 "抽象重写",因为父节点的
// 函数是抽象的,此处常规的 "覆盖"就够了。
abstract override def draw() {
println("in HorizontalScrollbarDecoration")
super.draw()
// 现在画一个水平滚动条
}
}
trait VerticalScrollbarDecoration extends WindowDecoration {
abstract override def draw() {
println("in VerticalScrollbarDecoration")
super.draw()
// 现在画一个垂直滚动条
}
}
trait TitleDecoration extends WindowDecoration {
abstract override def draw() {
println("in TitleDecoration")
super.draw()
// 现在画出标题栏
}
}
一个变量可以这样声明:
val mywin = new SimpleWindow with VerticalScrollbarDecoration with HorizontalScrollbarDecoration with TitleDecoration
调用mywin.draw()的结果是:
in TitleDecoration
in HorizontalScrollbarDecoration
in VerticalScrollbarDecoration
in SimpleWindow
表现型系统
Scala配备了一个富有表现力的静态类型系统,该系统主要是强制要求安全、连贯地使用抽象。该类型系统支持不健全:
l 类和抽象类型作为对象成员
l 结构类型
l 路径依赖型
l 复合物类型
l 明确类型的自我引用
l 通用类
l 多态性方法
l 上限和下限
l 差异
l 注解
l 视图
Scala能够通过使用情况来推断类型。这使得大多数静态类型声明是可选的。静态类型不需要显式声明,除非编译器报错指明有此需要。
在实践中,为了代码的清晰,一些静态类型是显式声明的。
丰富类型
Scala中的一种常见技术,被称为 "增强我的程序库"。这类似于C#中的扩展方法概念,但更加强大,因为该技术不限于添加方法,它可以用来实现新的接口。
在Scala中,这种技术涉及到从"接收"方法的类型声明一个隐式转换,将方法从类型"接收"到一个新的类型(通常是类),这个新的类型封装了原来的类型并提供了额外的方法。如果在给定类型中找不到方法,编译器会自动搜索任何适用的隐式转换类型,将其转换为提供该方法的类型。
这种技术允许使用附加库将新的方法添加到现有的类中,这样只有导入附加库的代码才能获得新的功能,其他的代码不受影响。
下面的例子展示了用方法isEven和isOdd来增强类型Int:
object MyExtensions {
implicit class IntPredicates(i: Int) {
def isEven = i % 2 == 0
def isOdd = !isEven
}
}
import MyExtensions._ // 将隐含的丰富性纳入范围
4.isEven // -> true
导入MyExtensions的成员,将隐式转换为扩展类IntPredicates的成员导入。
并发
Scala的标准库除了标准的Java并发API外,还包括了对actor模型的支持。Lightbend Inc.提供了一个平台,其中包括Akka,一个单独的开源框架,它提供了基于actor的并发。
Akka行为体可以是分布式的,也可以与软件事务性内存(transactors)相结合。基于通道的消息传递的替代通信顺序进程(CSP)实现是Communicating Scala对象,或者干脆通过JCSP。
Actor就像一个带邮箱的线程实例。它可以通过 system.actorOf 创建,覆盖接收方法来接收消息,并使用 ! (感叹号)方法来发送消息。下面的例子显示了一个EchoServer接收消息,然后打印消息:
val echoServer = actor(new Act {
become {
case msg => println("echo " + msg)
}
})
echoServer ! "hi"
Scala还内置了对数据并行编程的支持,其形式为Parallel Collections,从2.9.0版本开始就集成到标准库中。
下面的例子显示了如何使用Parallel Collections来提高性能:
val urls = List("https://scala-lang.org", "https://github.com/scala/scala")
def fromURL(url: String) = scala.io.Source.fromURL(url)
.getLines().mkString("\n")
val t = System.currentTimeMillis()
urls.par.map(fromURL(_)) // par返回一个集合的并行实现
println("time: " + (System.currentTimeMillis - t) + "ms")
除了actor支持和数据并行性之外,Scala还支持Futures和Promises、软件事务性内存和事件流等异步编程。
集群计算
用Scala编写的最著名的开源集群计算解决方案是Apache Spark。此外, 与Spark和其他流处理技术齐名的发布-订阅消息队列Apache Kafka也是用Scala编写的。
测试
在Scala中测试代码有几种方法:
ScalaTest支持多种测试风格,并可以与基于Java的测试框架集成。
ScalaCheck是一个类似于Haskell的QuickCheck.specs2的库,是一个用于编写可执行软件规范的库。
ScalaMock提供了对高阶函数和卷积函数测试的支持。
JUnit和TestNG是用Java编写的流行测试框架。
与其他JVM语言的比较
Scala经常与Groovy和Clojure进行比较。它们的本质区别在于类型系统、对面向对象和功能型编程的支持程度,以及与Java的语法相似性。
Scala是静态类型化的,而Groovy和Clojure都是动态类型化的。
这使得Scala类型系统更复杂,更难理解,但却能在编译时捕获几乎所有类型错误,并能使执行速度大大加快。
相比之下,动态类型化则需要更多的测试来确保程序的正确性,一般来说速度较慢,但程序的灵活性和简单性更强。
关于速度差异,Groovy和Clojure的当前版本允许可选的类型注解,在静态类型的情况下可以避免动态引入的开销。
当使用最新版本的JVM时,这种动态开销会进一步降低,因为JVM已经为使用动态类型化参数定义的方法增强了一条调用动态指令。
这些进步缩小了静态类型化和动态类型化之间的速度差距。
尽管如此,当执行效率非常重要时,像Scala这样的静态类型化语言仍然是首选。
关于编程范式,Scala继承了Java的面向对象模型,并以各种方式进行了扩展。
Groovy虽然也是强烈的面向对象,但更注重于减少动词性。
在Clojure中,面向对象的编程是不强调的,功能型编程是该语言的主要优势。
Scala也有很多功能型编程机制,包括像Haskell等高级功能型语言中的功能,让开发者在这两种范式之间进行选择,或者更多的时候,是两者的一些组合。
关于与Java的语法相似性,Scala继承了很多Java的语法,这跟Groovy一样。
另一方面,Clojure沿用了Lisp语法,在外观和理念上是不同的。
由于Scala的许多高级特性,学习Scala是比较困难的。
Groovy则不然,尽管它也是一门功能丰富的语言,但它主要被设计成脚本语言。
使用状况
l 2009年4月,Twitter宣布将其后端的大部分内容从Ruby转为Scala,并打算将来把其余部分也转换。
l Gilt 使用 Scala 和 Play Framework。
l Foursquare使用Scala和Lift。
l Coursera使用Scala和Play框架[105]。
l 苹果公司在某些团队中使用Scala以及Java和Play框架。
l 《卫报》的高流量网站guardian.co.uk在2011年4月宣布从Java转向Scala。
l 《纽约时报》在2014年透露,其内部内容管理系统Blackbeard使用Scala、Akka和Play构建的内部内容管理系统。
l 2013年,《赫芬顿邮报》开始采用Scala作为其内容分发系统Athena的一部分。
l 瑞士银行UBS批准了Scala用于一般的生产用途。
l LinkedIn使用Scalatra微框架为其Signal API提供动力。
l Meetup使用Unfiltered工具包进行实时API。
l Remember the Milk使用了Unfiltered toolkit、Scala和Akka的公共API和实时更新。
l Verizon寻求用Scala做一个"下一代框架"。
l Airbnb开发的开源机器学习软件"Aerosolve",用Java和Scala编写。
l Zalando将其技术栈从Java转移到Scala和Play。
l SoundCloud的后端使用Scala,采用了Finagle(微服务)、Scalding和Spark(数据处理)等技术。
l Databricks在Apache Spark大数据平台上使用Scala。
l Morgan Stanley在其金融和资产相关项目中广泛使用Scala。
l Google/Alphabet公司内部有一些团队使用Scala,主要是由于收购了Firebase和Nest等公司。
l 沃尔玛加拿大公司的后端平台使用Scala。
l Duolingo使用Scala作为生成课程的后端模块。
l HMRC在许多英国政府税务申请中使用Scala。
【Kotlin】
Kotlin是一种跨平台、静态类型化的通用编程语言,它具有类型推理功能。
Kotlin是为了与Java完全互操作而设计的,其标准库的JVM版本依赖于Java类库,但类型推理使其语法更加简洁。
Kotlin主要针对JVM,也可以编译成JavaScript或本地代码(通过LLVM)。语言开发费用由JetBrains承担,Kotlin基金会保护Kotlin商标。
2019年5月7日,谷歌宣布Kotlin编程语言现在是其Android应用开发者的首选语言,自2017年10月Android Studio 3.0发布以来,Kotlin已经被纳入了标准Java编译器的替代方案。Android Kotlin编译器默认以Java 6为目标,但允许程序员在Java 8到13之间进行选择,从而进行优化。
【Java】
Java是一种通用的编程语言,它是面向对象的通用编程语言,其设计目的是尽可能减少对实现的依赖。
它的目的是让应用程序开发人员可以一次编译到处运行。
这意味着编译后的Java代码可以在所有支持Java的平台上运行,而不需要重新编译。
Java的语法类似于C和C++,但它的底层比这两个都少。
截至2019年,根据GitHub的数据,Java是最受欢迎的编程语言之一,特别是对于客户端-服务器Web应用来说,更为流行,据说有900万开发者。
Java最初由James Gosling在Sun Microsystems公司(后被Oracle收购)开发,1995年作为Sun Microsystems公司Java平台的核心组件发布。
原始的和参考实现的Java编译器、虚拟机和类库最初是由Sun公司以专有许可证发布。
截至2007年5月,为了遵守Java社区进程的规范,Sun已经将其大部分的Java技术以GNU通用公共许可证的形式重新授权给。
同时,其他一些人也开发了这些Sun技术的替代实现,如GNU Java编译器(字节码编译器)、GNU Classpath(标准库)和IcedTea-Web(用于小程序的浏览器插件)。
最新的版本是2020年3月发布的Java 14。2018年9月25日发布的Java 11是目前的长期支持(LTS)版本;Oracle在2019年1月为遗留的Java 8 LTS发布了最后一次支持供商业使用的免费的公共更新。
Oracle(和其他公司)强烈建议用户卸载旧版本的Java,因为未解决的安全问题会带来严重的风险,Oracle建议用户立即过渡到最新版本(目前为Java 14)或LTS版本。
【语言排名】
在2018年版的 "Java状态 "调查中,收集了5160名开发者关于各种Java相关话题的数据,在JVM上替代语言的使用量方面,Scala排在第三位。与上一年度的调查相比,Scala在替代JVM语言中的使用率下降了近四分之一(从28.4%降至21.5%),被Kotlin超越,从2017年的11.4%上升至2018年的28.8%,而在2018年,Scala的使用率则被Kotlin超越。
【字节码验证器】
Java的一个基本理念是安全性,即任何用户程序都不能使主机崩溃或以其他不当方式干扰主机上的其他操作,并且可以保护受信任代码的某些方法和数据结构不被同一JVM内执行的非信任代码访问或损坏。
此外,经常导致数据损坏或不可预测行为的常见程序员错误也会避免,比如访问数组越界或使用未初始化指针等。
Java的这些特性(类模型、垃圾收集堆和验证器)结合起来提供了这种安全性。
JVM在执行字节码之前,会对字节码进行验证。这种验证主要包括三种类型的检查:
1. 分支总是在有效的位置
2. 数据总是被初始化,引用总是类型安全的。
3. 严格控制对私人数据和方法或打包的私人数据和方法的访问权限。
其中前两个检查主要是在加载类和验证步骤中进行,以确保其有资格被使用。第三种主要是动态地执行,当一个类的数据项或方法首次被另一个类访问时进行。
验证器只允许有效程序中的某些字节码序列,如跳转(分支)指令只能针对同一方法内的一条指令。
此外,验证器确保任何给定的指令都在固定的堆栈位置上操作,允许JIT编译器将堆栈访问转化为固定的寄存器访问。
正因为如此,当使用JIT编译器时,JVM作为一种堆栈架构并不意味着速度上的性能会像通常基于寄存器的架构那样丧失。
对于JIT编译器来说,在代码验证的JVM架构面前,无论是获得命名的虚寄存器还是必须分配给目标架构的寄存器的虚栈位置,都没有什么区别。
事实上,代码验证使得JVM不同于经典的堆栈架构,其中用JIT编译器进行高效仿真的JVM更加复杂,通常由一个较慢的解释器来完成。
最初的字节码校验器规范使用的自然语言在某些方面是不完整或不正确的。人们进行了许多尝试,将JVM指定为一个正式系统。通过这样做,可以更彻底地分析当前JVM实现的安全性,并防止潜在的安全漏洞。同时,如果正在运行的应用程序被证明是安全的,也可以跳过不必要的安全检查来优化JVM。
【远程代码的安全执行】
虚拟机架构允许对机器内的代码采取的操作进行非常精细的控制。它假定代码在 "语义上"是正确的,也就是说,它成功地通过了正式的字节码验证器过程,这个过程由一个工具具体化,这个工具可能是在虚拟机之外的虚拟机。
这样做的目的是为了安全地执行来自远程的非信任代码,这是Java小程序和其他安全代码下载所使用的模式。一旦经过字节码验证后,下载的代码就会在一个受限的 "沙箱"中运行,其目的是为了保护用户不受错误行为或恶意代码的影响。
作为对字节码验证过程的补充,发布者可以购买一个证书,用它来对小程序进行数字签名作为安全证书,允许他们要求用户跳出沙盒,进入本地文件系统、剪贴板、执行外部软件或使用网络。
正式的字节码验证器的证明已经由Javacard业内人士完成(Java卡字节码嵌入式验证器的正式开发)。
【字节码解释器和适时编译器】
对于每个硬件架构,都需要不同的Java字节码解释器。当计算机拥有Java字节码解释器时,它可以运行任何Java字节码程序,同样的程序可以在任何拥有这样解释器的计算机上运行。
当Java字节码被解释器执行时,其执行速度总是会比编译成原生机器语言的相同程序的执行速度慢。
这个问题可以通过JIT(just-in-time(JIT)编译器)执行Java字节码来缓解。JIT编译器可以在执行程序时将Java字节码翻译成本地机器语言。这样,程序的翻译部分就可以比它们被解释的速度快得多。这种技术被应用于程序中那些经常被执行的部分。这样,JIT编译器就可以大大加快整个程序的执行时间。
Java编程语言和Java字节码之间没有必然的联系。用Java编写的程序可以直接编译到计算机机器语言中,用Java以外的其他语言编写的程序也可以编译成Java字节码。
Java字节码的目的是独立于平台,并且是安全的。有些JVM实现不包括解释器,而是只包含一个即时编译器。
【Web浏览器中的JVM】
在Java平台诞生之初,JVM作为一种可以创建丰富互联网应用的Web技术进行销售。
截止到2018年,大多数Web浏览器和操作系统都没有附带Java插件,也不允许加载任何非Flash插件。在JDK 9中,Java浏览器插件被弃用。
NPAPI Java浏览器插件的设计是为了让JVM执行所谓的Java小程序嵌入到HTML页面中。对于安装了该插件的浏览器,小程序可以在页面上分配给它的矩形区域内进行绘制工作。
因为插件包含了JVM,所以Java小程序不限于Java编程语言;任何支持JVM的语言都可以在插件中运行。
尽管小程序不能在其矩形区域外修改内容,它提供一组需要用户授权的API允许小程序访问用户的麦克风或使用3D加速功能。作为其主要竞争技术的Adobe Flash Player在这方面的工作方式与此相同。
根据W3Techs的数据,截至2015年6月,Java applet和Silverlight在所有网站中的使用量已经下降到了0.1%,而Flash的使用量则下降到了10.8%。
【JavaScript JVMs和解释器】
截至2016年5月,JavaPoly允许用户导入未修改的Java库,并直接从JavaScript中调用它们。即使用户的计算机上没有安装Java, JavaPoly允许网站使用未修改的Java库。
【编译成JavaScript】
随着JavaScript执行的速度不断提高,再加上移动设备的使用越来越多,而这些设备的浏览器不支持插件的使用。
因此,人们正努力将源码或JVM字节码编译成JavaScript来服务这些用户。
编译JVM字节码,这在整个JVM的编程语言中是通用的。 主要的JVM字节码到JavaScript编译器有TeaVM, Dragome Web SDK中的编译器,Bck2Brwsr和j2js-compiler。
从JVM编程语言到JavaScript的主要编译器包括Google Web Toolkit中包含的Java-to-JavaScript编译器、Clojurescript(Clojure)、GoroScript(Apache Groovy)、Scala.js(Scala)等。
【Java运行时环境】
Oracle发布的Java运行时环境(JRE)是一个免费的软件发行版,包含一个独立的JVM (HotSpot)、Java标准库(Java类库)、一个配置工具,以及直到JDK 9中停止使用的浏览器插件。
JRE是个人电脑上最常见的Java环境,安装在笔记本电脑和台式机上。包括功能型手机和早期智能手机在内的移动电话很可能包含一个JVM,用于运行Java平台的微型版应用程序。
大多数现代的智能手机、平板电脑和其他手持式PC运行Java应用的时候,很可能是通过支持Android操作系统来实现的,而Android操作系统包括一个与JVM规范不兼容的开源虚拟机。Google的Android开发工具将Java程序作为输入,并输出Dalvik字节码,这是Android设备上的虚拟机的原生输入格式。
【性能】
JVM规范在实现细节上给了实现者很大的回旋余地。从Java 1.3开始,Oracle的JRE包含了一个名为HotSpot的JVM。它被设计成了一个高性能的JVM。
为了加快代码的执行速度,HotSpot依赖于just-in-time编译。为了加速对象分配和垃圾回收,HotSpot使用了代间堆。
【代间堆】
Java虚拟机堆是JVM用于动态内存分配的内存区域。
在HotSpot中,堆被分为几代:
l 年轻一代堆存储的是临时对象,这些对象被创建后会立即进行垃圾回收。
l 持续时间较长的对象会被存储到老一代堆中。这个内存被细分为两个幸存者空间,分别存储在上一次和下一次垃圾收集后幸存下来的对象。
在Java 8之前,永久代(或permgen)用于类定义和类相关的元数据。它不属于堆的一部分。从Java 8开始删除了永久代。
最初没有永久代,对象和类被存储在同一个区域。由于类的卸载比对象的收集要少得多,因此将类结构移动到一个特定的区域,可以显著提高性能。
【安全性】
Oracle的JRE被安装在大量的计算机上。 而使用过期版本的JRE的终端用户很容易受到许多已知的攻击。 这让人们普遍认为Java是不安全的,自从Java 1.7之后,Oracle的JRE for Windows包含了自动更新功能。
在Java浏览器插件停用之前,任何网页都有可能运行Java小程序,这就为恶意网站提供了一个容易访问的攻击面。2013年卡巴斯基实验室报告称,Java插件是计算机犯罪分子的首选方法。许多黑客把Java漏洞包部署到被黑网站中,鉴于此,2018年9月25日推出的Java 11中删除了Java applet。
【小结】
JVM(Java虚拟机)是运行JVM应用程序的运行时引擎,它是JRE(Java Runtime Environment)的一部分。
本文从JVM规范,Web浏览器中得JVM和运行时环境几个方面对JVM相关的知识进行了探索,重点关注了一下JVM生态环境下的7种编程语言(由于目前本文篇幅已经很长,对于Kotlin和Java的详细探究会在以后的文章中进行),希望对当前和以后的业务开发有所裨益。
欢迎讨论。
- 点赞
- 收藏
- 关注作者
评论(0)