走在架構(gòu)師的大道上 Jack.Wang's home

          Java, C++, linux c, C#.net 技術(shù),軟件架構(gòu),領(lǐng)域建模,IT 項(xiàng)目管理 Dict.CN 在線詞典, 英語(yǔ)學(xué)習(xí), 在線翻譯

          BlogJava 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
            195 Posts :: 3 Stories :: 728 Comments :: 0 Trackbacks
           

               JVM 學(xué)習(xí)筆記

          Jack.Wang(本文未完,待續(xù)…..

          摘要:JVM 作為 Java 的核心技術(shù),很多朋友想必也有研究。一直都在關(guān)注 JVM 方面的技術(shù),以前看過一些書籍和網(wǎng)上的資料,自己也發(fā)了些 Blog 文章,不過還是沒有徹底的了解 JVM 機(jī)制,最近有時(shí)間研究了研究,特此寫下一篇文章并結(jié)合筆者多年實(shí)踐以揭露 JVM 實(shí)現(xiàn)機(jī)理,本文后面提供資料下載,讀者可進(jìn)一步研究。 

          關(guān)鍵字: JVMJava 技術(shù),虛擬機(jī),Java 架構(gòu)

          目錄
          1 JVM架構(gòu)引言 
          2 JVM安全框架 
          3 JVM內(nèi)部機(jī)理 
            3.1 JVM的生命周期 
            3.2 JVM的框架 
            3.3 數(shù)據(jù)類型 
              3.3.1 Java數(shù)據(jù)類型 
              3.3.2 浮點(diǎn)運(yùn)算 
            3.4 方法區(qū) 
            3.5 操作數(shù)棧 
            3.6 本地方法棧 
            3.7 執(zhí)行引擎 
          4 類文件結(jié)構(gòu) 
          5 線程同步 
          6 垃圾回收機(jī)制 
          7 總結(jié) 
          8 參考資料

          文中的圖片沒有上傳,不過想看圖片的可以下載原文。
          本文下載地址 JVM學(xué)習(xí)筆記.doc

          1          JVM架構(gòu)引言

          Java 的平臺(tái)獨(dú)立,安全和網(wǎng)絡(luò)可移植性是的 Java 最適合網(wǎng)絡(luò)計(jì)算環(huán)境,java 的四大核心技術(shù)是,Java 語(yǔ)言,字節(jié)碼格式,Java APIJVM。每次你編寫Java程序時(shí)你就用到了這四項(xiàng)技術(shù),那么你是否對(duì)他們有足夠的了解呢?

              很多人把 JVM 看成是 Java 的解釋器,這是錯(cuò)誤的理解,并不是所有的 JVM 實(shí)現(xiàn)都 Java 的解釋器,有的也用到了 JIT (Just-In-Time) 技術(shù)。Java 是不允許直接操作本地機(jī)器指令的,對(duì)于Java方法來(lái)說(shuō)有兩種:Javanative,對(duì)于native我更習(xí)慣用C++ DLL,但Java并不提倡你這么做,因?yàn)橛袚pJava平臺(tái)獨(dú)立性。JVM 中除了執(zhí)行引擎就是類加載器了,ClassLoader也分為兩種:原始加載器和加載器Object,原始加載器使用和寫JVM 一樣的語(yǔ)言寫的,比如用C寫的類加載器,而加載器Object就是用 Java 實(shí)現(xiàn)的類加載器,方便我們擴(kuò)展,比如你自己可以 New 一個(gè) URLClassLoader 從網(wǎng)絡(luò)上下載字節(jié)碼到本地運(yùn)行。一個(gè)類的加載和他參考的類的加載應(yīng)該用同一個(gè) ClassLoader。這一點(diǎn)在發(fā)生異常的時(shí)候很難找出,比如 OSGI 中每個(gè) bundle 都有自己獨(dú)立的 ClassLoader,對(duì)于新手很容易犯錯(cuò)誤而無(wú)從下手,我們熟悉的WEB 服務(wù)器 Tomcat 的類加載器是分層(有繼承關(guān)系)的,所以在應(yīng)用整合的時(shí)候也很容易發(fā)生 ClassLoader 相關(guān)的異常,而這樣的異常往往很難定位。平臺(tái)互異的字節(jié)序問題,在Java中,字節(jié)碼是大字節(jié)序的。Java 為支持開發(fā)者開發(fā)應(yīng)用軟件提供了大量的 API,可以說(shuō),在計(jì)算機(jī)領(lǐng)域的大部分計(jì)算中Java都有對(duì)應(yīng)的解決方案。

              C++中可能比較受關(guān)注和困擾的就是指針了,而在Java中用“參考”這樣一個(gè)類似的東西代替了,參考不向指針那樣允許參與計(jì)算,避免了開發(fā)人員直接操作內(nèi)存,還有個(gè)垃圾回收機(jī)制也避免了開發(fā)者手動(dòng)釋放內(nèi)存,還有就是 C++ 中的數(shù)組是不進(jìn)行邊界檢查的而Java中每次使用數(shù)組的時(shí)候都要進(jìn)行邊界檢查,豈不安全。 可見Java相比C++ 提高了開發(fā)效率和安全性。JavaC++ 比運(yùn)行速度是個(gè)大問題,因此任何語(yǔ)言都不萬(wàn)能的,在開發(fā)是我們應(yīng)該適當(dāng)權(quán)衡,Java運(yùn)行速度低的原因主要有:

          Ø         Interpreting bytecodes is 10 to 30 times slower than native execution.

          Ø         Just-in-time compiling bytecodes can be 7 to 10 times faster than interpreting, but still not quite as fast as native execution.

          Ø         Java programs are dynamically linked.

          Ø         The Java Virtual Machine may have to wait for class files to download across a network.

          Ø         Array bounds are checked on each array access.

          Ø         All objects are created on the heap (no objects are created on the stack).

          Ø         All uses of object references are checked at run-time for null.

          Ø         All reference casts are checked at run-time for type safety.

          Ø         The garbage collector is likely less efficient (though often more effective) at managing the heap than you could be if you managed it directly as in C++.

          Ø         Primitive types in Java are the same on every platform, rather than adjusting to the most efficient size on each platform as in C++.

          Ø         Strings in Java are always UNICODE. When you really need to manipulate just an ASCII string, a Java program will be slightly less efficient than an equivalent C++ program.

          2          JVM安全框架

          Java 允許基于網(wǎng)絡(luò)的代碼運(yùn)行和傳播,為其帶了安全問題。但Java也提供了內(nèi)嵌的安全模型,開發(fā)者可高枕無(wú)憂。Java的沙箱安全模型使即時(shí)你不信任的代碼也可讓他在本機(jī)執(zhí)行,如果是惡意的代碼他的惡意行為也會(huì)被沙箱攔截,所以在運(yùn)行任何你有點(diǎn)懷疑的代碼前請(qǐng)確保你的沙箱沒有缺陷。

          對(duì)于沙箱的四大基礎(chǔ)組件是:類加載器,類文件驗(yàn)證,JVM安全特性,安全管理的API,其中最重要的是類加載器和安全管理API,因?yàn)樗麄兛梢钥椭苹?duì)于加載器,每個(gè) JVM 都可以有多個(gè),同一個(gè)類可以加載多次到不同的 ClassLoader 中,類跨ClassLoader是不可見的,而在同一ClassLoader中是可直接訪問的,這樣可以隔離一些不安全的類。

          類型檢查是很必要的,分為兩個(gè)階段,第一是在類加載進(jìn)來(lái)的時(shí)候要進(jìn)行類的合法性和完整性檢查,第二是運(yùn)行時(shí)確認(rèn)該類所參考的類,方法和屬性是否存在。類文件頭都是以一個(gè)四個(gè)字節(jié)的幻數(shù)開頭(0xCAFEBABE)來(lái)標(biāo)識(shí)是個(gè)類文件,當(dāng)然也有文件大小域,第一階段確保加載進(jìn)來(lái)的類是正確格式,內(nèi)部一直,Java語(yǔ)法語(yǔ)義限制一直,包括安全的可執(zhí)行代碼,在這個(gè)過程中如果有錯(cuò)誤,JVM會(huì)拋出異常,該類就不會(huì)被使用。第二階段其實(shí)由于動(dòng)態(tài)連接的原因,需要在運(yùn)行時(shí)檢查參考,因?yàn)?ClassLoader 在需要某些類時(shí)才去加載,延遲加載,在 ORM 產(chǎn)品中,比如 Hibernate, jdo 等都有所謂的延遲加載

          SecurityManager 有一系列的 checkXXX 的方法,用來(lái)檢測(cè)相關(guān)操作是否合法,一般我們的程序是不用 SecurityManager 的,除非你安裝一個(gè)SecurityManager,如果沒有寫自己的策略文件,一般是用jre 下面的默認(rèn)策略文件的設(shè)置,當(dāng)然也可在 VM 運(yùn)行參數(shù)設(shè)置策略文件的位置。SecurityManager 類的相關(guān)方法。

              publicstatic SecurityManager getSecurityManager() {

              return security;

              }

             publicstatic

              void setSecurityManager(final SecurityManager s) {

                  try {

                      s.checkPackageAccess("java.lang");

                  } catch (Exception e) {

                      // no-op

                  }

                  setSecurityManager0(s);

              }

              privatestaticsynchronized

              void setSecurityManager0(final SecurityManager s) {

              SecurityManager sm = getSecurityManager();

                  if (sm != null) {

                  // ask the currently installed security manager if we

                  // can replace it.

                  sm.checkPermission(new RuntimePermission

                             ("setSecurityManager"));

              }

              if ((s != null) && (s.getClass().getClassLoader() != null)) {

                  AccessController.doPrivileged(new PrivilegedAction() {

                 public Object run() {

                     s.getClass().getProtectionDomain().implies

                     (SecurityConstants.ALL_PERMISSION);

                     returnnull;

                 }

                  });

              }

          security = s;

          InetAddressCachePolicy.setIfNotSet(InetAddressCachePolicy.FOREVER);}

          設(shè)置自己的 SercurityManager:

          System.setSecurityManager(new JackSecurityManager());

          SecurityManager sm = System.getSecurityManager();

          3          JVM內(nèi)部機(jī)理

          3.1         JVM的生命周期

          任何一個(gè)類的 main 函數(shù)運(yùn)行都會(huì)創(chuàng)建一個(gè)JVM實(shí)例,當(dāng)main 函數(shù)結(jié)束JVM實(shí)例也就結(jié)束了,如果三個(gè)類分別運(yùn)行各自的 main 函數(shù)則會(huì)創(chuàng)建3個(gè)不同的JVM實(shí)例。JVM實(shí)例啟動(dòng)時(shí)默認(rèn)啟動(dòng)幾個(gè)守護(hù)線程,而 main 方法的執(zhí)行是在一個(gè)單獨(dú)的非守護(hù)線程中執(zhí)行的。只要母線程結(jié)束,子線程就自動(dòng)銷毀,只要非守護(hù)main 線程結(jié)束JVM實(shí)例就銷毀了。

          3.2         JVM的框架

          JVM主要由 ClassLoader 子系統(tǒng)和執(zhí)行引擎子系統(tǒng)組成,運(yùn)行數(shù)據(jù)區(qū)分為五個(gè)部分,他們是方法區(qū),堆,棧,指令寄存器,本地方法棧。方法區(qū)和堆是所有線程共享的,一般我們習(xí)慣對(duì)臨時(shí)變量放在寄存器中,但JVM中不用寄存器而是用棧,每個(gè)線程都有自己獨(dú)立的棧空間和指令計(jì)數(shù)器,其中棧里的元素叫幀,幀有三部分組成分別是局部變量,操作數(shù)棧和幀數(shù)據(jù)。正式因?yàn)闂J敲總€(gè)線程獨(dú)有的,所以對(duì)于 local 變量,是沒有多線程沖突問題的。棧和幀的數(shù)據(jù)結(jié)構(gòu)和大小規(guī)范里沒有硬性規(guī)定,由實(shí)現(xiàn)者具體實(shí)做。

          3.3         數(shù)據(jù)類型

          3.3.1   Java數(shù)據(jù)類型

                  

          虛擬機(jī)的數(shù)據(jù)類型分為:原始類型和參考類型,有人一直說(shuō)Java只有90%OO的,其中原始類型就算的上,不過還好有對(duì)應(yīng)的封裝類型如 int->IntegerJDK1.5可以用這些封裝類型直接做算術(shù)運(yùn)算,不過Java內(nèi)部要做拆箱和裝箱的工作。似乎   OO了些,不過現(xiàn)代的程序員才不過你這些,能用就行。

          字長(zhǎng)是數(shù)據(jù)值得基本單元,一般像 int, byte, short 等類型的值用一個(gè)字長(zhǎng)存儲(chǔ),而浮現(xiàn)類型用兩個(gè)字長(zhǎng)存儲(chǔ),字長(zhǎng)的大小一般和機(jī)器的指針大小相同,最小是32 位。

          byte, short, or char 這樣的類型在方法區(qū)和堆中Java是原來(lái)類型,但在程序運(yùn)行時(shí)放在棧幀中,統(tǒng)一用整型(int)來(lái)表示

          對(duì)于類型之間的轉(zhuǎn)換JVM有對(duì)應(yīng)的指令如:

          上圖是小范圍向大范圍轉(zhuǎn)變,叫做向上轉(zhuǎn)型,當(dāng)然也有向下轉(zhuǎn)型的指令,不過可能會(huì)造成數(shù)據(jù)被破壞,如圖:

          不管你怎么變化,棧幀里都是以字為單位的,32位機(jī)子一個(gè)字是4個(gè)字節(jié),正好是一個(gè)整型大小,如前所述原始類型在棧中都被表示為 int 型,當(dāng)然如果是 long double 那就用兩個(gè)字來(lái)表示咯。

          3.3.2   浮點(diǎn)運(yùn)算

          浮點(diǎn)運(yùn)算算法因該是倍受關(guān)注的了,Java中浮點(diǎn)數(shù)的表示遵照IEEE 754 1985的規(guī)范,這個(gè)規(guī)范定義了32位和64位浮點(diǎn)格式和運(yùn)算,我都知道Java中的 float 32位而,double64位。對(duì)于浮點(diǎn)數(shù)大家都很清楚他分為四個(gè)部分:符號(hào),尾數(shù),基數(shù)和冪,要得到浮點(diǎn)數(shù)值必須將這四個(gè)部分相乘才能得到值。所有的浮點(diǎn)數(shù)都要經(jīng)過標(biāo)準(zhǔn)化,我們都知道計(jì)算機(jī)里只有01這樣的表示,尾數(shù)和冪都要二進(jìn)制表示,對(duì)于尾數(shù)float 用它的23位表示,double 用它的52位來(lái)表示,對(duì)于冪數(shù) float 用它的 8位表示,double用它的11為表示,剩下以為表示符號(hào)。

               JVM也有對(duì)應(yīng)的浮點(diǎn)運(yùn)算指令如圖(加法指令)

          3.4         方法區(qū)

          ClassLoader負(fù)責(zé)把class加載進(jìn)來(lái)并解析類信息放在方法區(qū),方法區(qū)是被所有線程共享的,所以方法區(qū)必須保證線程的安全。比如兩個(gè)線程同時(shí)需要一個(gè)類,必須保證只有一個(gè)線程去加載而另一個(gè)線程等待。

          方法區(qū)的大小不是固定的,一般會(huì)在運(yùn)行時(shí)JVM會(huì)根據(jù)運(yùn)行情況作出調(diào)整,但JVM實(shí)現(xiàn)者都留有接口可供調(diào)節(jié),比如最大和最小值。

          我們都知道堆是被垃圾回收器回收的,在方法區(qū)的類也是會(huì)被回收的,類也有他的生命周期,當(dāng)某個(gè)類不再被參考之后也就沒有這個(gè)類的引用和對(duì)象了那么他的存在就沒有意義了,只是站著內(nèi)存,回收器就會(huì)按照一定的算法將它卸載了。對(duì)于一個(gè)類信息的表述都是用全名的,在方法區(qū)中也有其他信息,比如常量池,屬性和方法信息,還有就是指向堆中的 ClassLoaderClass 的參考。我大家都熟悉的就是類的靜態(tài)變量,也放在方法區(qū)的由所有的對(duì)象實(shí)例共享。

          可以想象在方法區(qū)內(nèi)放置了大量的二進(jìn)制的Class信息,為了加快訪問速度,JVM實(shí)現(xiàn)者都會(huì)維護(hù)一個(gè)方法表,記得讀研一的時(shí)候中間件老師講過這些東西是結(jié)合指針講的C++ 的內(nèi)存模型。另外注意的就是方法表只針對(duì)可實(shí)例化的類,對(duì)抽象類和接口沒有意義。

          每個(gè)JVM實(shí)例都只有一個(gè)堆,所有的線程共享其中的對(duì)象,這才出現(xiàn)了多線程安全問題。JVMnew 對(duì)象的指令但沒有釋放對(duì)象的指令,當(dāng)讓這些指令都是虛擬指令,這些對(duì)象的釋放是有 GC 來(lái)做的,GCJVM規(guī)范中并沒有硬性的規(guī)定,有實(shí)現(xiàn)者設(shè)計(jì)他的實(shí)現(xiàn)形式和算法。

          想必很多同人都想知道,對(duì)象是怎么樣在堆里表示的,其實(shí)很簡(jiǎn)單。其實(shí)面JVM規(guī)范也沒有細(xì)致的規(guī)定對(duì)象怎么在堆里表示的,

          如圖是一個(gè)參考的堆模型,具體實(shí)現(xiàn)可能不是這樣的,這個(gè)是HeapOfFish applet 的一個(gè)演示模型,具體內(nèi)容可以看看JVM規(guī)范。當(dāng)然也有很多其他的模型,這個(gè)模型的好處就是在堆壓縮的時(shí)候很方便,而在 reference 直接 point到一個(gè)對(duì)象的模型來(lái)說(shuō)在堆壓縮方面是很麻煩的,因?yàn)槟阋紤]到方法區(qū),堆,棧里可能的參考,你都要修改。對(duì)象還有一個(gè)很重要的數(shù)據(jù)結(jié)構(gòu)就是方法表,方法表可以加快訪問速度,但并不是說(shuō)所有的JVM實(shí)現(xiàn)都有。

          堆中的每個(gè)對(duì)象都有指向方法區(qū)的指針,而自己主要保留對(duì)象屬性信息,如圖:

          看一個(gè)方法區(qū)鏈接的例子,看看一個(gè)類是怎么加載進(jìn)來(lái),怎么鏈接初始化的:

          有一 Salutation 類

          class Salutation {

              private static final String hello = "Hello, world!";

              private static final String greeting = "Greetings, planet!";

              private static final String salutation = "Salutations, orb!";

              private static int choice = (int) (Math.random() * 2.99);

              public static void main(String[] args) {

                  String s = hello;

                  if (choice == 1) {

                      s = greeting;

                  }

                  else if (choice == 2) {

                      s = salutation;

                  }

                  System.out.println(s);

              }

          }

          Assume that you have asked a Java Virtual Machine to run Salutation. When the virtual machine starts, it attempts to invoke the main() method of Salutation. It quickly realizes, however, that it canít invoke main(). The invocation of a method declared in a class is an active use of that class, which is not allowed until the class is initialized. Thus, before the virtual machine can invoke main(), it must initialize Salutation. And before it can initialize Salutation, it must load and link Salutation. So, the virtual machine hands the fully qualified name of Salutation to the primordial class loader, which retrieves the binary form of the class, parses the binary data into internal data structures, and creates an instance of java.lang.Class.

          常量池里的內(nèi)容:

          Index

          Type

          Value

          1

          CONSTANT_String_info

          30

          2

          CONSTANT_String_info

          31

          3

          CONSTANT_String_info

          39

          4

          CONSTANT_Class_info

          37

          5

          CONSTANT_Class_info

          44

          6

          CONSTANT_Class_info

          45

          7

          CONSTANT_Class_info

          46

          8

          CONSTANT_Class_info

          47

          9

          CONSTANT_Methodref_info

          7, 16

          10

          CONSTANT_Fieldref_info

          4, 17

          11

          CONSTANT_Fieldref_info

          8, 18

          12

          CONSTANT_Methodref_info

          5, 19

          13

          CONSTANT_Methodref_info

          6, 20

          14

          CONSTANT_Double_info

          2.99

          16

          CONSTANT_NameAndType_info

          26, 22

          17

          CONSTANT_NameAndType_info

          41, 32

          18

          CONSTANT_NameAndType_info

          49, 34

          19

          CONSTANT_NameAndType_info

          50, 23

          20

          CONSTANT_NameAndType_info

          51, 21

          21

          CONSTANT_Utf8_info

          "()D"

          22

          CONSTANT_Utf8_info

          "()V"

          23

          CONSTANT_Utf8_info

          (Ljava/lang/String;)V"

          24

          CONSTANT_Utf8_info

          ([Ljava/lang/String;)V"

          25

          CONSTANT_Utf8_info

          <clinit"

          26

          CONSTANT_Utf8_info

          <init"

          27

          CONSTANT_Utf8_info

          "Code"

          28

          CONSTANT_Utf8_info

          "ConstantValue"

          29

          CONSTANT_Utf8_info

          "Exceptions"

          30

          CONSTANT_Utf8_info

          "Greetings,planet!"

          31

          CONSTANT_Utf8_info

          "Hello, world!"

          32

          CONSTANT_Utf8_info

          "I"

          33

          CONSTANT_Utf8_info

          "LineNumberTable"

          34

          CONSTANT_Utf8_info

          "Ljava/io/PrintStream;"

          35

          CONSTANT_Utf8_info

          "Ljava/lang/String;"

          36

          CONSTANT_Utf8_info

          "LocalVariables"

          37

          CONSTANT_Utf8_info

          "Salutation"

          38

          CONSTANT_Utf8_info

          "Salutation.java"

          39

          CONSTANT_Utf8_info

          "Salutations, orb!"

          40

          CONSTANT_Utf8_info

          "SourceFile"

          41

          CONSTANT_Utf8_info

          "choice"

          42

          CONSTANT_Utf8_info

          "greeting"

          43

          CONSTANT_Utf8_info

          "hello"

          44

          CONSTANT_Utf8_info

          "java/io/PrintStream"

          45

          CONSTANT_Utf8_info

          "java/lang/Math"

          46

          CONSTANT_Utf8_info

          "java/lang/Object"

          47

          CONSTANT_Utf8_info

          "java/lang/System"

          48

          CONSTANT_Utf8_info

          "main"

          49

          CONSTANT_Utf8_info

          "out"

          50

          CONSTANT_Utf8_info

          "println"

          51

          CONSTANT_Utf8_info

          "random"

          52

          CONSTANT_Utf8_info

          "salutation"

          As part of the loading process for Salutation, the Java Virtual Machine must make sure all of Salutationís superclasses have been loaded. To start this process, the virtual machine looks into Salutationís type data at the super_class item, which is a seven. The virtual machine looks up entry seven in the constant pool, and finds a CONSTANT_Class_info entry that serves as a symbolic reference to class java.lang.Object. See Figure 8-5 for a graphical depiction of this symbolic reference. The virtual machine resolves this symbolic reference, which causes it to load class Object. Because Object is the top of Salutationís inheritance hierarchy, the virtual machine and links and initializes Object as well.

          3.5         操作數(shù)棧

          操作數(shù)棧是Java運(yùn)行時(shí)的核心棧,看看 i+j 的一個(gè)簡(jiǎn)單運(yùn)算,

          iload_0  

          iload_1   

          iadd      

          istore_2 

          以上是四個(gè)JVM指令,完成 i+j并把結(jié)果保存到k中,如圖示:

          在堆中不可能分配一個(gè)原始類型的空間放值,而是先用對(duì)象封裝才能存在堆空間中,帶Java棧中也不可能放對(duì)象,而只有原始類型和參考類型。上次有人爭(zhēng)議數(shù)組放在何處?在Java中數(shù)組和對(duì)象是同等地位的,都放在堆中而他的參考是放在棧里,JVM有對(duì)應(yīng)的指令比如 newarray, anewarray等。

          3.6         本地方法棧

          想必很多人用過 JNI 結(jié)束,Java是不提倡這么做的,而且在這放的設(shè)計(jì)和實(shí)現(xiàn)上,個(gè)人覺得不是那么好,至少他比不那么方便,所以很少見應(yīng)用開發(fā)者去寫些 Native 方法,每次你去看Java原代碼是你經(jīng)常看到native 方法,也看到JDK下的 DLL 文件,大部分JVM都是用CC++寫的。前面也提過,這樣就破壞了Java的平臺(tái)獨(dú)立性,在本地方法運(yùn)行的時(shí)候也有專門的棧去處理。Java在執(zhí)行本地方法的時(shí)候暫時(shí)放棄Java stack 的操作,轉(zhuǎn)向本地方法,本地方法有自己的棧或堆的處理方式,Java在執(zhí)行本地方法時(shí)會(huì)在本地棧和Java棧之間切換,如圖:

          a thread first invoked two Java methods, the second of which invoked a native method. This act caused the virtual machine to use a native method stack. In this figure, the native method stack is shown as a finite amount of contiguous memory space. Assume it is a C stack. The stack area used by each C-linkage function is shown in gray and bounded by a dashed line. The first C-linkage function, which was invoked as a native method, invoked another C-linkage function. The second C-linkage function invoked a Java method through the native method interface. This Java method invoked another Java method, which is the current method shown in the figure,做過JNI開發(fā)的朋友應(yīng)該了解JavaCC++ 是如何交互的,這些都是執(zhí)行引擎的事。

          3.7         執(zhí)行引擎

          執(zhí)行引擎,應(yīng)該是JVM的核心了,一般我把它看作指令集合。JVM規(guī)范詳細(xì)的描述了每個(gè)指令的作用,但沒有進(jìn)一步描述如何實(shí)現(xiàn),還由實(shí)現(xiàn)廠商自己設(shè)計(jì),可以用解釋的方式,JIT方式,編譯本地代碼的方式,或者幾者混合的方式,當(dāng)然也可用一些新的不為我們知道的技術(shù)。

          每一個(gè)線程都有一個(gè)自己的執(zhí)行引擎,據(jù)我了解JVM指令用一個(gè)字節(jié)表示,也就是JVM最多有256個(gè)指令,目前JVM已有160(目前可能多于這些)個(gè)指令。就有這百十個(gè)指令組成了我的系統(tǒng),JVM指令一般只有操作碼沒有操作數(shù),一般操作數(shù)放在常量池和Java棧中,設(shè)計(jì)指令集的最重要的目標(biāo)應(yīng)該是平臺(tái)獨(dú)立性,同時(shí)在驗(yàn)證bytecode也比較方便。有些指令的操作都是基于具體類型的,有的就沒有比如 goto,如圖

          指令前綴表示操作類型, 可見Java編譯器和JVM對(duì)類型操作要求很嚴(yán)格,為何使指令盡量用一個(gè)字節(jié)表示,很多類型如 byte, char 都沒有直接運(yùn)算,而是在運(yùn)算前把他們轉(zhuǎn)換成整型。

          執(zhí)行過程的在某一時(shí)刻內(nèi)容如圖:

          在幀里的局部變量有四個(gè)分別都是 reference,都指向不同的對(duì)象,有的時(shí)候我們編程當(dāng)操作完成,最好把 o, greeter ,c, gcl  這四個(gè)參考付為 null,這樣他們指向的對(duì)象不可達(dá),對(duì)象不可達(dá),他們?cè)诜椒▍^(qū)的類信息也不可達(dá),類信息不可達(dá),堆中的 Class 對(duì)象不可達(dá),你可以看到圖中的所有對(duì)象,類信息都是可回收狀態(tài),這樣GC某個(gè)時(shí)刻就可以釋放了這些內(nèi)存了。

          寫方法時(shí)我們希望 try cache 起來(lái),當(dāng)然也有需要在后面 加上 finally,我們都知道在finally 里的代碼被執(zhí)行玩前所有的 return 操作都會(huì)壓入棧里,等 finally塊執(zhí)行完了再?gòu)棾龇祷兀纾?/span>

          static boolean test(boolean bVal) {

          while (bVal) {

          try {

          return true;

          }

          finally {

          break;

          }

          }

          return false;

          }

          可以看出這個(gè)方法的執(zhí)行以返回 false 告終。

          4          類文件結(jié)構(gòu)

          讀者可以用 UltrEdit 之類的編輯工具打開一個(gè) class 文件查看,每個(gè)類文件都包含如下內(nèi)容如圖:其中 u2 表示兩個(gè)字節(jié)大小,u4表示四個(gè)字節(jié)大小。

          如第一行所示,每個(gè)類文件都以幻數(shù)開頭固定為(0xCAFEBABE),用UltraEdit 等編輯工具可以直接看到,當(dāng)然你也可以看到很多如圖所示的屬性和方法的描述:

          常量池是以類型來(lái)劃分不同的表格的,如CONSTANT_Double_info 表就只是double 常量值,CONSTANT_Class_info表存儲(chǔ)參考信息,比如類,接口,屬性和方法的符號(hào)參考,類文件的核心數(shù)據(jù)結(jié)構(gòu)就是常量池了,而所有的類,屬性和方法的描述以及值都是通過 index 到常量池獲得的,所以常量池是個(gè)很重要的概念,有興趣的朋友可進(jìn)一步參考JVM規(guī)范。

          5          線程同步

                  對(duì)多線程的支持是java 語(yǔ)言的一大亮點(diǎn),java 實(shí)現(xiàn)線程同步的方式是monitor,具體有兩種方式:互斥和協(xié)同,每個(gè)對(duì)象都有一把鎖,通過 lock 和 unlock 實(shí)現(xiàn)互斥的操作,Object類有 wait notify 等這樣的方法可以實(shí)現(xiàn)線程為實(shí)現(xiàn)一個(gè)共同的目標(biāo)而協(xié)同工作。monitor就像一個(gè)建筑有一個(gè)特殊的房間每次只允許一個(gè)線程訪問。都知道線程同步有一個(gè)重要的概念就是臨界區(qū),任何一個(gè)線程要想進(jìn)入臨界區(qū)就必須獲得monitor,如果線程發(fā)現(xiàn)monitor被其他線程獲得,此線程必須排隊(duì),等到他前面的線程也退出了 monitor 的占有,則此線程才能操作 monitor 的臨界區(qū),當(dāng)然也有優(yōu)先級(jí) 的問題。此圖說(shuō)明了多個(gè)線程協(xié)作工作的過程。

                  前面介紹過,堆和方法區(qū)是被線程共享的,這兩大塊要做同步,而對(duì)于局部變量是由線程獨(dú)有的所以不存在同步問題,Java API 中有一個(gè)很重要也很簡(jiǎn)單的類就是 ThreadLocal,他是用 Theard key 來(lái)標(biāo)識(shí)變量,是非局部變量局部化的一個(gè)很好的例子。

                  對(duì)象鎖的概念大家都很熟悉了,Java中通過這個(gè)鎖有兩種方式,一種是同步塊,一種是同步方法,具體的內(nèi)容請(qǐng)看看java 多線程編程的書。

          6          垃圾回收機(jī)制

                    堆里聚集了所有由應(yīng)用程序創(chuàng)建的對(duì)象,JVM也有對(duì)應(yīng)的指令比如 new, newarray, anewarraymultianewarray,然并沒有向 C++ deletefree 等釋放空間的指令,Java的所有釋放都由 GC 來(lái)做,GC除了做回收內(nèi)存之外,另外一個(gè)重要的工作就是內(nèi)存的壓縮,這個(gè)在其他的語(yǔ)言中也有類似的實(shí)現(xiàn),相比 C++ 不僅好用,而且增加了安全性,當(dāng)然她也有弊端,比如性能這個(gè)大問題。

          垃圾回收的算法有很多,不過怎么樣,任何一個(gè)算法都要做兩件事情,一是識(shí)別垃圾對(duì)象,二是清理內(nèi)存以備運(yùn)行程序使用。區(qū)分活著的對(duì)象和垃圾對(duì)象的主要方法就是參考計(jì)數(shù)和追蹤,參考計(jì)數(shù)就是每個(gè)對(duì)象都維護(hù)一個(gè)引用 count,而每次追蹤都去標(biāo)記對(duì)象,當(dāng)一次跟蹤完成沒有被標(biāo)記的對(duì)象就是不可到達(dá)的那就是垃圾了。參考計(jì)數(shù)的方式現(xiàn)在已經(jīng)很少JVM實(shí)現(xiàn)用了,因?yàn)閷?duì)于 child, parent 的內(nèi)部參考,count 永遠(yuǎn)不會(huì)是0,也就是不會(huì)是垃圾對(duì)象,而且每次修改引用都要加減 count。而跟蹤是從根節(jié)點(diǎn)開始掃描所有對(duì)象,并作標(biāo)記。知道那些對(duì)象是垃圾了之后,就要釋放了,有兩種方式一個(gè)是壓縮,一個(gè)是拷貝,不管哪種方式改變了對(duì)象的物理位置,就要修改他的參考值,每個(gè)對(duì)像都有一個(gè) finalize 方法,一般我們不去 override 他,他是在回收時(shí)期執(zhí)行,但并不是所有的對(duì)象的 finalize 方法都會(huì)被執(zhí)行,具體內(nèi)容請(qǐng)看 inside in java machine.

          7          總結(jié)

          本文結(jié)合作者的一些觀點(diǎn)和幾年的開發(fā)實(shí)踐,談?wù)剬?duì)JVM規(guī)范的看法,由于完稿倉(cāng)促,也沒多少時(shí)間整理,如果有什么問題可以進(jìn)一步討論,本人編譯器這方面也是比較薄弱,真正按照JVM規(guī)范實(shí)現(xiàn)虛擬機(jī)還有很長(zhǎng)的路要走,可能在將來(lái)的某天有機(jī)會(huì)進(jìn)一步研究一下。多謝各位的指導(dǎo)和幫助,我會(huì)陸續(xù)在Blog里發(fā)些文章。

          8          參考資料

          1)inside in java machine 想要的請(qǐng)點(diǎn)擊下載

          2)http://www.aygfsteel.com/Jack2007/archive/2008/05/23/202485.htmlJava虛擬機(jī)深入研究

          3)http://www.aygfsteel.com/Jack2007/archive/2008/05/21/202018.html

          4)http://www.tudou.com/programs/view/hN_4sQJMoFQ/

          5)http://www.aygfsteel.com/Jack2007/archive/2008/04/11/192288.html

          6)http://developer.51cto.com/art/200805/73338.htm

           





          本博客為學(xué)習(xí)交流用,凡未注明引用的均為本人作品,轉(zhuǎn)載請(qǐng)注明出處,如有版權(quán)問題請(qǐng)及時(shí)通知。由于博客時(shí)間倉(cāng)促,錯(cuò)誤之處敬請(qǐng)諒解,有任何意見可給我留言,愿共同學(xué)習(xí)進(jìn)步。
          posted on 2008-08-27 15:18 Jack.Wang 閱讀(6835) 評(píng)論(5)  編輯  收藏 所屬分類: 開發(fā)技術(shù)

          Feedback

          # re: JVM 學(xué)習(xí)筆記 2008-08-28 00:11 流浪汗
          比如兩個(gè)線程同時(shí)需要一個(gè)類,必須保證只有一個(gè)線程去加載而另一個(gè)線程等待。

          我不在認(rèn)為正確, 方法區(qū)是共用, 但運(yùn)行時(shí), 方法調(diào)用的時(shí)候有自己的數(shù)據(jù)區(qū), 除非那變量是類的(static), 否則保證object線程完全就行了.  回復(fù)  更多評(píng)論
            

          # re: JVM 學(xué)習(xí)筆記 2008-08-28 11:48 zhuxing
          java虛擬機(jī)對(duì)類加載本身就做了同步處理。!!!

          同一個(gè)類加載器實(shí)例不可能對(duì)同一類型加載多次
          不同的類加載器實(shí)例可能會(huì)對(duì)同一全名類型記載多次,那本質(zhì)上是不同的類型了,類加載器實(shí)例一個(gè)很核心的作用就是來(lái)維護(hù)命名空間  回復(fù)  更多評(píng)論
            

          # re: JVM 學(xué)習(xí)筆記 2008-08-28 17:21 Swing
          ORM中的延遲加載似乎指的是對(duì)象不是類吧?  回復(fù)  更多評(píng)論
            

          # re: JVM 學(xué)習(xí)筆記 2008-08-29 12:21 隔葉黃鶯
          一個(gè) JVM 可以由多個(gè) ClassLoader 加載同一個(gè)類,所以說(shuō)在同一個(gè) JVM 中一個(gè)類靜態(tài)變量可以表現(xiàn)為多個(gè)值,但它們是互不可見的,所以仍不會(huì)影響到真正的使用。在 JVM 中對(duì)象有對(duì)象的實(shí)例,類有類的實(shí)例,比如
          Class1 obj1,obj2; 對(duì)象實(shí)例是不一樣的,但同們的類實(shí)例 obj1.getClass() 是一樣的,前提是它們都是委派給同一個(gè) ClassLoader 加載的

          字長(zhǎng)的大不應(yīng)該是由 JVM 的統(tǒng)一,比如在 64 位 CPU 中運(yùn)行的 32 位 JVM,最小字長(zhǎng)這時(shí)候會(huì)是多少呢?Java 要由不跨平臺(tái)的 JVM 來(lái)保證跨平臺(tái)。

          ORM 的延遲加載應(yīng)該是在對(duì)象層面的概念,提前加載的是是目標(biāo)對(duì)象的代理對(duì)象。

          像 Groovy 那樣的腳本會(huì)是更純粹面向?qū)ο?

          def i = 1; 打印出 i.getClass(); 會(huì)顯示為 class java.lang.Integer

          在棧幀中,不光是 byte, short, char 表現(xiàn)為 int,而且 boolean 也是用 int 來(lái)表示

          我想,大家都習(xí)慣用引用來(lái)表示“參考”

          寫得不錯(cuò),就是有不少錯(cuò)別字。

          順便問一下,讀研若之前沒有積蓄能不能完全養(yǎng)活自己,不然靠老婆養(yǎng)可沒面子哦,呵呵!  回復(fù)  更多評(píng)論
            

          # re: JVM 學(xué)習(xí)筆記 2008-08-29 12:53 Jack.Wang
          @隔葉黃鶯
          本文只是亂打的草書,沒有細(xì)致的分析,所以錯(cuò)別字太多。
          考研當(dāng)然要有積蓄。學(xué)費(fèi)+生活費(fèi)要幾萬(wàn)塊啦,在加上兩三年不賺錢,對(duì)于我們工作的人來(lái)說(shuō),損失幾十萬(wàn)啊!不過自己選擇的路,就應(yīng)該好好走下去。
            回復(fù)  更多評(píng)論
            

          主站蜘蛛池模板: 中江县| 马鞍山市| 武平县| 绥德县| 西青区| 新宾| 南靖县| 漾濞| 株洲市| 彰化县| 滕州市| 定边县| 塘沽区| 绥中县| 肇源县| 邹城市| 邵东县| 修文县| 眉山市| 卓尼县| 吐鲁番市| 深水埗区| 昌吉市| 拉萨市| 凭祥市| 泰宁县| 宜兴市| 凉山| 大渡口区| 当阳市| 乌兰察布市| 静安区| 甘肃省| 文成县| 凤山市| 西丰县| 湘潭市| 舟曲县| 沽源县| 苍山县| 六盘水市|