天天看點

[JVM]虛拟機動态類型語言支援

這篇部落格是根據《深入了解java虛拟機》的講解和本人對動态類型語言的一些認識,來深度剖析一下java虛拟機對動态類型語言的支援!

JDK存在js執行引擎

(一)什麼是動态類型語言

在講解java虛拟機對動态類型語言支援之前,我們首先要弄明白動态類型語言是什麼?它與java語言、java虛拟機有什麼關系?

那麼接下來先回答第一個問題,什麼是動态類型語言:動态類型語言的關鍵特征是它的類型檢查的主體過程是在運作期而不是編譯器,

滿足這個特征的語言有很多,常用的包括: APL、JavaScript、python、Ruby、Groovy等。相對的在編譯期就進行類型檢查過程的

語言(C++和Java等)就是最常用的靜态類型語言。大家可能對上面定義過于概念化,那我們不妨通過幾個例子以最淺顯的方式說明

什麼是“在編譯器/運作期進行”和什麼是“類型檢查”。首先看下面這段簡單的Java代碼:它是否能正常編譯和運作呢?

public static void main(String[] args) {
    
    int i = 10;
    int j = 0;
    int v = i/j;
  }      

這段代碼相信大家再熟悉不過了,它可以正常編譯,但是會運作時會報ArithmeticException異常。在Java虛拟機中規範中明确規定了

ArithmeticException是一個運作異常,通俗一點說,運作時異常隻要代碼不運作到這一行就不會有問題。與運作異常相對應的是編譯時

異常,接下來看一下編譯時異常的例子:

public static void main(String[] args) {
    
     FileInputStream fis = null;  
           
           fis = new FileInputStream("test.txt");  
          
  }      

上面這個例子中 fis = new FileInputStream("test.txt")會抛出IOException異常,這是一個編譯時異常,如果不做try-catch處理,

編譯都通不過。通過上面兩個例子就是想說明有些檢查是在運作期進行的,有些檢查是在編譯器進行的。

接下來再舉一個例子來解釋“類型檢查”,例如下面這一句非常簡單的代碼:

obj = Demo();
      obj.function();      

上面代碼中假設Demo是一個類,且裡面有function方法,這兩行對于Java說,相信大家都知道是無法編譯的更别提執行了。

但是類似的代碼在JavaScript或者Python中情況則不一樣,是可以編譯并且可以執行的。這種差别産生的原因在于動态類型語言中,

變量obj本身是沒有類型的,變量obj的值才具有類型,這是因為動态類型語言在運作期确定類型的,而Java或者靜态類型語言是

編譯器确定類型的。孰好孰壞不知道,應該是各有所長吧。

(二)JDK1.7與動态類型

在介紹完動态類型,回到Java語言、虛拟機和動态類型語言之間的關系。其實Java虛拟機層面對動态類型語言的支援一直都有所欠缺,

主要表現在方法調用方面:JDK1.7以前的位元組碼指令集中,4條方法調用指令(invokevirtual , invokespecial , invokestatic ,

 invokeinterface)的第一條參數都是被調用的方法的符号引用,前面已經提到過,方法的符号引用在編譯時産生,而動态類型語言

是在動态運作期才能确定接受者的類型。是以這也就是JDK1.7中invokedynamic指令以及java.lang.invoke包出現要解決的問題。

(1)java.lang.invoke包

JDK1.7中新加入的java.lang.invoke包的主要目的就是在之前單純依靠符号引用來确定的目标方法這種方式以外,提供一種新的

動态确定目标方法的機制,稱之為MethodHandle。其實MethodHandle就是類似C/C++中的函數指針,或者C#中的委托。

舉個例子,如果我們要實作一個帶有函數參數的排序函數,用函數指針的方如下:

void sort(int list[], const int size , int (*compare)(int, int))      

但Java語言就做不到這點,即沒有辦法把一個函數作為參數進行傳遞。普遍的做法是設計一個帶有compare()方法的Comparator接口,

以實作了這個接口的對象作為參數。不過,在擁有Method Handle之後,Java語言也可以擁有類似于函數指針或者委托的方法别名的

工具了。如下代碼示範了MethodHandle的基本用法,無論obj是何種類型,都可以正确的調用到println()方法。

package demo;
 
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import static java.lang.invoke.MethodHandles.lookup;
 
public class MethodHandleTest {
 
  static class ClassA{
    public void println(String s){
      System.out.println(s);
    }
  }
  public static void main(String[] args) throws Throwable {
    Object obj = System.currentTimeMillis() % 2 == 0 ? System.out : new ClassA();
    
    //無論obj最終是哪個實作類,下面這句都能正确調用到println方法
    getPrintlnMH(obj).invokeExact("test");
  }
  private static MethodHandle getPrintlnMH(Object reveiver) throws Throwable{
    //MethodType: 代表"方法類型",包含了方法的傳回值(methodType()的第一個參數)和具體參數(methodType()第二個及以後的參數)
    MethodType mt = MethodType.methodType(void.class,String.class);
    //lookup()方法來自于MethodHandles.lookup,這句的作用是在指定類中查找符合給定的方法名稱、方法類型、并且符合調用權限的方法語柄
    /*因為這裡調用的是一個虛方法,按照Java語言的規則,方法第一個參數是隐式的,代表該方法的接受者,也即是this指向的對象,這個參數以前是放在
     參數清單中進行傳遞的,而現在提供了bindTo方法來完成這件事情*/
    return lookup().findVirtual(reveiver.getClass(),"println",mt).bindTo(reveiver);
    
  }
}      

實際上,方法getPrintlnMH()中模拟了invokevirt指令的執行過程,隻不過它的分派邏輯并非固化在Class位元組碼上,而是通過一個具體