作者:劉學超
1 Java技術和Java虛擬機
說起Java,人們首先想到的是Java編程語言,但其實Java是壹門技術,它由四個方面組成:Java編程語言、Java類文件格式、Java虛擬機和Java應用編程接口(Java API)。它們的關系如下圖所示:
圖1 Java的四個方面之間的關系
運行時環境代表Java平臺。開發人員編寫Java代碼(。java文件)然後編譯成字節碼(。類文件)。最後,字節碼被裝入內存。字節碼壹旦進入虛擬機,就會被解釋器解釋執行,或者被即時代碼生成器選擇性地轉換成機器碼執行。從上圖也可以看出,Java平臺是由Java虛擬機和Java應用程序接口搭建而成,Java語言是通往這個平臺的門戶,用Java語言編寫和編譯的程序都可以在這個平臺上運行。該平臺的結構如下圖所示:
在Java平臺的結構中,我們可以看到Java虛擬機(JVM)處於核心地位,這是程序與底層操作系統和硬件無關的關鍵。它下面是移植接口,由適配器和Java操作系統兩部分組成,其中與平臺相關的部分稱為適配器;JVM通過移植接口在特定平臺和操作系統上實現;JVM之上是Java的基本類庫、擴展類庫及其API。用Java API編寫的應用和小程序,可以在任何Java平臺上運行,而不考慮底層平臺,只是因為有壹個Java虛擬機(JVM)把程序從操作系統中分離出來,從而實現了Java的平臺無關性。
那麽到底什麽是Java虛擬機(JVM)?通常當我們談論JVM時,我們可能指的是:
JVM規範的更抽象的描述;
JVM的具體實現;
在程序運行期間生成的JVM實例。
JVM規範的抽象描述是壹些概念的集合,這些概念在《Java虛擬機規範》壹書中有詳細描述。JVM的具體實現要麽是軟件,要麽是軟硬件結合。它已經被許多制造商實現,並且存在於各種平臺上。運行Java程序的任務由JVM的運行時實例單獨承擔。我們在本文中討論的Java虛擬機(JVM)主要針對第三種情況。可以看作是壹臺虛機,在實際計算機上通過軟件模擬實現。它有自己想象的硬件,如處理器、堆棧、寄存器等,有自己相應的指令系統。
JVM在其生命周期中有壹個明確的任務,就是運行Java程序,所以當Java程序啟動時,產生壹個JVM的實例;當程序結束時,實例消失。讓我們從JVM的架構和運行過程兩個方面對其進行深入的研究。
2 Java虛擬機架構
正如剛才提到的,JVM可以由不同的供應商實現。不同的廠商必然會導致JVM的實現存在壹些差異,但是JVM仍然能夠實現跨平臺的特性,這是由於設計JVM時的架構。
我們知道,JVM實例的行為不僅僅是它自己的事,還涉及到它的子系統、存儲區域、數據類型和指令。它們描述了JVM的壹個抽象的內部架構,其目的不僅是在實現JVM時指定其內部架構,更重要的是,提供了壹種在實現時嚴格定義其外部行為的方法。每個JVM都有兩種機制。壹種是加載壹個有合適名稱的類(類或接口),稱為類加載子系統。另壹個負責執行加載的類或接口中包含的指令,稱為運行引擎。每個JVM包括五個部分:方法區、堆、Java棧、程序計數器和本地方法棧。由這些部分、類加載機制和運行引擎機制組成的架構圖如下:
圖3 JVM的架構
JVM的每個實例都有自己的方法域和堆,由JVM中運行的所有線程共享;虛擬機加載類文件時,解析二進制數據中包含的類信息,放入方法域中;程序運行時,JVM將程序初始化的所有對象放在堆上;每個線程在創建的時候都會有自己的程序計數器和Java棧,其中程序計數器中的值指向下壹條要執行的指令,線程的Java棧存儲為線程調用Java方法的狀態;本地方法調用的狀態存儲在本地方法堆棧中,這取決於具體的實現。
這些部分解釋如下。
執行引擎是JVM的核心,它的行為由Java虛擬機規範中的指令集決定。雖然對於每條指令,規範都詳細解釋了JVM在執行字節碼時遇到指令應該怎麽做,但是對於如何做卻很少提及。Java虛擬機支持大約248個字節碼。每個字節碼執行壹個基本的CPU操作,比如向寄存器中添加壹個整數,子例程轉移等。Java指令集相當於Java程序的匯編語言。
Java指令集中的指令包含壹個單字節運算符來指定要執行的操作,以及零個或多個操作數來提供操作所需的參數或數據。許多指令沒有操作數,只包含壹個單字節運算符。
虛擬機內部循環的執行過程如下:
做{
取壹個操作符字節;
根據操作符的值執行操作;
}while(程序未完成)
由於指令系統的簡單性,虛擬機執行的過程非常簡單,有利於提高執行效率。指令中操作數的數量和大小由運算符決定。如果操作數大於壹個字節,則它的存儲順序是高位字節優先。例如,16位參數在存儲時占用兩個字節,其值為:
第壹個字節*256+第二個字節的字節碼。
指令流通常是字節對齊的。指令tableswitch和lookup是例外,在這兩個指令中需要強制的4字節邊界對齊。
對於本地方法接口,JVM的實現不需要它的支持,甚至根本不需要支持。Sun公司實現了Java本地接口(JNI)的可移植性。當然,我們也可以設計其他本地接口來代替Sun公司的JNI。然而,這些設計和實現是復雜的,並且有必要確保垃圾收集器不會釋放被本地方法調用的對象。
Java的堆是壹個運行時數據區,類(對象)的實例從這裏分配空間,它的管理由垃圾收集負責:它沒有給程序員顯式釋放對象的能力。Java沒有規定具體的垃圾收集算法,可以根據系統的要求使用各種算法。
Java方法區域類似於傳統語言中的編譯代碼或Unix進程中的主體段。它保存方法代碼(編譯的java代碼)和符號表。在當前的Java實現中,方法代碼不包含在垃圾收集堆中,但計劃在未來的版本中實現。每個類文件都包含Java類或Java接口的編譯代碼。可以說,類文件就是Java語言的執行代碼文件。為了保證類文件的平臺無關性,Java虛擬機規範中也對類文件的格式進行了詳細說明。具體可參考Sun的Java虛擬機規範。
Java虛擬機的寄存器用來保存機器的運行狀態,類似於微處理器中的壹些特殊寄存器。Java虛擬機有四種寄存器:
Pc: Java程序計數器;
Optop:指向操作數堆棧頂部的指針;
Frame:指向當前執行方法的執行環境的指針;。
Vars:指向當前正在執行的方法的局部變量區域中的第壹個變量的指針。
在上面的架構圖中,我們說的是第壹種,也就是程序計數器。壹旦每個線程被創建,它就有自己的程序計數器。當線程執行Java方法時,它包含線程正在執行的指令的地址。但是如果線程執行壹個本地方法,程序計數器的值將不會被定義。
Java虛擬機的堆棧有三個區域:局部變量區、運行環境區和操作數區。
局部可變區域
每個Java方法都使用壹組固定大小的局部變量。它們根據vars寄存器的字偏移量進行尋址。局部變量都是32位的。長整數和雙精度浮點數占用兩個局部變量的空間,但根據第壹個局部變量的索引尋址。(例如,如果壹個索引為n的局部變量是壹個雙精度浮點數,它實際上占用了索引n和n+1所代表的存儲空間。)虛擬機規範不要求局部變量中的64位值是64位對齊的。虛擬機提供將局部變量中的值加載到操作數堆棧中的指令,並且還提供將操作數堆棧中的值寫入局部變量中的指令。
操作環境區域
運行時環境中包含的信息用於動態鏈接、正常方法返回和異常捕捉。
動態連接
運行時環境包括指向當前類和當前方法的解釋器符號表的指針,以支持方法代碼的動態鏈接。方法的類文件代碼在引用要調用的方法和要訪問的變量時使用符號。動態鏈接將符號方法調用翻譯成實際的方法調用,加載必要的類來解釋未定義的符號,並將變量訪問翻譯成這些變量運行時存儲結構對應的偏移地址。動態鏈接方法和變量使得方法中使用的其他類的變化不會影響這個程序的代碼。
正常方法返回
如果當前方法正常結束,當執行正確類型的返回指令時,被調用的方法將獲得返回值。執行環境用於在調用者正常返回時恢復調用者的寄存器,並將調用者的程序計數器增加壹個適當的值以跳過已執行的方法調用指令,然後在調用者的執行環境中繼續執行。
異常捕獲
Exception在Java中稱為Error或exception,是Throwable類的子類。程序中的原因有:①動態鏈接錯誤,比如找不到需要的類文件。(2)運行時錯誤,例如引用空指針。程序使用了throw語句。
當異常發生時,Java虛擬機采取以下措施:
檢查與當前方法關聯的catch子句表。每個catch子句都包含其有效的指令範圍、可以處理的異常類型以及處理異常的代碼塊的地址。
與異常匹配的catch子句應滿足以下條件:導致異常的指令在其指令範圍內,並且發生的異常類型是其可以處理的異常類型的子類型。如果找到匹配的catch子句,系統移動到指定的異常處理塊執行;如果沒有找到異常處理塊,則重復查找匹配catch子句的過程,直到檢查完當前方法的所有嵌套catch子句。
由於虛擬機從第壹個匹配的catch子句開始繼續執行,catch子句表中的順序非常重要。因為Java代碼是結構化的,所以壹個方法的所有異常處理程序總是可以按順序排列在壹個表中,對於任何可能的程序計數器值,都可以按線性順序找到合適的異常處理塊來處理程序計數器值下發生的異常。
如果沒有找到匹配的catch子句,當前方法將獲得壹個“異常未被截獲”的結果,並將其返回給當前方法的調用方,就好像異常剛剛在其調用方發生壹樣。如果在調用者中仍然找不到對應的異常處理塊,那麽這個錯誤就會傳播開來。如果錯誤傳播到頂層,系統將調用默認的異常處理塊。
操作數堆棧區
機器指令只從操作數堆棧中取出操作數,對其進行操作,然後將結果返回堆棧。之所以選擇堆棧結構,是因為它可以在只有少量寄存器或非通用寄存器的機器上(如Intel486)有效地模擬虛擬機的行為。操作數堆棧是32位。它用於向方法傳遞參數和從方法接收結果,還用於支持操作的參數和保存操作的結果。例如,iadd指令將兩個整數相加。相加的兩個整數應該是操作數堆棧頂部的兩個字。這兩個字是由前面的指令推到堆棧上的。這兩個整數將彈出堆棧,相加,並將結果推回操作數堆棧。
每種原始數據類型都有特殊的指令來對它們執行必要的操作。每個操作數在堆棧中都需要壹個存儲位置,除了long和double類型,它們需要兩個位置。操作數只能由適合其類型的運算符進行運算。例如,壓入兩個int類型的數字並將它們視為long類型的數字是非法的。在Sun的虛擬機實現中,這種限制是由字節碼驗證器強制執行的。然而,有壹些操作(操作符dupe和swap)用於對運行時數據區進行操作,而不管其類型如何。
本地方法棧,線程調用本地方法時,不再受虛擬機結構和安全限制的約束。它不僅可以訪問虛擬機的運行時數據區,還可以使用本地處理器和任何類型的堆棧。比如本地棧是C語言中的壹個棧,所以當壹個C程序調用壹個C函數時,函數的參數按照壹定的順序被推入棧中,結果返回給調用函數。Java虛擬機實現時,本地方法接口使用C語言的模型棧,因此其本地方法棧的調度和使用與C語言完全相同。
3 Java虛擬機運行過程
上面詳細描述了虛擬機的各個部分,下面通過壹個具體的例子來分析其運行過程。
虛擬機的啟動是通過調用指定類的方法main,將壹個字符串數組參數傳遞給main,這樣指定的類就被加載了,該類使用的其他類型也被鏈接和初始化。例如,對於程序:
HelloApp類
{
公共靜態void main(String[] args)
{
System.out.println("Hello World!");
for(int I = 0;我& ltargs.lengthi++)
{
system . out . println(args[I]);
}
}
}
編譯完成後,鍵入:java HelloApp在命令行模式下運行虛擬機。
java虛擬機將通過調用HelloApp的方法main來啟動,壹個包含三個字符串“run”、“virtual”、“machine”的數組將被傳遞給main。現在讓我們概述壹下虛擬機在執行HelloApp時可能采取的步驟。
壹開始我試著執行了類HelloApp的main方法,發現類沒有加載,也就是說虛擬機目前不包含這個類的二進制表示,於是虛擬機使用ClassLoader試圖找到這樣的二進制表示。如果此過程失敗,將引發異常。加載類之後,調用main方法之前,HelloApp類必須與其他類型鏈接並初始化。環節包括檢查、準備、分析三個階段。檢查加載的主類的符號和語義。準備時,創建類或接口的靜態字段,並將這些字段初始化為標準默認值。解析負責檢查主類對其他類或接口的符號引用。這壹步是可選的。類初始化是執行類中聲明的靜態初始化函數和靜態域初始化構造方法。在初始化壹個類之前,必須先初始化它的父類。整個過程如下:
圖4:虛擬機的運行過程
4結論
本文通過對JVM體系結構的深入研究和對Java程序執行時虛擬機運行過程的詳細分析,旨在清晰地分析Java虛擬機的機制。
發布@ 2006-07-21 18:14 Sunfruit閱讀(22) |評論(0) |編輯收藏
2006年7月20日#
[Original]Oracle Spatial驅動的查詢記錄示例
-太陽果
Oracle的空間數據庫的操作驅動已經更新,新驅動適用於Oracle8.0以上版本。新的驅動在數據庫的操作上和原來的驅動有很大的不同,但是很好用。
跳過了建立空間數據庫和空間索引的步驟。網上有很多例子,實現方式沒有變。下面列出了用於查詢空間數據庫記錄的代碼:
導入Java . SQL . driver manager;
導入Java . SQL . resultset;
導入Java . SQL . SQL exception;
導入Oracle . spatial . geometry . j geometry;
導入Java . SQL . prepared statement;
導入Oracle . SQL . struct;
導入Java . SQL . connection;
/**
* & ltp & gt標題:& lt/p & gt;
*
* & ltp & gt描述:& lt/p & gt;
*
* & ltp & gt版權所有:版權所有(c)2006 & lt;/p & gt;
*
* & ltp & gt公司:& lt/p & gt;
*
* @作者sunfruit
* @版本1.0
*/
公共類SdoSelect {
public SdoSelect() {
}
公共靜態void main(String[] args) {
string driver = " Oracle . JDBC . driver . Oracle driver ";
string URL = " JDBC:Oracle:thin:@ 172.16 . 75 . 200:1521:star map ";
String uid = " hmbst
String psw = " hmbst
連接連接=空;
PreparedStatement ps = null
嘗試{
Class.forName(驅動程序);
conn = driver manager . getconnection(URL,uid,PSW);
j geometry j geometry = new j geometry(41884696,14377039,42884696,14477039,0);
STRUCT obj =jGeometry.store(康涅狄格州jGeometry);
string SQL = " SELECT * FROM pois do p WHERE SDO _ filter(p . g shape,,' query type = window ')= ' TRUE ' ";
ps = conn.prepareStatement(sql,ResultSet。TYPE_FORWARD_ONLY,結果集。CONCUR _ READ _ ONLY);
PS . clear parameters();
ps.setObject(1,obj);
//插入點圖記錄
ResultSet RS = PS . execute query();
while(rs.next())
{
STRUCT ST =(Oracle . SQL . STRUCT)RS . getobject(" g shape ");
j geometry j _ geom = j geometry . load(ST);
double[]dou = j _ geom . get point();
String buff =
for(int I = 0;我& lt竇. length;i++)
{
buff = buff+string . value of((int)dou[I])+" ";
}
system . out . println(buff);
}
}
catch(例外){
ex . printstacktrace();
}
最後
{
如果(conn!=空)
{
嘗試{
conn . close();
}
catch (SQLException ex) {
ex . printstacktrace();
}
}
如果(ps!=空)
{
嘗試{
PS . close();
}
catch (SQLException ex) {
ex . printstacktrace();
}
}
}
}
}
表POISDO的結構如下
創建表興趣點(
id整數,
gname VARCHAR2(256),
gshape MDSYS。SDO _ GEOMETRY);