㈠ jvm在調用native本地方法時,本地方法是由誰執行的
jvm和其他虛擬機一樣,API有兩種類型,一種是在託管環境下執行的,就是你日常使用的;還有一類是在非託管環境下執行的,也就是所謂本地(Native)方法。虛擬機會將本地方法封裝成wrapper,你在調用native方法的時候,jvm會通過wrapper將其包裝成本地api的格式,然後去調用並返回結果。
jvm負責幫你寫的java代碼連接到動態或靜態鏈接的api上。而這些鏈接的庫是誰編譯的,這不需要jvm關心,jvm只負責按照api調用就可以了。
㈡ 揭秘Java虛擬機:JVM設計原理與實現詳細資料大全
《揭秘Java虛擬機:JVM設計原理與實現》是2017年電子工業出版社出版的圖書,作者是封亞飛。
㈢ jvm原理是什麼呢
jvm原理是java的核心和基礎,在java編譯器和os平台之間的虛擬處理器。它是一種利用軟體方法實現的抽象的計算機基於下層的操作系統和硬體平台,可以在上面執行java的位元組碼程序,java編譯器只要面向JVM,生成JVM能理解的代碼或位元組碼文件。
jvm執行程序的過程
載入.class文件,管理並分配內存,執行垃圾收,四步完成JVM環境,創建JVM裝載環境和配置,裝載JVM.dll,初始化JVM.dll並掛界到JNIENV(JNI調用介面)實例,調用JNIEnv實例裝載並處理class類。
JVM實例和JVM執行JVM實例對應了一個獨立運行的java程序,進程級別, 一個運行時的Java虛擬機(JVM)負責運行一個Java程序,當啟動一個Java程序時,一個虛擬機實例誕生,當程序關閉退出,這個虛擬機實例也就隨之消亡。
㈣ java強制類型轉換的時候JVM是如何工作的
JVM是java的核心和基礎,在java編譯器和os平台之間的虛擬處理器。它是一種基於下層的操作系統和硬體平台並利用軟體方法來實現的抽象的計算機,可以在上面執行java的位元組碼程序。
java編譯器只需面向JVM,生成JVM能理解的代碼或位元組碼文件。Java源文件經編譯器,編譯成位元組碼程序,通過JVM將每一條指令翻譯成不同平台機器碼,通過特定平台運行。
JVM執行程序的過程 :
I.載入.class文件
II.管理並分配內存
III.執行垃圾收集
JRE(java運行時環境)包含JVM的java程序的運行環境
JVM是Java程序運行的容器,但是他同時也是操作系統的一個進程,因此他也有他自己的運行的生命周期,也有自己的代碼和數據空間。
JVM在整個jdk中處於最底層,負責與操作系統的交互,用來屏蔽操作系統環境,提供一個完整的Java運行環境,因此也叫虛擬計算機.操作系統裝入JVM是通過jdk中Java.exe來完成,通過下面4步來完成JVM環境。
1.創建JVM裝載環境和配置
2.裝載JVM.dll
3.初始化JVM.dll並掛接到JNIENV(JNI調用介面)實例
4.調用JNIEnv實例裝載並處理class類。
㈤ jvm 操作棧 怎麼調用
堆棧是JVM分配的,一般涉及的時候都是啟動JVM時。
eclipse可以在eclipse.ini 配置文件中設置,也可以在eclipse執行一個java類時,通過右鍵的參數添加部分去設置。
一般的像tomcat,weblogic這些web容器,都是應用jvm啟動的,所以在他們的啟動腳本中都會去調用JVM,就可以在他們的啟動腳本中設置堆棧的大小。
再就是直接通過java 命令去執行class文件的時候,應該也可以設置JVM參數,eg : java -Xms512m -Xmx1024m HelloWorld
在cmd中設置,也必須是執行java命令時,否則我覺得沒有意義。
㈥ 深入理解JVM之ClassLoader
在編寫Java程序時需要使用javac命令將.java後綴名的文件編譯成.class文件,然後JVM通過執行.class文件來運行我們寫的程序,那麼JVM怎麼才能執行.class文件呢?
這就需要類載入器了。
在jdk中找到sun.misc.Launcher文件,這個文件是JVM中的啟動器實例,在該類內部有 getLauncher() 方法
它返回的是 launcher 實例
在構造方法調用時會初始化 ExtClassLoader和AppClassLoader(BootstrapClassLoader會在Java虛擬機創建之後進行載入,所以是在此之前)。
通過點擊 getXxxClassLoader() 跟到最後可以看到
注意: 我們查看ExtClassLoader時會發現這里的parent是null,而查看AppClassLoader時傳入的parent是ExtClassLoader的對象實例。所以這里也就印證了上面畫的圖。
載入步驟:
找到某個ClassLoader,這里以AppClassLoader為例
在AppClassLoader中搜索 findClass() 方法是找不到的,所以找到每個載入器共同的父類 URLClassLoader ,在這里可以看到 findClass() 方法。
findClass 方法的作用是從指定的路徑找到傳入的文件名稱的.class文件,並返回回去,該方法的主要邏輯在 run() 方法中。
從前面可以知道,要想實現classLoader只需要繼承ClassLoader類並重寫 findClass 和 loadClass 方法即可。
在桌面創建了一個名為Test的Java文件,並使用javac命令進行編譯。
運行結果:
從運行結果可以看出,自定義的類載入器已經完成了對指定文件的載入,並正確的執行了方法。
㈦ 如何獲得JVM執行過程中調用的方法名
這應該分成兩個問題,1.如何獲取參數值. 2.如何獲取參數名,
1.如何獲取參數值。這個是運行時的數據,你程序處理下獲取就好了。比如寫一個代理
2.參數名是在編譯的時候就寫入到class文件的。,而這些方法的參數在class中就是一個局部變數。class對於局部變數的定義和存儲專門有張表。
單純通過反射目前好像沒有辦法,通過位元組碼解析倒是可以
比如下面代碼
public static void staticMethod(String args1, String args2) {
}
局部變數表:
[pc: 0, pc: 1] local: args1 index: 0 type: java.lang.String
[pc: 0, pc: 1] local: args2 index: 1 type: java.lang.String
pc 0是每個位元組碼指令的程序計數器。[pc: 0, pc: 1] local: args1 index: 0 type: java.lang.String就是說在程序第0個指令到第1個指令的局部變數數組下標為0的變數類型是String變數名是args1.
public static void nonStaticMethod(String args1, String args2) {
}
局部變數表;
[pc: 0, pc: 1] local: this index: 0 type: asmtest.Test
[pc: 0, pc: 1] local: args1 index: 1 type: java.lang.String
[pc: 0, pc: 1] local: args2 index: 2 type: java.lang.String
這個方法比上面的方法多了一個this。因為這個方法是非靜態方法。
所以如果要獲取參數名需要解析位元組碼。這里給你一段代碼使用ASM
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
public class ReadMethodArgNameClassVisitor extends ClassVisitor {
public Map<String, List<String>> nameArgMap = new HashMap<String, List<String>>();
public ReadMethodArgNameClassVisitor() {
super(Opcodes.ASM5);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
Type methodType = Type.getMethodType(desc);
int len = methodType.getArgumentTypes().length;
List<String> argumentNames = new ArrayList<String>();
nameArgMap.put(name, argumentNames);
visitor = new (Opcodes.ASM5);
visitor.argumentNames = argumentNames;
visitor.argLen = len;
return visitor;
}
}
import java.util.List;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
public class extends MethodVisitor {
public List<String> argumentNames;
public int argLen;
public (int api) {
super(api);
}
@Override
public void visitLocalVariable(String name, String desc, String signature,
Label start, Label end, int index) {
if("this".equals(name)) {
return;
}
if(argLen-- > 0) {
argumentNames.add(name);
}
}
}
public class POJO {
public void say(String message, int times){
}
}
import java.io.IOException;
import java.util.List;
import java.util.Map.Entry;
import org.objectweb.asm.ClassReader;
public class Test {
public static void main(String... args1) throws IOException {
ClassReader cr = new ClassReader("POJO");
ReadMethodArgNameClassVisitor classVisitor = new ReadMethodArgNameClassVisitor();
cr.accept(classVisitor, 0);
for(Entry<String, List<String>> entry : classVisitor.nameArgMap.entrySet()) {
System.out.println(entry.getKey());
for(String s : entry.getValue()) {
System.out.println(" " + s);
}
}
}
}
使用asm版本是
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-all</artifactId>
<version>5.0.3</version>
</dependency>
這里存在一個隱患,如果有些class文件做了加密混淆吧局部變數表裡面的變數名改變了,那就沒法獲得源碼級別的參數名了。
㈧ JVM原理是什麼
首先這里澄清兩個概念:JVM實例和JVM執行引擎實例,JVM實例對應了一個獨立運行的Java程序,而JVM執行引擎實例則對應了屬於用戶運行程序的線程;也就是JVM實例是進程級別,而執行引擎是線程級別的。JVM是什麼?—JVM的生命周期JVM實例的誕生:當啟動一個Java程序時,一個JVM實例就產生了,任何一個擁有publicstaticvoidmain(String[]args)函數的class都可以作為JVM實例運行的起點,既然如此,那麼JVM如何知道是運行classA的main而不是運行classB的main呢?這就需要顯式的告訴JVM類名,也就是我們平時運行Java程序命令的由來,如JavaclassAhelloworld,這里Java是告訴os運行SunJava2SDK的Java虛擬機,而classA則指出了運行JVM所需要的類名。JVM實例的運行:main()作為該程序初始線程的起點,任何其他線程均由該線程啟動。JVM內部有兩種線程:守護線程和非守護線程,main()屬於非守護線程,守護線程通常由JVM自己使用,Java程序也可以標明自己創建的線程是守護線程。JVM實例的消亡:當程序中的所有非守護線程都終止時,JVM才退出;若安全管理器允許,程序也可以使用Runtime類或者System.exit()來退出。JVM是什麼?—JVM的體系結構粗略分來,JVM的內部體系結構分為三部分,分別是:類裝載器(ClassLoader)子系統,運行時數據區,和執行引擎。下面將先介紹類裝載器,然後是執行引擎,最後是運行時數據區1、類裝載器,顧名思義,就是用來裝載.class文件的。JVM的兩種類裝載器包括:啟動類裝載器和用戶自定義類裝載器,啟動類裝載器是JVM實現的一部分,用戶自定義類裝載器則是Java程序的一部分,必須是ClassLoader類的子類。(下面所述情況是針對SunJDK1.2)動類裝載器:只在系統類(JavaAPI的類文件)的安裝路徑查找要裝入的類用戶自定義類裝載器:系統類裝載器:在JVM啟動時創建,用來在CLASSPATH目錄下查找要裝入的類其他用戶自定義類裝載器:這里有必要先說一下ClassLoader類的幾個方法,了解它們對於了解自定義類裝載器如何裝載.class文件至關重要。(Stringname,bytedata[],intoffset,intlength) (Stringname,bytedata[],intoffset,intlength,);(Stringname) (Classc) defineClass用來將二進制class文件(新類型)導入到方法區,也就是這里指的類是用戶自定義的類(也就是負責裝載類)findSystemClass通過類型的全限定名,先通過系統類裝載器或者啟動類裝載器來裝載,並返回Class對象。ResolveClass:讓類裝載器進行連接動作(包括驗證,分配內存初始化,將類型中的符號引用解析為直接引用),這里涉及到Java命名空間的問題,JVM保證被一個類裝載器裝載的類所引用的所有類都被這個類裝載器裝載,同一個類裝載器裝載的類之間可以相互訪問,但是不同類裝載器裝載的類看不見對方,從而實現了有效的屏蔽。2、執行引擎:它或者在執行位元組碼,或者執行本地方法要說執行引擎,就不得不的指令集,每一條指令包含一個單位元組的操作碼,後面跟0個或者多個操作數。(一)指令集以棧為設計中心,而非以寄存器為中心這種指令集設計如何滿足Java體系的要求:平台無關性:以棧為中心使得在只有很少register的機器上實現Java更便利compiler一般採用stack向連接優化器傳遞編譯的中間結果,若指令集以stack為基礎,則有利於運行時進行的優化工作與執行即時編譯或者自適應優化的執行引擎結合,通俗的說就是使編譯和運行用的數據結構統一,更有利於優化的開展。網路移動性:class文件的緊湊性。安全性:指令集中絕大部分操作碼都指明了操作的類型。(在裝載的時候使用數據流分析期進行一次性驗證,而非在執行每條指令的時候進行驗證,有利於提高執行速度)。(二)執行技術主要的執行技術有:解釋,即時編譯,自適應優化、晶元級直接執行其中解釋屬於第一代JVM,即時編譯JIT屬於第二代JVM,自適應優化(目前Sun的HotspotJVM採用這種技術)則吸取第一代JVM和第二代JVM的經驗,採用兩者結合的方式自適應優化:開始對所有的代碼都採取解釋執行的方式,並監視代碼執行情況,然後對那些經常調用的方法啟動一個後台線程,將其編譯為本地代碼,並進行仔細優化。若方法不再頻繁使用,則取消編譯過的代碼,仍對其進行解釋執行。3、運行時數據區:主要包括:方法區,堆,Java棧,PC寄存器,本地方法棧(1)方法區和堆由所有線程共享堆:存放所有程序在運行時創建的對象方法區:當JVM的類裝載器載入.class文件,並進行解析,把解析的類型信息放入方法區。(2)Java棧和PC寄存器由線程獨享,在新線程創建時間里(3)本地方法棧:存儲本地方法調用的狀態上邊總體介紹了運行時數據區的主要內容,下邊進行詳細介紹,要介紹數據區,就不得不說明JVM中的數據類型。JVM中的數據類型:JVM中基本的數據單元是word,而word的長度由JVM具體的實現者來決定數據類型包括基本類型和引用類型,(1)基本類型包括:數值類型(包括除boolean外的所有的Java基本數據類型),boolean(在JVM中使用int來表示,0表示false,其他int值均表示true)和returnAddress(JVM的內部類型,用來實現finally子句)。(2)引用類型包括:數組類型,類類型,介面類型前邊講述了JVM中數據的表示,下面讓我們輸入到JVM的數據區首先來看方法區:上邊已經提到,方法區主要用來存儲JVM從class文件中提取的類型信息,那麼類型信息是如何存儲的呢?眾所周知,Java使用的是大端序(big?endian:即低位元組的數據存儲在高位內存上,如對於1234,12是高位數據,34為低位數據,則Java中的存儲格式應該為12存在內存的低地址,34存在內存的高地址,x86中的存儲格式與之相反)來存儲數據,這實際上是在class文件中數據的存儲格式,但是當數據倒入到方法區中時,JVM可以以任何方式來存儲它。類型信息:包括class的全限定名,class的直接父類,類類型還是介面類型,類的修飾符(public,等),所有直接父介面的列表,Class對象提供了訪問這些信息的窗口(可通過Class.forName(「」)或instance.getClass()獲得),下面是Class的方法,相信大家看了會恍然大悟,(原來如此J)getName(),getSuperClass(),isInterface(),getInterfaces(),getClassLoader();static變數作為類型信息的一部分保存指向ClassLoader類的引用:在動態連接時裝載該類中引用的其他類指向Class類的引用:必然的,上邊已述該類型的常量池:包括直接常量(String,integer和floatpoint常量)以及對其他類型、欄位和方法的符號引用(注意:這里的常量池並不是普通意義上的存儲常量的地方,這些符號引用可能是我們在編程中所接觸到的變數),由於這些符號引用,使得常量池成為Java程序動態連接中至關重要的部分欄位信息:普通意義上的類型中聲明的欄位方法信息:類型中各個方法的信息編譯期常量:指用final聲明或者用編譯時已知的值初始化的類變數class將所有的常量復制至其常量池或者其位元組碼流中。方法表:一個數組,包括所有它的實例可能調用的實例方法的直接引用(包括從父類中繼承來的)除此之外,若某個類不是抽象和本地的,還要保存方法的位元組碼,操作數棧和該方法的棧幀,異常表。舉例:classLava{ privateintspeed=5; voidflow(){} classVolcano{ publicstaticvoidmain(String[]args){ Lavalava=newLava(); lava.flow(); } } 運行命令JavaVolcano;(1)JVM找到Volcano.class倒入,並提取相應的類型信息到方法區。通過執行方法區中的位元組碼,JVM執行main()方法,(執行時會一直保存指向Vocano類的常量池的指針)(2)Main()中第一條指令告訴JVM需為列在常量池第一項的類分配內存(此處再次說明了常量池並非只存儲常量信息),然後JVM找到常量池的第一項,發現是對Lava類的符號引用,則檢查方法區,看Lava類是否裝載,結果是還未裝載,則查找「Lava.class」,將類型信息寫入方法區,並將方法區Lava類信息的指針來替換Volcano原常量池中的符號引用,即用直接引用來替換符號引用。(3)JVM看到new關鍵字,准備為Lava分配內存,根據Volcano的常量池的第一項找到Lava在方法區的位置,並分析需要多少對空間,確定後,在堆上分配空間,並將speed變數初始為0,並將lava對象的引用壓到棧中(4)調用lava的flow()方法好了,大致了解了方法區的內容後,讓我們來看看堆Java對象的堆實現:Java對象主要由實例變數(包括自己所屬的類和其父類聲明的)以及指向方法區中類數據的指針,指向方法表的指針,對象鎖(非必需),等待集合(非必需),GC相關的數據(非必需)(主要視GC演算法而定,如對於標記並清除演算法,需要標記對象是否被引用,以及是否已調用finalize()方法)。那麼為什麼Java對象中要有指向類數據的指針呢?我們從幾個方面來考慮首先:當程序中將一個對象引用轉為另一個類型時,如何檢查轉換是否允許?需用到類數據其次:動態綁定時,並不是需要引用類型,而是需要運行時類型,這里的迷惑是:為什麼類數據中保存的是實際類型,而非引用類型?這個問題先留下來,我想在後續的讀書筆記中應該能明白指向方法表的指針:這里和C++的VTBL是類似的,有利於提高方法調用的效率對象鎖:用來實現多個線程對共享數據的互斥訪問等待集合:用來讓多個線程為完成共同目標而協調功過。(注意Object類中的wait(),notify(),notifyAll()方法)。Java數組的堆實現:數組也擁有一個和他們的類相關聯的Class實例,具有相同dimension和type的數組是同一個類的實例。數組類名的表示:如[[LJava/lang/Object表示Object[][],[I表示int[],[[[B表示byte[][][]至此,堆已大致介紹完畢,下面來介紹程序計數器和Java棧程序計數器:為每個線程獨有,在線程啟動時創建,若thread執行Java方法,則PC保存下一條執行指令的地址。若thread執行native方法,則Pc的值為undefinedJava棧:Java棧以幀為單位保存線程的運行狀態,Java棧只有兩種操作,幀的壓棧和出棧。每個幀代表一個方法,Java方法有兩種返回方式,return和拋出異常,兩種方式都會導致該方法對應的幀出棧和釋放內存。幀的組成:局部變數區(包括方法參數和局部變數,對於instance方法,還要首先保存this類型,其中方法參數按照聲明順序嚴格放置,局部變數可以任意放置),操作數棧,幀數據區(用來幫助支持常量池的解析,正常方法返回和異常處理)。本地方法棧:依賴於本地方法的實現,如某個JVM實現的本地方法借口使用C連接模型,則本地方法棧就是C棧,可以說某線程在調用本地方法時,就進入了一個不受JVM限制的領域,也就是JVM可以利用本地方法來動態擴展本身。相信大家都明白JVM是什麼了吧。原文鏈接: http://www.cnblogs.com/chenzhao/archive/2011/08/14/2137713.html
㈨ Java JVM 中怎麼控制方法的遞歸調用。是在heap中的方法區,「重復」代碼段嗎
前面是我自己理解的後面是復制的
java有自動垃圾回收機制
當垃圾收集器判斷已經沒有任何引用指向對象的時候,會調用對象的finalize方法來釋放對象占據的內存空間~
java中垃圾回收以前聽老師講好像是內存滿了他才去做一次整體垃圾回收,在回收垃圾的同時會調用finalize方法.你在構造一個類時可以構造一個類時覆蓋他的finalize方法以便於該類在被垃圾回收時執行一些代碼,比如釋放資源.
1.JVM的gc概述
gc即垃圾收集機制是指jvm用於釋放那些不再使用的對象所佔用的內存。java語言並不要求jvm有gc,也沒有規定gc如何工作。不過常用的jvm都有gc,而且大多數gc都使用類似的演算法管理內存和執行收集操作。
在充分理解了垃圾收集演算法和執行過程後,才能有效的優化它的性能。有些垃圾收集專用於特殊的應用程序。比如,實時應用程序主要是為了避免垃圾收集中斷,而大多數OLTP應用程序則注重整體效率。理解了應用程序的工作負荷和jvm支持的垃圾收集演算法,便可以進行優化配置垃圾收集器。
垃圾收集的目的在於清除不再使用的對象。gc通過確定對象是否被活動對象引用來確定是否收集該對象。gc首先要判斷該對象是否是時候可以收集。兩種常用的方法是引用計數和對象引用遍歷。
1.1.引用計數
引用計數存儲對特定對象的所有引用數,也就是說,當應用程序創建引用以及引用超出范圍時,jvm必須適當增減引用數。當某對象的引用數為0時,便可以進行垃圾收集。
1.2.對象引用遍歷
早期的jvm使用引用計數,現在大多數jvm採用對象引用遍歷。對象引用遍歷從一組對象開始,沿著整個對象圖上的每條鏈接,遞歸確定可到達(reachable)的對象。如果某對象不能從這些根對象的一個(至少一個)到達,則將它作為垃圾收集。在對象遍歷階段,gc必須記住哪些對象可以到達,以便刪除不可到達的對象,這稱為標記(marking)對象。
下一步,gc要刪除不可到達的對象。刪除時,有些gc只是簡單的掃描堆棧,刪除未標記的未標記的對象,並釋放它們的內存以生成新的對象,這叫做清除(sweeping)。這種方法的問題在於內存會分成好多小段,而它們不足以用於新的對象,但是組合起來卻很大。因此,許多gc可以重新組織內存中的對象,並進行壓縮(compact),形成可利用的空間。
為此,gc需要停止其他的活動活動。這種方法意味著所有與應用程序相關的工作停止,只有gc運行。結果,在響應期間增減了許多混雜請求。另外,更復雜的 gc不斷增加或同時運行以減少或者清除應用程序的中斷。有的gc使用單線程完成這項工作,有的則採用多線程以增加效率。
2.幾種垃圾回收機制
2.1.標記-清除收集器
這種收集器首先遍歷對象圖並標記可到達的對象,然後掃描堆棧以尋找未標記對象並釋放它們的內存。這種收集器一般使用單線程工作並停止其他操作。
2.2.標記-壓縮收集器
有時也叫標記-清除-壓縮收集器,與標記-清除收集器有相同的標記階段。在第二階段,則把標記對象復制到堆棧的新域中以便壓縮堆棧。這種收集器也停止其他操作。
2.3.復制收集器
這種收集器將堆棧分為兩個域,常稱為半空間。每次僅使用一半的空間,jvm生成的新對象則放在另一半空間中。gc運行時,它把可到達對象復制到另一半空間,從而壓縮了堆棧。這種方法適用於短生存期的對象,持續復制長生存期的對象則導致效率降低。
2.4.增量收集器
增量收集器把堆棧分為多個域,每次僅從一個域收集垃圾。這會造成較小的應用程序中斷。
2.5.分代收集器
這種收集器把堆棧分為兩個或多個域,用以存放不同壽命的對象。jvm生成的新對象一般放在其中的某個域中。過一段時間,繼續存在的對象將獲得使用期並轉入更長壽命的域中。分代收集器對不同的域使用不同的演算法以優化性能。
2.6.並發收集器
並發收集器與應用程序同時運行。這些收集器在某點上(比如壓縮時)一般都不得不停止其他操作以完成特定的任務,但是因為其他應用程序可進行其他的後台操作,所以中斷其他處理的實際時間大大降低。
2.7.並行收集器
並行收集器使用某種傳統的演算法並使用多線程並行的執行它們的工作。在多cpu機器上使用多線程技術可以顯著的提高java應用程序的可擴展性。
3.Sun HotSpot
1.4.1 JVM堆大小的調整
Sun HotSpot 1.4.1使用分代收集器,它把堆分為三個主要的域:新域、舊域以及永久域。Jvm生成的所有新對象放在新域中。一旦對象經歷了一定數量的垃圾收集循環後,便獲得使用期並進入舊域。在永久域中jvm則存儲class和method對象。就配置而言,永久域是一個獨立域並且不認為是堆的一部分。
下面介紹如何控制這些域的大小。可使用-Xms和-Xmx 控制整個堆的原始大小或最大值。
下面的命令是把初始大小設置為128M:
java –Xms128m
–Xmx256m為控制新域的大小,可使用-XX:NewRatio設置新域在堆中所佔的比例。
下面的命令把整個堆設置成128m,新域比率設置成3,即新域與舊域比例為1:3,新域為堆的1/4或32M:
java –Xms128m –Xmx128m
–XX:NewRatio =3可使用-XX:NewSize和-XX:MaxNewsize設置新域的初始值和最大值。
下面的命令把新域的初始值和最大值設置成64m:
java –Xms256m –Xmx256m –Xmn64m
永久域默認大小為4m。運行程序時,jvm會調整永久域的大小以滿足需要。每次調整時,jvm會對堆進行一次完全的垃圾收集。
使用-XX:MaxPerSize標志來增加永久域搭大小。在WebLogic Server應用程序載入較多類時,經常需要增加永久域的最大值。當jvm載入類時,永久域中的對象急劇增加,從而使jvm不斷調整永久域大小。為了避免調整,可使用-XX:PerSize標志設置初始值。
下面把永久域初始值設置成32m,最大值設置成64m。
java -Xms512m -Xmx512m -Xmn128m -XX:PermSize=32m -XX:MaxPermSize=64m
默認狀態下,HotSpot在新域中使用復制收集器。該域一般分為三個部分。第一部分為Eden,用於生成新的對象。另兩部分稱為救助空間,當Eden 充滿時,收集器停止應用程序,把所有可到達對象復制到當前的from救助空間,一旦當前的from救助空間充滿,收集器則把可到達對象復制到當前的to救助空間。From和to救助空間互換角色。維持活動的對象將在救助空間不斷復制,直到它們獲得使用期並轉入舊域。使用-XX:SurvivorRatio 可控制新域子空間的大小。
同NewRation一樣,SurvivorRation規定某救助域與Eden空間的比值。比如,以下命令把新域設置成64m,Eden佔32m,每個救助域各佔16m:
java -Xms256m -Xmx256m -Xmn64m -XX:SurvivorRation =2
如前所述,默認狀態下HotSpot對新域使用復制收集器,對舊域使用標記-清除-壓縮收集器。在新域中使用復制收集器有很多意義,因為應用程序生成的大部分對象是短壽命的。理想狀態下,所有過渡對象在移出Eden空間時將被收集。如果能夠這樣的話,並且移出Eden空間的對象是長壽命的,那麼理論上可以立即把它們移進舊域,避免在救助空間反復復制。但是,應用程序不能適合這種理想狀態,因為它們有一小部分中長壽命的對象。最好是保持這些中長壽命的對象並放在新域中,因為復制小部分的對象總比壓縮舊域廉價。為控制新域中對象的復制,可用-XX:TargetSurvivorRatio控制救助空間的比例(該值是設置救助空間的使用比例。如救助空間位1M,該值50表示可用500K)。該值是一個百分比,默認值是50。當較大的堆棧使用較低的 sruvivorratio時,應增加該值到80至90,以更好利用救助空間。用-XX:maxtenuring threshold可控制上限。
為放置所有的復制全部發生以及希望對象從eden擴展到舊域,可以把MaxTenuring Threshold設置成0。設置完成後,實際上就不再使用救助空間了,因此應把SurvivorRatio設成最大值以最大化Eden空間,設置如下:
java … -XX:MaxTenuringThreshold=0 –XX:SurvivorRatio=50000 …
4.BEA JRockit JVM的使用
Bea WebLogic 8.1使用的新的JVM用於Intel平台。在Bea安裝完畢的目錄下可以看到有一個類似於jrockit81sp1_141_03的文件夾。這就是 Bea新JVM所在目錄。不同於HotSpot把Java位元組碼編譯成本地碼,它預先編譯成類。JRockit還提供了更細致的功能用以觀察JVM的運行狀態,主要是獨立的GUI控制台(只能適用於使用Jrockit才能使用jrockit81sp1_141_03自帶的console監控一些cpu及 memory參數)或者WebLogic Server控制台。
Bea JRockit JVM支持4種垃圾收集器:
4.1.1.分代復制收集器
它與默認的分代收集器工作策略類似。對象在新域中分配,即JRockit文檔中的nursery。這種收集器最適合單cpu機上小型堆操作。
4.1.2.單空間並發收集器
該收集器使用完整堆,並與背景線程共同工作。盡管這種收集器可以消除中斷,但是收集器需花費較長的時間尋找死對象,而且處理應用程序時收集器經常運行。如果處理器不能應付應用程序產生的垃圾,它會中斷應用程序並關閉收集。
分代並發收集器這種收集器在護理域使用排它復制收集器,在舊域中則使用並發收集器。由於它比單空間共同發生收集器中斷頻繁,因此它需要較少的內存,應用程序的運行效率也較高,注意,過小的護理域可以導致大量的臨時對象被擴展到舊域中。這會造成收集器超負荷運作,甚至採用排它性工作方式完成收集。
4.1.3.並行收集器
該收集器也停止其他進程的工作,但使用多線程以加速收集進程。盡管它比其他的收集器易於引起長時間的中斷,但一般能更好的利用內存,程序效率也較高。
默認狀態下,JRockit使用分代並發收集器。要改變收集器,可使用-Xgc:,對應四個收集器分別為 gen,singlecon,gencon以及parallel。可使用-Xms和-Xmx設置堆的初始大小和最大值。要設置護理域,則使用- Xns:java –jrockit –Xms512m –Xmx512m –Xgc:gencon –Xns128m…盡管JRockit支持-verbose:gc開關,但它輸出的信息會因收集器的不同而異。JRockit還支持memory、 load和codegen的輸出。
注意 :如果 使用JRockit JVM的話還可以使用WLS自帶的console(C:\bea\jrockit81sp1_141_03\bin下)來監控一些數據,如cpu, memery等。要想能構監控必須在啟動服務時startWeblogic.cmd中加入-Xmanagement參數。
5.如何從JVM中獲取信息來進行調整
-verbose.gc開關可顯示gc的操作內容。打開它,可以顯示最忙和最空閑收集行為發生的時間、收集前後的內存大小、收集需要的時間等。打開- xx:+ printgcdetails開關,可以詳細了解gc中的變化。打開-XX: + PrintGCTimeStamps開關,可以了解這些垃圾收集發生的時間,自jvm啟動以後以秒計量。最後,通過-xx: + PrintHeapAtGC開關了解堆的更詳細的信息。為了了解新域的情況,可以通過-XX:=PrintTenuringDistribution開關了解獲得使用期的對象權。
6.Pdm系統JVM調整
6.1.伺服器:前提內存1G 單CPU
可通過如下參數進行調整:-server 啟用伺服器模式(如果CPU多,伺服器機建議使用此項)
-Xms,-Xmx一般設為同樣大小。 800m
-Xmn 是將NewSize與MaxNewSize設為一致。320m
-XX:PerSize 64m
-XX:NewSize 320m 此值設大可調大新對象區,減少Full GC次數
-XX:MaxNewSize 320m
-XX:NewRato NewSize設了可不設。
-XX: SurvivorRatio
-XX:userParNewGC 可用來設置並行收集
-XX:ParallelGCThreads 可用來增加並行度
-XXUseParallelGC 設置後可以使用並行清除收集器
-XX:UseAdaptiveSizePolicy 與上面一個聯合使用效果更好,利用它可以自動優化新域大小以及救助空間比值
6.2.客戶機:通過在JNLP文件中設置參數來調整客戶端JVM
JNLP中參數:initial-heap-size和max-heap-size
這可以在framework的RequestManager中生成JNLP文件時加入上述參數,但是這些值是要求根據客戶機的硬體狀態變化的(如客戶機的內存大小等)。建議這兩個參數值設為客戶機可用內存的60%(有待測試)。為了在動態生成JNLP時以上兩個參數值能夠隨客戶機不同而不同,可靠慮獲得客戶機系統信息並將這些嵌到首頁index.jsp中作為連接請求的參數。
在設置了上述參數後可以通過Visualgc 來觀察垃圾回收的一些參數狀態,再做相應的調整來改善性能。一般的標準是減少fullgc的次數,最好硬體支持使用並行垃圾回收(要求多CPU)。
㈩ JVM是如何工作的呢
Java虛擬機
一、什麼是Java虛擬機
Java虛擬機是一個想像中的機器,在實際的計算機上通過軟體模擬來實現。Java虛擬機有自己想像中的硬體,如處理器、堆棧、寄存器等,還具有相應的指令系統。
1.為什麼要使用Java虛擬機
Java語言的一個非常重要的特點就是與平台的無關性。而使用Java虛擬機是實現這一特點的關鍵。一般的高級語言如果要在不同的平台上運行,至少需要編譯成不同的目標代碼。而引入Java語言虛擬機後,Java語言在不同平台上運行時不需要重新編譯。Java語言使用模式Java虛擬機屏蔽了與具體平台相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(位元組碼),就可以在多種平台上不加修改地運行。Java虛擬機在執行位元組碼時,把位元組碼解釋成具體平台上的機器指令執行。
2.誰需要了解Java虛擬機
Java虛擬機是Java語言底層實現的基礎,對Java語言感興趣的人都應對Java虛擬機有個大概的了解。這有助於理解Java語言的一些性質,也有助於使用Java語言。對於要在特定平台上實現Java虛擬機的軟體人員,Java語言的編譯器作者以及要用硬體晶元實現Java虛擬機的人來說,則必須深刻理解Java虛擬機的規范。另外,如果你想擴展Java語言,或是把其它語言編譯成Java語言的位元組碼,你也需要深入地了解Java虛擬機。
3.Java虛擬機支持的數據類型
Java虛擬機支持Java語言的基本數據類型如下:
byte://1位元組有符號整數的補碼
short://2位元組有符號整數的補碼
int://4位元組有符號整數的補碼
long://8位元組有符號整數的補碼
float://4位元組IEEE754單精度浮點數
double://8位元組IEEE754雙精度浮點數
char://2位元組無符號Unicode字元
幾乎所有的Java類型檢查都是在編譯時完成的。上面列出的原始數據類型的數據在Java執行時不需要用硬體標記。*作這些原始數據類型數據的位元組碼(指令)本身就已經指出了*作數的數據類型,例如iadd、ladd、fadd和dadd指令都是把兩個數相加,其*作數類型別是int、long、 float和double。虛擬機沒有給boolean(布爾)類型設置單獨的指令。boolean型的數據是由integer指令,包括integer 返回來處理的。boolean型的數組則是用byte數組來處理的。虛擬機使用IEEE754格式的浮點數。不支持IEEE格式的較舊的計算機,在運行 Java數值計算程序時,可能會非常慢。
虛擬機支持的其它數據類型包括:
object//對一個Javaobject(對象)的4位元組引用
returnAddress//4位元組,用於jsr/ret/jsr-w/ret-w指令
注:Java數組被當作object處理。
虛擬機的規范對於object內部的結構沒有任何特殊的要求。在Sun公司的實現中,對object的引用是一個句柄,其中包含一對指針:一個指針指向該object的方法表,另一個指向該object的數據。用Java虛擬機的位元組碼表示的程序應該遵守類型規定。Java虛擬機的實現應拒絕執行違反了類型規定的位元組碼程序。Java虛擬機由於位元組碼定義的限制似乎只能運行於32位地址空間的機器上。但是可以創建一個Java虛擬機,它自動地把位元組碼轉換成64位的形式。從Java虛擬機支持的數據類型可以看出,Java對數據類型的內部格式進行了嚴格規定,這樣使得各種Java虛擬機的實現對數據的解釋是相同的,從而保證了Java的與平台無關性和可
移植性。
二、Java虛擬機體系結構
Java虛擬機由五個部分組成:一組指令集、一組寄存器、一個棧、一個無用單元收集堆(Garbage-collected-heap)、一個方法區域。這五部分是Java虛擬機的邏輯成份,不依賴任何實現技術或組織方式,但它們的功能必須在真實機器上以某種方式實現。
1.Java指令集
Java虛擬機支持大約248個位元組碼。每個位元組碼執行一種基本的CPU運算,例如,把一個整數加到寄存器,子程序轉移等。Java指令集相當於Java程序的匯編語言。
Java指令集中的指令包含一個單位元組的*作符,用於指定要執行的*作,還有0個或多個*作數,提供*作所需的參數或數據。許多指令沒有*作數,僅由一個單位元組的*作符構成。 虛擬機的內層循環的執行過程如下:
do{
取一個*作符位元組;
根據*作符的值執行一個動作;
}while(程序未結束)
由於指令系統的簡單性,使得虛擬機執行的過程十分簡單,從而有利於提高執行的效率。指令中*作數的數量和大小是由*作符決定的。如果*作數比一個位元組大,那麼它存儲的順序是高位位元組優先。例如,一個16位的參數存放時佔用兩個位元組,其值為:
第一個位元組*256+第二個位元組位元組碼指令流一般只是位元組對齊的。指令tableswitch和lookup是例外,在這兩條指令內部要求強制的4位元組邊界對齊。
2.寄存器
Java虛擬機的寄存器用於保存機器的運行狀態,與微處理器中的某些專用寄存器類似。
Java虛擬機的寄存器有四種:
pc:Java程序計數器。
optop:指向*作數棧頂端的指針。
frame:指向當前執行方法的執行環境的指針。
vars:指向當前執行方法的局部變數區第一個變數的指針。
Java虛擬機
Java虛擬機是棧式的,它不定義或使用寄存器來傳遞或接受參數,其目的是為了保證指令集的簡潔性和實現時的高效性(特別是對於寄存器數目不多的處理器)。
所有寄存器都是32位的。
3.棧
Java虛擬機的棧有三個區域:局部變數區、運行環境區、*作數區。
(1)局部變數區 每個Java方法使用一個固定大小的局部變數集。它們按照與vars寄存器的字偏移量來定址。局部變數都是32位的。長整數和雙精度浮點數占據了兩個局部變數的空間,卻按照第一個局部變數的索引來定址。(例如,一個具有索引n的局部變數,如果是一個雙精度浮點數,那麼它實際占據了索引n和n+1所代表的存儲空間。)虛擬機規范並不要求在局部變數中的64位的值是64位對齊的。虛擬機提供了把局部變數中的值裝載到*作數棧的指令, 也提供了把*作數棧中的值寫入局部變數的指令。
(2)運行環境區 在運行環境中包含的信息用於動態鏈接,正常的方法返回以及異常傳播。
·動態鏈接
運行環境包括對指向當前類和當前方法的解釋器符號表的指針,用於支持方法代碼的動態鏈接。方法的class文件代碼在引用要調用的方法和要訪問的變數時使用符號。動態鏈接把符號形式的方法調用翻譯成實際方法調用,裝載必要的類以解釋還沒有定義的符號,並把變數訪問翻譯成與這些變數運行時的存儲結構相應的偏移地址。動態鏈接方法和變數使得方法中使用的其它類的變化不會影響到本程序的代碼。
·正常的方法返回
如果當前方法正常地結束了,在執行了一條具有正確類型的返回指令時,調用的方法會得到一個返回值。執行環境在正常返回的情況下用於恢復調用者的寄存器,並把調用者的程序計數器增加一個恰當的數值,以跳過已執行過的方法調用指令,然後在調用者的執行環境中繼續執行下去。
·異常和錯誤傳播
異常情況在Java中被稱作Error(錯誤)或Exception(異常),是Throwable類的子類,在程序中的原因是:①動態鏈接錯,如無法找到所需的class文件。②運行時錯,如對一個空指針的引用
·程序使用了throw語句。
當異常發生時,Java虛擬機採取如下措施:
·檢查與當前方法相聯系的catch子句表。每個catch子句包含其有效指令范圍,能夠處理的異常類型,以及處理異常的代碼塊地址。
·與異常相匹配的catch子句應該符合下面的條件:造成異常的指令在其指令范圍之內,發生的異常類型是其能處理的異常類型的子類型。如果找到了匹配的catch子句,那麼系統轉移到指定的異常處理塊處執行;如果沒有找到異常處理塊,重復尋找匹配的catch子句的過程,直到當前方法的所有嵌套的 catch子句都被檢查過。
·由於虛擬機從第一個匹配的catch子句處繼續執行,所以catch子句表中的順序是很重要的。因為Java代碼是結構化的,因此總可以把某個方法的所有的異常處理器都按序排列到一個表中,對任意可能的程序計數器的值,都可以用線性的順序找到合適的異常處理塊,以處理在該程序計數器值下發生的異常情況。
·如果找不到匹配的catch子句,那麼當前方法得到一個"未截獲異常"的結果並返回到當前方法的調用者,好像異常剛剛在其調用者中發生一樣。如果在調用者中仍然沒有找到相應的異常處理塊,那麼這種錯誤傳播將被繼續下去。如果錯誤被傳播到最頂層,那麼系統將調用一個預設的異常處理塊。
(3)*作數棧區 機器指令只從*作數棧中取*作數,對它們進行*作,並把結果返回到棧中。選擇棧結構的原因是:在只有少量寄存器或非通用寄存器的機器(如Intel486)上,也能夠高效地模擬虛擬機的行為。*作數棧是32位的。它用於給方法傳遞參數,並從方法接收結果,也用於支持*作的參數,並保存*作的結果。例如,iadd指令將兩個整數相加。相加的兩個整數應該是*作數棧頂的兩個字。這兩個字是由先前的指令壓進堆棧的。這兩個整數將從堆棧彈出、相加,並把結果壓回到*作數棧中。
每個原始數據類型都有專門的指令對它們進行必須的*作。每個*作數在棧中需要一個存儲位置,除了long和double型,它們需要兩個位置。* 作數只能被適用於其類型的*作符所*作。例如,壓入兩個int類型的數,如果把它們當作是一個long類型的數則是非法的。在Sun的虛擬機實現中,這個限制由位元組碼驗證器強制實行。但是,有少數*作(*作符pe和swap),用於對運行時數據區進行*作時是不考慮類型的。
4.無用單元收集堆
Java的堆是一個運行時數據區,類的實例(對象)從中分配空間。Java語言具有無用單元收集能力:它不給程序員顯式釋放對象的能力。Java不規定具體使用的無用單元收集演算法,可以根據系統的需求使用各種各樣的演算法。
5.方法區
方法區與傳統語言中的編譯後代碼或是Unix進程中的正文段類似。它保存方法代碼(編譯後的java代碼)和符號表。在當前的Java實現中,方法代碼不包括在無用單元收集堆中,但計劃在將來的版本中實現。每個類文件包含了一個Java類或一個Java界面的編譯後的代碼。可以說類文件是Java 語言的執行代碼文件。為了保證類文件的平台無關性,Java虛擬機規范中對類文件的格式也作了詳細的說明。其具體細節請參考Sun公司的Java虛擬機規范。
內容來源於網上。