日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

【转】深入理解JVM

發布時間:2024/1/4 综合教程 25 生活家
生活随笔 收集整理的這篇文章主要介紹了 【转】深入理解JVM 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

http://www.cnblogs.com/liupeizhi/articles/1942764.html

1.類的加載:

在java中可分為四類加載器

引導類加載器 Bootstrap Loader 用來加載%java_home%lib下的核心類庫像String、Date等等

擴展類加載器 Extension Loader 用來加載%java_home%lib/ext下的擴展api

系統類加載器 AppClassLoader 加載classpath下面的類文件,我們的所有類文件默認都是由它來加載的,怎么樣,感覺親切吧

用戶自定義的類加載器

下面我們舉例說明類的加載

public class A{

public static void main(String args[]){

B b=new B();

}

}

Public class B{

Public static String a="hello";

public static String b=getValue();

Static{

System.out.println("Hello World");

}

Public static void getValue(){

}

}

假如我們自己定義了一個名字為A 的類,當我們啟動虛擬機即(java A)的時候,會創建一個JVM實例。現在我們就看類B 的加載過程。由于B是被A 引用 的,所以B是由A的類加載器進行加載。在這里我們不得不說一下類加載器是比較孝順的孩子,為什么這么說呢,因為類加載器在加載類的時候采用雙親委托機制。簡單說就是在加載類的時候,加載器會調用父類加載器來加載,父類再調用父類,依次類推。這個說起來比較抽象,我們這里給出源代碼來表示一下其中的加載過程:

public Class loadClass(String name){

ClassLoader parent=this.getClassLoader().getParent();

Try{

Class c=findLoadedClass(name);

//如果這個類沒有被加載

If(c!=null){

//如果有父類加載器

If(parent!=null)

parent.loadClass(name);

Else

BootstrapLoader.loadClass(name)

}catch(FileNotFoundException ex){

//如果父類加載器找不到,就調用自己的findClass查找

this.findClass(name);

}

//如果這個類已經被加載

Else

Return c;

}

這代碼是我自己寫的,是對源代碼的簡化表示,不要直接拷貝使用,如果想要知道詳細內容,建議參源碼。這段可以完全清晰地表示出類加載器的調用關系了。但是里面有個問題,相信各位都會發現了,就是 BootstrapLoader.loadClass(name).BootstrapLoader為什么不創建實例呢?因為BootstrapLoader并不是用java寫的,是一個本地方法(native),也就是說是用c/c++或者其他語言編寫的方法。為什么要這么做呢?主要是因為我們的類加載器也是類,如果他們都是用java實現,那么他們如何加載?所以,sun給了我們一個引導類加載器用來加載其他的類加載器,之后我們才能用這些類加載器加載我們的類文件。這里我們說一下他們的父子關系。

我們自定義的類加載器的父類加載器是 AppClassLoader

AppClassLoader的父類加載器是Extension Loader

Extension Loader的父類加載器是 Bootstrap Loader

當我們加載類B的時候,由于沒有指定它的類加載器,默認由AppClassLoader進行加載,調用loadClass()方法,AppClassLoader發現它的parent不是null,就會調用父類加載器(Extension Loader)加載,Extension Loader發現它的父母是null(因為BootstrapLoader 不是java寫的,所以不會被Extension Loader訪問到)于是就調用BootstrapLoader來加載,由于我們的B類是在我們的classpath中,所以必然會產生ClassNotFoundException ,接著調用自己的findClass進行查找,ExtensionLoader訪問的是%java_home%/lib/ext下面的類,必然也無法找到我們的B。于是會在AppClassLoader中捕獲到異常,然后接著調用AppClassLoader的findClass進行加載,結果找到了。

  終于啊,經過這么復雜的遞歸調用和冒泡查找后找到了我們的類B 了。至于為什么要設計的這么復雜,直接加載不就完了嗎,干嘛搞得這么難受。這主要是出于安全性的考慮。你想想,這個過程中總是由Bootstrap來加載核心類,假如你自己寫了一個名字叫String的類,里面含有攻擊性的代碼,如果能加載成功,必然會導致其他依賴此類的類導致錯誤,整個JVM就會崩潰。然而這個類是無法加載到內存中的,因為類的加載總是由BootstrapLoader開始,當他發現已經加載了String,就不會再加載了,有效地保證了系統的安全性。類的加載過程基本就這樣,下面貼出一段代碼,自己實現的類加載器。

import java.io.ByteArrayOutputStream;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;



public class MyClassLoader extends ClassLoader{

private String path="F:\\JSP\\ClassLoaderDemo\\classes\\";

private String fileType=".class";


@Override

public Class findClass(String name){

byte bytes[]=this.loadClassData(name);


return this.defineClass(name, bytes, 0, bytes.length);

}

//加載類數據,返回一個byte數組

public byte[] loadClassData(String name){

try {

FileInputStream fin=new FileInputStream(path+name+fileType);

ByteArrayOutputStream bout=new ByteArrayOutputStream();

int ch=0;

while((ch=fin.read())!=-1){

bout.write(ch);

}

return bout.toByteArray();

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

return null;

}

}

//類加載器的測試類

public class TestLoader {

public static void main(String argsp[]){

MyClassLoader loader=new MyClassLoader();

try {

//在指定的目錄中加載HelloWorld.class文件

Class myclass=loader.loadClass("HelloWorld");

//加載完畢后進行實例化,這個過程包含了對類的解析

myclass.newInstance();

System.out.println(myclass.getName());

} catch (ClassNotFoundException e) {

e.printStackTrace();

} catch (InstantiationException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}


這段代碼可以直接拷貝運行.

我們大篇幅的講述了類的加載過程,這是jvm運行的第一步,建議各位讀到這里的時候在腦海中回顧一下類的整個加載過程,以便于理解下面我們要說的類的鏈接。如果你覺得理解的沒有問題,我們繼續說一下類的連接階段。

當我們將類加載到內存中的時候,我們就需要對他進行驗證。這里我們先介紹一下JVM是如何進行虛擬的。

想必大家都知道我們的CPU是由控制器,運算器,存儲器,輸入輸出設備組成的,這些是我們程序運行所必需的硬件環境。你可以這樣認為,我們的JVM為我們的java程序虛擬出了完整的一套運行環境,他的控制器由我們的JVM直接擔任,像垃圾處理了內存分配了等等,運算器當然就是我們的cpu了,存儲器是jvm的運行數據區,輸入輸出設備就是硬件設備了。這里你可以發現,我們的java是不能直接與硬件進行交互的,底層功能的實現需要通過本地方法進行實現,這就是我們的java跨平臺的原因。我們的JVM會根據硬件環境的不同(這里主要是指CPU的指令集的不同),將我們的class文件解釋成cpu可以識別的指令碼,這樣,我們的CPU就能夠運行我們的java程序了,這就是java的偉大之處。更確切的說使我們JVM的偉大之處了,呵呵。

這里,你只需要大概的了解一下JVM的原理就OK了,之后我們會細細的講解。

我們現在再說說類的連接階段。

當我們把類加載到內存之后,我們如何保證他的正確性呢,或者說我們如何保證加載進來的二進制碼是不是符合我們的Class類型的結構呢?關于結構,要細細說來需要很大的篇幅,這里你只需要這樣理解他Class就像是類的模板一樣,它包含類的所有信息,包括訪問控制符號(public,private,友好型)、是類還是接口,直接父類是誰,實現的接口有什么,以及字段信息,方法信息 ,還有一個常量池。你看看,這不就是我們類所包含的所有信息嗎。他們按照一定的結構組織在內存中,我們把這樣的一塊內存結構稱為Class。就是我們常說的類類型。

我們接著說,為了保證我們加載的二進制代碼是Class結構,所以我們需要進行校驗,很多地方稱為驗證,我感覺稱之為校驗更為合適。我們的校驗程序校驗完畢,發現它是我們需要的Class結構的時候,就會通知JVM為我們Class在方法區域分配空間。

說道這里,我們又要說說我們JVM的運行時數據區了,也就是他的存儲結構,下面,我會用圖示的方法來解釋它。

我們的JVM將運行數據區分為如下幾塊

堆:用來存儲我們創建的對象的地方

棧:JVM會為每個線程創建一個棧,用來存儲局部變量和操作數,棧跟棧之間不能通信。存儲單位是棧幀。我們每調用一個方法,就新建一個棧幀壓入棧中。棧幀之間是屏蔽的,這就是為什么一個方法無法訪問另一個方法中的變量。棧幀由局部變量區(用數組實現),操作數棧(棧結構),幀數據(主要用來支持對類常量池的解析,方法的正常返回,異常處理)

方法區:用來保存Class類型數據

JVM的內存主要結構就這么多了。

好了,我們接著說,也許你現在對這張圖還有很多疑問,稍后你就會明白了。我們的類現在已經通過驗證了,校驗器告訴我們它符合我們的Class結構,而且在方法區域為他分配了空間,我們非常高興。下面就是關乎初始化問題了。有人會問,不對還有解析呢。呵呵,在寫程序我們也知道了,這個階段是可選的,也就是說你可以讓你的類加載后馬上初始化,也可以加載完畢不進行初始化。在這里我們要讓我們的類初始化,下面即使解析階段了。

解析階段的主要任務:將類變量的符號引用解析成直接地址引用。就拿我們的變量a來說,他的值是"hello",這個東西在Class中只是一個符號而已。然而,我們的Class需要將所有的常量都存放在常量池中,所以hello會被存儲到常量池中,然后提供一個入口地址給a。a 就能直接引用它了。這里得好好地理解理解。

我們的方法b引用的是一個方法,這個方法在Class中只是一個符號而已,這個方法的實際代碼存放在一張表中,這張表我們成為方法表。我們的b就指向了方法表的一個引用。

解析完畢之后,就要初始化了,初始化很簡單,就是執行靜態代碼塊中的內容了。整個加載到此已經完畢,想必大家已經很清楚了吧。

然而,我們的JVM的任務才剛剛開始。

下面我們說一下對象的創建吧,想必這個問題在很多人看來都是很不解的。那么我們馬上開始吧。

對象的實例是什么呢?在內存中的樣子是什么呢。

如果你知道了方法區中的東西,對于對象也就不難理解了。對象就像一種結構,其中存儲了指向方法的引用,實例變量,一個指向類常量池的引用(這就是為什么實例可以訪問類變量和類方法)。這些數據按照一定的結構(就像Class結構一樣,只是簡單很多)存儲在我們的堆區,這就是我們耳熟能詳的對象。當我們new的時候,JVM就會按照上面的過程,在堆區為我們構造一個這樣的數據結構,然后將塊數據的引用存儲到棧里面。稍后我們會細細講解棧的結構

說到堆,我們不得不說JVM的內存管理機制,或者堆空間的垃圾處理機制。假如你自己寫了一個JVM,你肯定會碰到這樣一個問題,我們不斷的在堆里面創建對象,再大的內存也有耗盡的時候,那我們如何進行垃圾處理呢。以前,在JDK1的時候采用的是對整個堆空間進行掃描,查找不再被使用的對象將其回收,可想而知這種策略是多么的低效。后來,我們聰明的java工程師給我們提供了這樣的存儲結構,他們將堆分為了兩大部分,新生區和永久區。

總結

以上是生活随笔為你收集整理的【转】深入理解JVM的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。