ThreadLocal
類是悄悄地出現(xiàn)在 Java 平臺版本 1.2 中的。雖然支持線程局部變量早就是許多線程工具(例如 Posixpthreads
工具)的一部分,但 Java Threads API 的最初設(shè)計卻沒有這項有用的功能。而且,最初的實現(xiàn)也相當?shù)托АS捎谶@些原因,ThreadLocal
極少受到關(guān)注,但對簡化線程安全并發(fā)程序的開發(fā)來說,它卻是很方便的。在 輕松使用線程的第 3 部分,Java 軟件顧問 Brian Goetz 研究了ThreadLocal
并提供了一些使用技巧。參加 Brian 的 多線程 Java 編程討論論壇以獲得您工程中的線程和并發(fā)問題的幫助。
編寫線程安全類是困難的。它不但要求仔細分析在什么條件可以對變量進行讀寫,而且要求仔細分析其它類能如何使用某個類。 有時,要在不影響類的功能、易用性或性能的情況下使類成為線程安全的是很困難的。有些類保留從一個方法調(diào)用到下一個方法調(diào)用的狀態(tài)信息,要在實踐中使這樣的類成為線程安全的是困難的。
管理非線程安全類的使用比試圖使類成為線程安全的要更容易些。非線程安全類通常可以安全地在多線程程序中使用,只要您能確保一個線程所用的類的實例不被其它線程使用。例如,JDBC Connection
類是非線程安全的 — 兩個線程不能在小粒度級上安全地共享一個 Connection
— 但如果每個線程都有它自己的 Connection
,那么多個線程就可以同時安全地進行數(shù)據(jù)庫操作。
不使用 ThreadLocal
為每個線程維護一個單獨的 JDBC 連接(或任何其它對象)當然是可能的;Thread API 給了我們把對象和線程聯(lián)系起來所需的所有工具。而 ThreadLocal 則使我們能更容易地把線程和它的每線程(per-thread)數(shù)據(jù)成功地聯(lián)系起來。
什么是線程局部變量(thread-local variable)?
線程局部變量高效地為每個使用它的線程提供單獨的線程局部變量值的副本。每個線程只能看到與自己相聯(lián)系的值,而不知道別的線程可能正在使用或修改它們自己的副本。一些編譯器(例如 Microsoft Visual C++ 編譯器或 IBM XL FORTRAN 編譯器)用存儲類別修飾符(像 static
或 volatile
)把對線程局部變量的支持集成到了其語言中。Java 編譯器對線程局部變量不提供特別的語言支持;相反地,它用 ThreadLocal
類實現(xiàn)這些支持, 核心 Thread
類中有這個類的特別支持。
因為線程局部變量是通過一個類來實現(xiàn)的,而不是作為 Java 語言本身的一部分,所以 Java 語言線程局部變量的使用語法比內(nèi)建線程局部變量語言的使用語法要笨拙一些。要創(chuàng)建一個線程局部變量,請實例化類 ThreadLocal
的一個對象。 ThreadLocal
類的行為與 java.lang.ref
中的各種 Reference
類的行為很相似; ThreadLocal
類充當存儲或檢索一個值時的間接句柄。清單 1 顯示了 ThreadLocal
接口。
|
get()
訪問器檢索變量的當前線程的值; set()
訪問器修改當前線程的值。 initialValue()
方法是可選的,如果線程未使用過某個變量,那么您可以用這個方法來設(shè)置這個變量的初始值;它允許延遲初始化。用一個示例實現(xiàn)來說明 ThreadLocal 的工作方式是最好的方法。清單 2 顯示了 ThreadLocal
的一個實現(xiàn)方式。它不是一個特別好的實現(xiàn)(雖然它與最初實現(xiàn)非常相似),所以很可能性能不佳,但它清楚地說明了 ThreadLocal 的工作方式。
|
這個實現(xiàn)的性能不會很好,因為每個 get()
和 set()
操作都需要 values
映射表上的同步,而且如果多個線程同時訪問同一個 ThreadLocal
,那么將發(fā)生爭用。此外,這個實現(xiàn)也是不切實際的,因為用 Thread
對象做 values
映射表中的關(guān)鍵字將導(dǎo)致無法在線程退出后對 Thread
進行垃圾回收,而且也無法對死線程的 ThreadLocal
的特定于線程的值進行垃圾回收。
![]() ![]() |
![]()
|
用 ThreadLocal 實現(xiàn)每線程 Singleton
線程局部變量常被用來描繪有狀態(tài)“單子”(Singleton) 或線程安全的共享對象,或者是通過把不安全的整個變量封裝進 ThreadLocal
,或者是通過把對象的特定于線程的狀態(tài)封裝進 ThreadLocal
。例如,在與數(shù)據(jù)庫有緊密聯(lián)系的應(yīng)用程序中,程序的很多方法可能都需要訪問數(shù)據(jù)庫。在系統(tǒng)的每個方法中都包含一個 Connection
作為參數(shù)是不方便的 — 用“單子”來訪問連接可能是一個雖然更粗糙,但卻方便得多的技術(shù)。然而,多個線程不能安全地共享一個 JDBC Connection
。如清單 3 所示,通過使用“單子”中的 ThreadLocal
,我們就能讓我們的程序中的任何類容易地獲取每線程 Connection
的一個引用。這樣,我們可以認為 ThreadLocal
允許我們創(chuàng)建 每線程單子。
|
任何創(chuàng)建的花費比使用的花費相對昂貴些的有狀態(tài)或非線程安全的對象,例如 JDBC Connection
或正則表達式匹配器,都是可以使用每線程單子(singleton)技術(shù)的好地方。當然,在類似這樣的地方,您可以使用其它技術(shù),例如用池,來安全地管理共享訪問。然而,從可伸縮性角度看,即使是用池也存在一些潛在缺陷。因為池實現(xiàn)必須使用同步,以維護池數(shù)據(jù)結(jié)構(gòu)的完整性,如果所有線程使用同一個池,那么在有很多線程頻繁地對池進行訪問的系統(tǒng)中,程序性能將因爭用而降低。
![]() ![]() |
![]()
|
其它適合使用 ThreadLocal
但用池卻不能成為很好的替代技術(shù)的應(yīng)用程序包括存儲或累積每線程上下文信息以備稍后檢索之用這樣的應(yīng)用程序。例如,假設(shè)您想創(chuàng)建一個用于管理多線程應(yīng)用程序調(diào)試信息的工具。您可以用如清單 4 所示的 DebugLogger
類作為線程局部容器來累積調(diào)試信息。在一個工作單元的開頭,您清空容器,而當一個錯誤出現(xiàn)時,您查詢該容器以檢索這個工作單元迄今為止生成的所有調(diào)試信息。
|
在您的代碼中,您可以調(diào)用 DebugLogger.put()
來保存您的程序正在做什么的信息,而且,稍后如果有必要(例如發(fā)生了一個錯誤),您能夠容易地檢索與某個特定線程相關(guān)的調(diào)試信息。 與簡單地把所有信息轉(zhuǎn)儲到一個日志文件,然后努力找出哪個日志記錄來自哪個線程(還要擔心線程爭用日志紀錄對象)相比,這種技術(shù)簡便得多,也有效得多。
ThreadLocal
在基于 servlet 的應(yīng)用程序或工作單元是一個整體請求的任何多線程應(yīng)用程序服務(wù)器中也是很有用的,因為在處理請求的整個過程中將要用到單個線程。您可以通過前面講述的每線程單子技術(shù)用 ThreadLocal
變量來存儲各種每請求(per-request)上下文信息。
![]() ![]() |
![]()
|
ThreadLocal 的線程安全性稍差的堂兄弟,InheritableThreadLocal
ThreadLocal 類有一個親戚,InheritableThreadLocal,它以相似的方式工作,但適用于種類完全不同的應(yīng)用程序。創(chuàng)建一個線程時如果保存了所有 InheritableThreadLocal
對象的值,那么這些值也將自動傳遞給子線程。如果一個子線程調(diào)用 InheritableThreadLocal
的 get()
,那么它將與它的父線程看到同一個對象。為保護線程安全性,您應(yīng)該只對不可變對象(一旦創(chuàng)建,其狀態(tài)就永遠不會被改變的對象)使用 InheritableThreadLocal
,因為對象被多個線程共享。 InheritableThreadLocal
很合適用于把數(shù)據(jù)從父線程傳到子線程,例如用戶標識(user id)或事務(wù)標識(transaction id),但不能是有狀態(tài)對象,例如 JDBC Connection
。
![]() ![]() |
![]()
|
雖然線程局部變量早已赫赫有名并被包括 Posix pthreads
規(guī)范在內(nèi)的很多線程框架支持,但最初的 Java 線程設(shè)計中卻省略了它,只是在 Java 平臺的版本 1.2 中才添加上去。在很多方面, ThreadLocal
仍在發(fā)展之中;在版本 1.3 中它被重寫,版本 1.4 中又重寫了一次,兩次都專門是為了性能問題。
在 JDK 1.2 中, ThreadLocal
的實現(xiàn)方式與清單 2 中的方式非常相似,除了用同步 WeakHashMap
代替 HashMap
來存儲 values 之外。(以一些額外的性能開銷為代價,使用 WeakHashMap 解決了無法對 Thread 對象進行垃圾回收的問題。)不用說, ThreadLocal
的性能是相當差的。
Java 平臺版本 1.3 提供的 ThreadLocal
版本已經(jīng)盡量更好了;它不使用任何同步,從而不存在可伸縮性問題,而且它也不使用弱引用。相反地,人們通過給 Thread
添加一個實例變量(該變量用于保存當前線程的從線程局部變量到它的值的映射的 HashMap
)來修改 Thread
類以支持 ThreadLocal
。因為檢索或設(shè)置一個線程局部變量的過程不涉及對可能被另一個線程讀寫的數(shù)據(jù)的讀寫操作,所以您可以不用任何同步就實現(xiàn) ThreadLocal.get()
和 set()
。而且,因為每線程值的引用被存儲在自已的 Thread
對象中,所以當對 Thread
進行垃圾回收時,也能對該 Thread
的每線程值進行垃圾回收。
不幸的是,即使有了這些改進,Java 1.3 中的 ThreadLocal
的性能仍然出奇地慢。據(jù)我的粗略測量,在雙處理器 Linux 系統(tǒng)上的 Sun 1.3 JDK 中進行 ThreadLocal.get()
操作,所耗費的時間大約是無爭用同步的兩倍。性能這么差的原因是 Thread.currentThread()
方法的花費非常大,占了 ThreadLocal.get()
運行時間的三分之二還多。雖然有這些缺點,JDK 1.3 ThreadLocal.get()
仍然比爭用同步快得多,所以如果在任何存在嚴重爭用的地方(可能是有非常多的線程,或者同步塊被頻繁地執(zhí)行,或者同步塊很大), ThreadLocal
可能仍然要高效得多。
在 Java 平臺的最新版本,即版本 1.4b2 中, ThreadLocal
和 Thread.currentThread()
的性能都有了很大提高。有了這些提高, ThreadLocal
應(yīng)該比其它技術(shù),如用池,更快。由于它比其它技術(shù)更簡單,也更不易出錯,人們最終將發(fā)現(xiàn)它是避免線程間出現(xiàn)不希望的交互的有效途徑。
![]() ![]() |
![]()
|
ThreadLocal
能帶來很多好處。它常常是把有狀態(tài)類描繪成線程安全的,或者封裝非線程安全類以使它們能夠在多線程環(huán)境中安全地使用的最容易的方式。使用 ThreadLocal
使我們可以繞過為實現(xiàn)線程安全而對何時需要同步進行判斷的復(fù)雜過程,而且因為它不需要任何同步,所以也改善了可伸縮性。除簡單之外,用 ThreadLocal
存儲每線程單子或每線程上下文信息在歸檔方面還有一個頗有價值好處 — 通過使用 ThreadLocal
,存儲在 ThreadLocal
中的對象都是 不被線程共享的是清晰的,從而簡化了判斷一個類是否線程安全的工作。
我希望您從這個系列中得到了樂趣,也學到了知識,我也鼓勵您到我的 討論論壇中來深入研究多線程問題。
Servlet是在多線程環(huán)境下的。即可能有多個請求發(fā)給一個servelt實例,每個請求是一個線程。
struts下的action也類似,同樣在多線程環(huán)境下。可以參考struts user guide: http://struts.apache.org/struts-action/userGuide/building_controller.html 中的Action Class Design Guidelines一節(jié): Write code for a multi-threaded environment - Our controller servlet creates only one instance of your Action class, and uses this one instance to service all requests. Thus, you need to write thread-safe Action classes. Follow the same guidelines you would use to write thread-safe Servlets.
譯:為多線程環(huán)境編寫代碼。我們的controller servlet指揮創(chuàng)建你的Action 類的一個實例,用此實例來服務(wù)所有的請求。因此,你必須編寫線程安全的Action類。遵循與寫線程安全的servlet同樣的方針。
1.什么是線程安全的代碼
在多線程環(huán)境下能正確執(zhí)行的代碼就是線程安全的。
安全的意思是能正確執(zhí)行,否則后果是程序執(zhí)行錯誤,可能出現(xiàn)各種異常情況。
2.如何編寫線程安全的代碼
很多書籍里都詳細講解了如何這方面的問題,他們主要講解的是如何同步線程對共享資源的使用的問題。主要是對synchronized關(guān)鍵字的各種用法,以及鎖的概念。
Java1.5中也提供了如讀寫鎖這類的工具類。這些都需要較高的技巧,而且相對難于調(diào)試。
但是,線程同步是不得以的方法,是比較復(fù)雜的,而且會帶來性能的損失。等效的代碼中,不需要同步在編寫容易度和性能上會更好些。
我這里強調(diào)的是什么代碼是始終為線程安全的、是不需要同步的。如下:
1)常量始終是線程安全的,因為只存在讀操作。
2)對構(gòu)造器的訪問(new 操作)是線程安全的,因為每次都新建一個實例,不會訪問共享的資源。
3)最重要的是:局部變量是線程安全的。因為每執(zhí)行一個方法,都會在獨立的空間創(chuàng)建局部變量,它不是共享的資源。局部變量包括方法的參數(shù)變量。
struts user guide里有:
Only Use Local Variables - The most important principle that aids in thread-safe coding is to use only local variables, not instance variables , in your Action class.
譯:只使用用局部變量。--編寫線程安全的代碼最重要的原則就是,在Action類中只使用局部變量,不使用實例變量。
總結(jié):
在Java的Web服務(wù)器環(huán)境下開發(fā),要注意線程安全的問題。最簡單的實現(xiàn)方式就是在Servlet和Struts Action里不要使用類變量、實例變量,但可以使用類常量和實例常量。
如果有這些變量,可以將它們轉(zhuǎn)換為方法的參數(shù)傳入,以消除它們。
注意一個容易混淆的地方:被Servlet或Action調(diào)用的類中(如值對象、領(lǐng)域模型類)中是否可以安全的使用實例變量?如果你在每次方法調(diào)用時
新建一個對象,再調(diào)用它們的方法,則不存在同步問題---因為它們不是多個線程共享的資源,只有共享的資源才需要同步---而Servlet和Action的實例對于多個線程是共享的。
換句話說,Servlet和Action的實例會被多個線程同時調(diào)用,而過了這一層,如果在你自己的代碼中沒有另外啟動線程,且每次調(diào)用后續(xù)業(yè)務(wù)對象時都是先新建一個實例再調(diào)用,則都是線程安全的。
Java虛擬機的深入研究
作者:劉學超
1??Java技術(shù)與Java虛擬機
說起Java,人們首先想到的是Java編程語言,然而事實上,Java是一種技術(shù),它由四方面組成: Java編程語言、Java類文件格式、Java虛擬機和Java應(yīng)用程序接口(Java API)。它們的關(guān)系如下圖所示:
圖1??Java四個方面的關(guān)系
運行期環(huán)境代表著Java平臺,開發(fā)人員編寫Java代碼(.java文件),然后將之編譯成字節(jié)碼(.class文件)。最后字節(jié)碼被裝入內(nèi)存,一旦字節(jié)碼進入虛擬機,它就會被解釋器解釋執(zhí)行,或者是被即時代碼發(fā)生器有選擇的轉(zhuǎn)換成機器碼執(zhí)行。從上圖也可以看出Java平臺由Java虛擬機和Java應(yīng)用程序接口搭建,Java語言則是進入這個平臺的通道,用Java語言編寫并編譯的程序可以運行在這個平臺上。這個平臺的結(jié)構(gòu)如下圖所示:
在Java平臺的結(jié)構(gòu)中, 可以看出,Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統(tǒng)和硬件無關(guān)的關(guān)鍵。它的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統(tǒng), 其中依賴于平臺的部分稱為適配器;JVM 通過移植接口在具體的平臺和操作系統(tǒng)上實現(xiàn);在JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應(yīng)用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺, 就是因為有Java虛擬機(JVM)實現(xiàn)了程序與操作系統(tǒng)的分離,從而實現(xiàn)了Java 的平臺無關(guān)性。
那么到底什么是Java虛擬機(JVM)呢?通常我們談?wù)揓VM時,我們的意思可能是:
對JVM規(guī)范的的抽象說明是一些概念的集合,它們已經(jīng)在書《The Java Virtual Machine Specification》(《Java虛擬機規(guī)范》)中被詳細地描述了;對JVM的具體實現(xiàn)要么是軟件,要么是軟件和硬件的組合,它已經(jīng)被許多生產(chǎn)廠商所實現(xiàn),并存在于多種平臺之上;運行Java程序的任務(wù)由JVM的運行期實例單個承擔。在本文中我們所討論的Java虛擬機(JVM)主要針對第三種情況而言。它可以被看成一個想象中的機器,在實際的計算機上通過軟件模擬來實現(xiàn),有自己想象中的硬件,如處理器、堆棧、寄存器等,還有自己相應(yīng)的指令系統(tǒng)。
JVM在它的生存周期中有一個明確的任務(wù),那就是運行Java程序,因此當Java程序啟動的時候,就產(chǎn)生JVM的一個實例;當程序運行結(jié)束的時候,該實例也跟著消失了。下面我們從JVM的體系結(jié)構(gòu)和它的運行過程這兩個方面來對它進行比較深入的研究。
2??Java虛擬機的體系結(jié)構(gòu)
剛才已經(jīng)提到,JVM可以由不同的廠商來實現(xiàn)。由于廠商的不同必然導(dǎo)致JVM在實現(xiàn)上的一些不同,然而JVM還是可以實現(xiàn)跨平臺的特性,這就要歸功于設(shè)計JVM時的體系結(jié)構(gòu)了。
我們知道,一個JVM實例的行為不光是它自己的事,還涉及到它的子系統(tǒng)、存儲區(qū)域、數(shù)據(jù)類型和指令這些部分,它們描述了JVM的一個抽象的內(nèi)部體系結(jié)構(gòu),其目的不光規(guī)定實現(xiàn)JVM時它內(nèi)部的體系結(jié)構(gòu),更重要的是提供了一種方式,用于嚴格定義實現(xiàn)時的外部行為。每個JVM都有兩種機制,一個是裝載具有合適名稱的類(類或是接口),叫做類裝載子系統(tǒng);另外的一個負責執(zhí)行包含在已裝載的類或接口中的指令,叫做運行引擎。每個JVM又包括方法區(qū)、堆、Java棧、程序計數(shù)器和本地方法棧這五個部分,這幾個部分和類裝載機制與運行引擎機制一起組成的體系結(jié)構(gòu)圖為:
圖3??JVM的體系結(jié)構(gòu)
JVM的每個實例都有一個它自己的方法域和一個堆,運行于JVM內(nèi)的所有的線程都共享這些區(qū)域;當虛擬機裝載類文件的時候,它解析其中的二進制數(shù)據(jù)所包含的類信息,并把它們放到方法域中;當程序運行的時候,JVM把程序初始化的所有對象置于堆上;而每個線程創(chuàng)建的時候,都會擁有自己的程序計數(shù)器和Java棧,其中程序計數(shù)器中的值指向下一條即將被執(zhí)行的指令,線程的Java棧則存儲為該線程調(diào)用Java方法的狀態(tài);本地方法調(diào)用的狀態(tài)被存儲在本地方法棧,該方法棧依賴于具體的實現(xiàn)。
下面分別對這幾個部分進行說明。
執(zhí)行引擎處于JVM的核心位置,在Java虛擬機規(guī)范中,它的行為是由指令集所決定的。盡管對于每條指令,規(guī)范很詳細地說明了當JVM執(zhí)行字節(jié)碼遇到指令時,它的實現(xiàn)應(yīng)該做什么,但對于怎么做卻言之甚少。Java虛擬機支持大約248個字節(jié)碼。每個字節(jié)碼執(zhí)行一種基本的CPU運算,例如,把一個整數(shù)加到寄存器,子程序轉(zhuǎn)移等。Java指令集相當于Java程序的匯編語言。
Java指令集中的指令包含一個單字節(jié)的操作符,用于指定要執(zhí)行的操作,還有0個或多個操作數(shù),提供操作所需的參數(shù)或數(shù)據(jù)。許多指令沒有操作數(shù),僅由一個單字節(jié)的操作符構(gòu)成。
虛擬機的內(nèi)層循環(huán)的執(zhí)行過程如下: do{ 取一個操作符字節(jié); 根據(jù)操作符的值執(zhí)行一個動作; }while(程序未結(jié)束)
由于指令系統(tǒng)的簡單性,使得虛擬機執(zhí)行的過程十分簡單,從而有利于提高執(zhí)行的效率。指令中操作數(shù)的數(shù)量和大小是由操作符決定的。如果操作數(shù)比一個字節(jié)大,那么它存儲的順序是高位字節(jié)優(yōu)先。例如,一個16位的參數(shù)存放時占用兩個字節(jié),其值為:
第一個字節(jié)*256+第二個字節(jié)字節(jié)碼。
指令流一般只是字節(jié)對齊的。指令tableswitch和lookup是例外,在這兩條指令內(nèi)部要求強制的4字節(jié)邊界對齊。
對于本地方法接口,實現(xiàn)JVM并不要求一定要有它的支持,甚至可以完全沒有。Sun公司實現(xiàn)Java本地接口(JNI)是出于可移植性的考慮,當然我們也可以設(shè)計出其它的本地接口來代替Sun公司的JNI。但是這些設(shè)計與實現(xiàn)是比較復(fù)雜的事情,需要確保垃圾回收器不會將那些正在被本地方法調(diào)用的對象釋放掉。
Java的堆是一個運行時數(shù)據(jù)區(qū),類的實例(對象)從中分配空間,它的管理是由垃圾回收來負責的:不給程序員顯式釋放對象的能力。Java不規(guī)定具體使用的垃圾回收算法,可以根據(jù)系統(tǒng)的需求使用各種各樣的算法。
Java方法區(qū)與傳統(tǒng)語言中的編譯后代碼或是Unix進程中的正文段類似。它保存方法代碼(編譯后的java代碼)和符號表。在當前的Java實現(xiàn)中,方法代碼不包括在垃圾回收堆中,但計劃在將來的版本中實現(xiàn)。每個類文件包含了一個Java類或一個Java界面的編譯后的代碼。可以說類文件是Java語言的執(zhí)行代碼文件。為了保證類文件的平臺無關(guān)性,Java虛擬機規(guī)范中對類文件的格式也作了詳細的說明。其具體細節(jié)請參考Sun公司的Java虛擬機規(guī)范。
Java虛擬機的寄存器用于保存機器的運行狀態(tài),與微處理器中的某些專用寄存器類似。Java虛擬機的寄存器有四種:
在上述體系結(jié)構(gòu)圖中,我們所說的是第一種,即程序計數(shù)器,每個線程一旦被創(chuàng)建就擁有了自己的程序計數(shù)器。當線程執(zhí)行Java方法的時候,它包含該線程正在被執(zhí)行的指令的地址。但是若線程執(zhí)行的是一個本地的方法,那么程序計數(shù)器的值就不會被定義。
Java虛擬機的棧有三個區(qū)域:局部變量區(qū)、運行環(huán)境區(qū)、操作數(shù)區(qū)。
局部變量區(qū)
每個Java方法使用一個固定大小的局部變量集。它們按照與vars寄存器的字偏移量來尋址。局部變量都是32位的。長整數(shù)和雙精度浮點數(shù)占據(jù)了兩個局部變量的空間,卻按照第一個局部變量的索引來尋址。(例如,一個具有索引n的局部變量,如果是一個雙精度浮點數(shù),那么它實際占據(jù)了索引n和n+1所代表的存儲空間)虛擬機規(guī)范并不要求在局部變量中的64位的值是64位對齊的。虛擬機提供了把局部變量中的值裝載到操作數(shù)棧的指令,也提供了把操作數(shù)棧中的值寫入局部變量的指令。
運行環(huán)境區(qū)
在運行環(huán)境中包含的信息用于動態(tài)鏈接,正常的方法返回以及異常捕捉。
動態(tài)鏈接
運行環(huán)境包括對指向當前類和當前方法的解釋器符號表的指針,用于支持方法代碼的動態(tài)鏈接。方法的class文件代碼在引用要調(diào)用的方法和要訪問的變量時使用符號。動態(tài)鏈接把符號形式的方法調(diào)用翻譯成實際方法調(diào)用,裝載必要的類以解釋還沒有定義的符號,并把變量訪問翻譯成與這些變量運行時的存儲結(jié)構(gòu)相應(yīng)的偏移地址。動態(tài)鏈接方法和變量使得方法中使用的其它類的變化不會影響到本程序的代碼。
正常的方法返回
如果當前方法正常地結(jié)束了,在執(zhí)行了一條具有正確類型的返回指令時,調(diào)用的方法會得到一個返回值。執(zhí)行環(huán)境在正常返回的情況下用于恢復(fù)調(diào)用者的寄存器,并把調(diào)用者的程序計數(shù)器增加一個恰當?shù)臄?shù)值,以跳過已執(zhí)行過的方法調(diào)用指令,然后在調(diào)用者的執(zhí)行環(huán)境中繼續(xù)執(zhí)行下去。
異常捕捉
異常情況在Java中被稱作Error(錯誤)或Exception(異常),是Throwable類的子類,在程序中的原因是:①動態(tài)鏈接錯,如無法找到所需的class文件。②運行時錯,如對一個空指針的引用。程序使用了throw語句。
當異常發(fā)生時,Java虛擬機采取如下措施:
操作數(shù)棧區(qū)
機器指令只從操作數(shù)棧中取操作數(shù),對它們進行操作,并把結(jié)果返回到棧中。選擇棧結(jié)構(gòu)的原因是:在只有少量寄存器或非通用寄存器的機器(如Intel486)上,也能夠高效地模擬虛擬機的行為。操作數(shù)棧是32位的。它用于給方法傳遞參數(shù),并從方法接收結(jié)果,也用于支持操作的參數(shù),并保存操作的結(jié)果。例如,iadd指令將兩個整數(shù)相加。相加的兩個整數(shù)應(yīng)該是操作數(shù)棧頂?shù)膬蓚€字。這兩個字是由先前的指令壓進堆棧的。這兩個整數(shù)將從堆棧彈出、相加,并把結(jié)果壓回到操作數(shù)棧中。
每個原始數(shù)據(jù)類型都有專門的指令對它們進行必須的操作。每個操作數(shù)在棧中需要一個存儲位置,除了long和double型,它們需要兩個位置。操作數(shù)只能被適用于其類型的操作符所操作。例如,壓入兩個int類型的數(shù),如果把它們當作是一個long類型的數(shù)則是非法的。在Sun的虛擬機實現(xiàn)中,這個限制由字節(jié)碼驗證器強制實行。但是,有少數(shù)操作(操作符dupe和swap),用于對運行時數(shù)據(jù)區(qū)進行操作時是不考慮類型的。
本地方法棧,當一個線程調(diào)用本地方法時,它就不再受到虛擬機關(guān)于結(jié)構(gòu)和安全限制方面的約束,它既可以訪問虛擬機的運行期數(shù)據(jù)區(qū),也可以使用本地處理器以及任何類型的棧。例如,本地棧是一個C語言的棧,那么當C程序調(diào)用C函數(shù)時,函數(shù)的參數(shù)以某種順序被壓入棧,結(jié)果則返回給調(diào)用函數(shù)。在實現(xiàn)Java虛擬機時,本地方法接口使用的是C語言的模型棧,那么它的本地方法棧的調(diào)度與使用則完全與C語言的棧相同。
3??Java虛擬機的運行過程
上面對虛擬機的各個部分進行了比較詳細的說明,下面通過一個具體的例子來分析它的運行過程。
虛擬機通過調(diào)用某個指定類的方法main啟動,傳遞給main一個字符串數(shù)組參數(shù),使指定的類被裝載,同時鏈接該類所使用的其它的類型,并且初始化它們。例如對于程序:
class HelloApp { public static void main(String[] args) { System.out.println("Hello World!"); for (int i = 0; i < args.length; i++ ) { System.out.println(args[i]); } } }
編譯后在命令行模式下鍵入: java HelloApp run virtual machine
將通過調(diào)用HelloApp的方法main來啟動java虛擬機,傳遞給main一個包含三個字符串"run"、"virtual"、"machine"的數(shù)組。現(xiàn)在我們略述虛擬機在執(zhí)行HelloApp時可能采取的步驟。
開始試圖執(zhí)行類HelloApp的main方法,發(fā)現(xiàn)該類并沒有被裝載,也就是說虛擬機當前不包含該類的二進制代表,于是虛擬機使用ClassLoader試圖尋找這樣的二進制代表。如果這個進程失敗,則拋出一個異常。類被裝載后同時在main方法被調(diào)用之前,必須對類HelloApp與其它類型進行鏈接然后初始化。鏈接包含三個階段:檢驗,準備和解析。檢驗檢查被裝載的主類的符號和語義,準備則創(chuàng)建類或接口的靜態(tài)域以及把這些域初始化為標準的默認值,解析負責檢查主類對其它類或接口的符號引用,在這一步它是可選的。類的初始化是對類中聲明的靜態(tài)初始化函數(shù)和靜態(tài)域的初始化構(gòu)造方法的執(zhí)行。一個類在初始化之前它的父類必須被初始化。整個過程如下:
圖4:虛擬機的運行過程
4??結(jié)束語
本文通過對JVM的體系結(jié)構(gòu)的深入研究以及一個Java程序執(zhí)行時虛擬機的運行過程的詳細分析,意在剖析清楚Java虛擬機的機理。
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title></title>
<script language=javascript>
function fTxtKeyDown(obj)
{
? if(event.keyCode == 32)
? {
??? event.returnValue=false;
??? //空格鍵
??? var sRet;
??? try {
????? sRet = eval(obj.value);
??? }
??? catch(e) {
??? }
??? if (isNaN(sRet))
??? {
????? alert("計算式輸入錯誤");
??? }
??? else
??? {
??????? obj.value=sRet;
??? }
? }
}
</script>
</head>
<body>
<font size="2">輸入表達式(如:3*2-4)后,按空格鍵得到結(jié)果 try</font><br>
<br>
<input type=text onkeydown="fTxtKeyDown(this);" size="42">
</body>
</html>
Days On My Past
I recall when I was young. Oh I will play and always having fun
With the neighbours next to me and we'll play until the setting sun
Try to be the best among the others in a game call the "spider battle"
It doesn't matter who is the best now. Those were the days of my past
A few years later when I got to school and was late for lesson all the time
Always day dreaming in the class, till I don't even know the lesson's done
Then my teacher always tell me never ever be lazy again
What can I do now? What can I say now? Those were the days of my past
As the days go on and on I grew up and had my first love
Candel light and sandy beach finally give away my first kiss
Mother said I was too young to fall in love and then I will one day regret
So love was over, but I do miss her. Those were the days of my past
Just when I left my high school and got my first job as salesmen
Working hard all day and night. No one there to lend a helping hand
Dady told me not to worry and said that l should go on step by step
What can I say now? What can I do now? Those were the days of my past
Then once day I settled down with the only one I really love setting? n.? (太陽)落山 spider? n. 蜘蛛,設(shè)圈套者 give away? v. 送掉,分發(fā) regret? v. 感到遺憾,后悔,惋惜,哀悼 step by step? adv. 逐步地 settle down? v. 定居,安定下來
Got a small family with two kids that is what I'm always hoping for
But I still remember having fun with all my friends when I was young
I miss my home town. I miss my old friends, those the days of my past
Oh I miss my home town I miss my old friends. When will I see them again?
重點詞匯
歌曲名:童年
詞/曲:羅大佑?
編曲:山崎稔?
池塘邊的榕樹上?知了在聲聲叫著夏天?
操場邊的秋千上?只有蝴蝶停在上面
黑板上老師的粉筆?還在拼命唧唧喳喳寫個不停?
等待著下課?等待著放學?等待游戲的童年
福利社里面什么都有?就是口袋里沒有半毛錢?
諸葛四郎和魔鬼黨?到底誰搶到那支寶劍
隔壁班的那個女孩?怎么還沒經(jīng)過我的窗前?
嘴里的零食?手里的漫畫?心里初戀的童年
總是要等到睡覺前?才知道功課只做了一點點?
總是要等到考試以后?才知道該念的書都沒有念
一寸光陰一寸金?老師說過寸金難買寸光陰?
一天又一天?一年又一年?迷迷糊糊的童年
沒有人知道為什么?太陽總下到山的那一邊?
沒有人能夠告訴我?山里面有沒有住著神仙?
多少的日子里?總是一個人面對著天空發(fā)呆?
就這么好奇?就這么幻想?這么孤單的童年
陽光下蜻蜓飛過來?一片片綠油油的稻田?
水彩蠟筆和萬花筒?畫不出天邊那一道彩虹?
什么時候才能像高年級的同學有張成熟與長大的臉?
盼望著假期?盼望著明天?盼望長大的童年
一天又一天?一年又一年?盼望長大的童年
?
Proud Of You
Fiona Fung
Love in your eyes, sitting silent by my side
Going on, holding hand, walking through the nights
Hold me up, hold me tight, lift me up to touch the sky
Teaching me to love with heart, helping me open my mind
I can fly. I'm proud that I can fly
To give the best of mine till the end of the time
Believe me I can fly. I'm proud that I can fly
To give the best of mine. The heaven in the sky
Stars in the sky wishing once upon a time
Give me love, make me smile till the end of life
Hold me up. Hold me tight. Lift me up to touch the sky
Teaching me to love with heart, helping me open my mind
I can fly. I'm proud that I can fly
To give the best of mine till the end of the time
Believe me I can fly. I'm proud that I can fly
To give the best of mine. The heaven in the sky
Can't you believe that you light up my way?
No matter how that ease my path I'll never lose my faith
See me fly I'm proud to fly up high
Show you the best of mine till the end of the time
Believe me I can fly I'm singing in the sky
Show you the best of mine The heaven in the sky
Nothing can stop me spread my wings so wide
參考譯文(來自互聯(lián)網(wǎng))
你眼中充滿愛意,靜靜的坐在我身旁。沿途挽著你的手,慢步整個晚上
抱起我,抱緊我,讓我能觸摸到天空。懂得了用心去愛,助我把心窗打開
我會飛了,飛讓我驕傲。給你我的全部,直至時光停頓
我真的會飛,飛令我驕傲。給你我的全部,天上的天國
天上繁星,希望有一天。愛的降臨讓我歡笑,直至我的一生
我會飛了,飛讓我驕傲。給你我的全部,直至時光停頓
我真的會飛,飛令我驕傲。給你我的全部,天上的天國
你可相信、你已照亮了我的前路。我的路途如何已不重要,我決不失去信心
看我飛,高飛讓我驕傲。給你展現(xiàn)最佳的我,直至時光停頓
我真的會飛,在天上高歌。給你展現(xiàn)最佳的我,天上的天國
沒什么能停止我展翅高飛。