當調用Java命令運行某個Java程序時,該命令將會啓動一個Java虛擬機進程。無論Java程序多麼複雜,啓動多少個線程,它們都處於該Java虛擬機進程裏,都是使用同一個Java進程內存區。java
JVM程序終止的方式:mysql
JVM進程結束,該進程所在內存中的狀態將會丟失sql
當程序主動使用某個類時,若是該類還未被加載到內存中,則系統會經過加載、鏈接、初始化三個步驟來對該類進行初始化。數據庫
類的加載時將該類的class文件讀入內存,併爲之建立一個java.lang.Class對象,也就是說,當程序使用任何類時,系統都會爲之創建一個java.lang.Class對象。編程
系統中全部的類實際上也是實例,它們都是java.lang.Class的實例api
類的加載經過JVM提供的類加載器完成,類加載器時程序運行的基礎,JVM提供的類加載器被稱爲系統類加載器。除此以外,開發者能夠經過繼承ClassLoader基類來建立本身的類加載器。數組
經過使用不一樣的類加載器,能夠從不一樣來源加載類的二進制數據,一般有以下幾種來源。緩存
類加載器一般無需等到首次使用該類時才加載該類,Java虛擬機規範容許系統預先加載某些類。安全
1.3 類的鏈接網絡
當類被加載後,系統會爲之生成一個對應的Class對象,接着會進入鏈接階段,鏈接階段負責把類的二進制數據合併到JRE中。類的連接可分爲以下三個階段。
1.4 類的初始化
再累溫馨化階段,虛擬機負責對類進行初始化,主要就是對類變量進行初始化。在Java類中對類變量指定初始值有兩種方式:①聲明類變量時指定初始值;②使用靜態初始化塊爲類變量指定初始值。
JVM初始化一個類包含以下步驟
當執行第2步時,系統對直接父類的初始化也遵循1~3,以此類推
當Java程序首次經過下面6種方式使用某個類或接口時,系統會初始化該類或接口
二. 類加載器
2.1類加載器介紹
類加載器負責將.class文件加載到內存中,併爲之生成對應的java.lang.Class對象。
一個載入JVM的類有一個惟一的標識。在Java中,一個類使用全限定類名(包括包名和類名)做爲標識;但在JVM中,一個類使用全限定類名和其類加載器做爲惟一標識。
當JVM啓動時,會造成由三個類加載器組成的初始類加載器層次結構
Bootrap ClassLoader被稱爲引導(也稱爲原始或跟)類加載器,它負責加載Java的核心類。跟類加載器不是java.lang.ClassLoader的子類,而是JVM自身實現的。
Extension ClassLoader負責加載JRE拓展目錄中的JAR包的類,它的父類加載器是跟類加載器
System ClassLoader,它負責在JVM啓動時加載來自Java命令的-classpath選項、java.class,path系統屬性,或CLASSPATH指定的jar包和類歷經。系統可經過ClassLoader的靜態方法或區該系統類加載器。若是沒有特別指定,則用戶自定義的類加載器都已類加載器做爲父加載器
JVM類加載機制主要有三種
類加載器加載Class大體通過8個步驟
其中,第五、6步容許重寫ClassLoader的findClass()方法來實現本身的載入策略,甚至重寫loadClass()方法來實現本身的載入過程。
JVM除跟類加載器以外的全部類加載器都是ClassLoader子類的實例,開發者能夠經過拓展ClassLoader的子類,並重寫該ClassLoader所包含的方法實現自定義的類加載器。ClassLoader有以下兩個關鍵方法。
若是須要是實現自定義的ClassLoader,則能夠經過重寫以上兩個方法來實現,一般推薦重寫findClass()方法而不是loadClass()方法。
classLoader()方法的執行步驟:
從上面看出,重寫findClass()方法能夠避免覆蓋默認類加載器的父類委託,緩衝機制兩種策略;若是重寫loadClass()方法,則實現邏輯更爲複雜。
ClassLoader的一些方法:
下面程序開發了一個自定義的ClassLoader。該classLoader經過重寫findClass()方法來實現自定義的類加載機制。這個ClassLoader能夠在加載類以前先編譯該類的源文件,從而實現運行Java以前先編譯該程序的目標,這樣便可經過該classLoader運行Java源文件。
package com.gdut.basic; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method; public class CompileClassLoader extends ClassLoader { private byte[] getBytes(String fileName) { File file = new File(fileName); Long len = file.length(); byte[] raw = new byte[(int)len]; FileInputStream fin = new FileInputStream(file); //一次讀取class文件的二進制數據 int r = fin.read(raw); if(r != len) { throw new IOException("沒法讀取文件"+r+"!="+raw); return null; } } private boolean compile(String javaFile) throws IOException { System.out.println("正在編譯"+javaFile+"..."); Process p = Runtime.getRuntime().exec("javac"+javaFile); try { //其餘線程都等待這線程完成 p.waitFor(); }catch(InterruptedException ie) { System.out.println(ie); } int ret = p.exitValue(); return ret == 0; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = null; String findStub = name.replace(".", "/"); String javaFileName = findStub+".java"; String classFileName = findStub+".class"; File javaFile = new File(javaFileName); File classFile = new File(classFileName); //但指定Java源文件存在,class文件不存在,或者Java源文件的修改時間比class文件修改的時間更晚時,從新編譯 if(javaFile.exists() && classFile.exists() || javaFile.lastModified() > classFile.lastModified()) { try { if(!compile(javaFileName)|| !classFile.exists()) { throw new ClassNotFoundException("ClassNotFoundExcetion"+javaFileName); } }catch(IOException ie) { ie.printStackTrace(); } } if(classFile.exists()) { byte[] raw = getBytes(classFileName); clazz = defineClass(name,raw,0,raw.length); } //若是clazz爲null,代表加載失敗,則拋出異常 if(clazz == null) { throw new ClassNotFoundException(name); } return clazz; } public static void main(String[] args) throws Exception { //若是運行該程序時沒有參數,即沒有目標類 if (args.length<1) { System.out.println("缺乏目標類,請按以下格式運行Java源文件:"); System.out.println("java CompileClassLoader ClassName"); } //第一個參數是須要運行的類 String progClass = args[0]; //剩下的參數將做爲運行目標類時的參數,將這些參數複製到一個新數組中 String[] progArgs = new String[args.length - 1]; System.arraycopy(args, 1,progArgs,0, progArgs.length); CompileClassLoader ccl = new CompileClassLoader(); //加載須要運行的類 Class<?> clazz = ccl.loadClass(progClass); //獲取運行時的類的主方法 Method main = clazz.getMethod("main", (new String[0]).getClass()); Object argsArray[] = {progArgs}; main.invoke(null, argsArray); } }
接下來能夠提供任意一個簡單的主類,該主類無需編譯就可使用上面的CompileClassLoader來運行他
package com.gdut.basic; public class Hello { public static void main(String[] args) { for(String arg:args) { System.out.println("運行Hello的參數:"+arg); } } }
無需編譯該Hello.java,能夠直接運行下面命令來運行該Hello.java程序
java CompileClassLoader hello 瘋狂Java講義
運行結果以下:
CompileClassLoader:正常編譯 Hello.java...
運行hello的參數:瘋狂Java講義
使用自定義的類加載器,能夠實現以下功能
2.4 URLClassLoader類
該類時系統類加載器和拓展類加載器的父類(此處的父類,是指類與類之間的的繼承關係)。URLClassLoader功能比較強大,它能夠從本地文件系統獲取二進制文件來加載類,也能夠從遠程主機獲取二進制文件加載類。
該類提供兩個構造器
下面程序示範瞭如何從文件系統中加載MySQL驅動,並使用該驅動獲取數據庫鏈接。經過這種方式來獲取數據庫鏈接,無需將MySQL驅動添加到CLASSPATH中。
package java.gdut; import java.net.URL; import java.net.URLClassLoader; import java.sql.Connection; import java.sql.Driver; import java.util.Properties; public class URLClassLoaderTest { private static Connection conn; public static Connection getConn(String url,String user,String pass)throws Exception{ if(conn == null){ URL[] urls = {new URL("file:mysql-connection-java-5.1.46-bin.jar")}; URLClassLoader myClassLoader = new URLClassLoader(urls); //加載MySQL,並建立實例 Driver driver = (Driver)myClassLoader.loadClass("com.mysql.jdbc.Driveer").newInstance(); Properties properties = new Properties(); properties.setProperty("user",user); properties.setProperty("pass",pass); //調用driver的connect方法來取得數據庫鏈接 conn = driver.connect(url,properties); } return conn; } public static void main(String[] args) throws Exception { System.out.println(getConn("jdbc:mysql://localhost:3306/tb_test","sherman","a123")); } }
本程序類加載器的加載路徑是當前路徑下的mysql-connection-java-5.1.46-bin.jar文件,將MySQL驅動複製到該路徑下,這樣保證ClassLoader能夠正常加載到驅動類
Java程序中的許多對象在運行時都會出現收到外部傳入的一個對象,該對象編譯時類型是Object,但程序又須要調用該對象運行時的方法。
每一個類被加載後,系統會爲該類生成一個對應的Class對象,經過該Class對象能夠訪問到JVM中的這個類。得到Class對象一般三種方式
對於第一種方式,第二種的優點:
Class類提供了大量的實例方法獲取該Class對象所對應類的詳細信息
下面4個方法用於獲取Class對象對應類的構造器
下面四個方法獲取Class對象對應類所包含方法。
下面四個方法獲取Class對象對應類所包含的成員變量。
以下幾個方法用於訪問Class對應類的上所包含的Annotation.
以下方法用於訪問Class對應類的內部類
以下方法用於訪問Class對應類的所在的外部類
以下方法用於訪問Class對應類的所實現的接口
以下方法用於訪問Class對應類的所繼承的父類
以下方法用於訪問Class對應類的修飾符,所在包,類名等基本信息
如下幾個方法來判斷該類是否爲接口、枚舉、註解類型
以上getMethod()方法和getConStructor()方法中,都須要傳入多個類型爲Class<?>的參數,用於獲取指定的方法和構造器。要肯定一個方法應該由方法名和形參列表肯定。例以下面代碼獲取clazz對應類的帶一個String參數的info方法:
clazz.getMethods("info",String.class)
若要獲取clazz對應類的帶一個String參數,一個Integer參數的info方法
clazz.getMethods("info",String.class,Integer.class)
Java 8新增了一個Executable抽象基類,該對象表明可執行的類成員,該類派生了Constructor和Method兩個子類。
Executable抽象基類提供了大量方法來獲取修飾該方法或構造器的註解信息;還提供了is VarArgs()方法用於判斷該方法或構造器是否包含數量可變的形參,以及經過getModifiers()方法獲取該方法或構造器的修飾符。除此以外,還提供以下兩個方法
Parameter類是Java 8新增的api,提供了大量方法來獲取聲明該方法或參數個數的泛型信息,還提供了以下方法獲取參數信息
須要指出的是,使用javac命令編譯Java源文件時,默認生成的class文件並不包含方法的形參名信息,所以調用isNamePresent()將返回false,調用getName()也不能獲得該參數的形參名。須要編譯時保留形參信息,則須要該命令指定-parameter選項。
下面示範了Java 8的參數反射功能
public class MethodParameterTest { public static void main(String[] args) throws Exception { Class<Test> clazz = Test.class; Method replace = clazz.getMethod("replace",String.class,List.class); System.out.println("replace方法的參數個數爲:"+replace.getParameterCount()); Parameter[] parameters = replace.getParameters(); int index = 1; for(Parameter parameter:parameters){ if(!parameter.isNamePresent()){ System.out.println("-----第"+index+"行的參數信息-----"); System.out.println("參數名:"+parameter.getName()); System.out.println("形參類型:"+parameter.getType()); System.out.println("泛型類型:"+parameter.getParameterizedType()); } } } }
Class對象能夠得到該類的方法,構造器,成員變量。程序能夠經過Method對象來執行對應的方法,經過ConStructor對象調用對應的構造器建立實例,能經過Field對象直接訪問並修改對象的成員變量值。
經過反射生成對象有兩種方式。
能夠經過Class對象的getMethods()方法和getMethod()方法來獲取所有方法和指定方法。
每一個Method對象對應一個方法,能夠經過它調用對應的方法,在Method裏包含一個invoke()方法,該方法的簽名以下。
下面程序是對象池工廠增強版,它容許在配置文件中增長配置對象的成員變量的值,對象池工廠會讀取爲該對象配置的成員變量值,並利用該對象的Setter方法設置成員變量的值。
package com.gdut.test0516; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Properties; public class ExtendedObjectPoolFactory { //定義一個對象池,前面是對象名,後面是實際對象 private Map<String,Object> objectPool = new HashMap<>(); private Properties config = new Properties(); public void init(String fileName) { try(FileInputStream fis = new FileInputStream(fileName)) { config.load(fis); }catch(IOException ex){ System.out.println("讀取"+fileName+"異常"); } } private Object createObject(String clazzName)throws ClassNotFoundException, InstantiationException,IllegalAccessException{ Class<?> clazz = Class.forName(clazzName); //使用clazz默認構造器建立實例 return clazz.newInstance(); } public void initPool()throws ClassNotFoundException, InstantiationException,IllegalAccessException{ for (String name:config.stringPropertyNames()) { //沒取出一個key-value對。若是key中不包含百分號(%),便可認爲該key用於 // 控制調用對象的setter方法設置值,%前半爲對象名字,後半控制setter方法名 if( !name.contains("%")){ objectPool.put(name,createObject(config.getProperty(name))); } } } public Object getObject(String name){ return objectPool.get(name); } public void initProperty()throws NoSuchMethodException, IllegalAccessException,InvocationTargetException { for (String name:config.stringPropertyNames()) { if(name.contains("%")){ String[] objAndProp = name.split("%"); Object target = getObject(objAndProp[0]); String mtdName = "set"+objAndProp[1].substring(1); Class<?> targetClass = target.getClass(); Method mtd = targetClass.getMethod(mtdName); mtd.invoke(target,config.getProperty(name)); } } } public static void main(String[] args)throws Exception { ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory(); epf.init("com/gdut/test0516/extObj.txt"); epf.initPool(); epf.initProperty(); System.out.println(epf.getObject("a")); } }
經過Class對象的getFields()方法和getField()方法能夠獲取該類包含的全部成員變量和指定成員變量。Field提供以下方法讀取或設置成員變量值
3.4.4 操做數組
在java.lang.reflect包下還提供了一個Array類,Array對象能夠表明全部的數組。程序能夠經過使用該類來建立數組,操做數組元素等。
Array提供以下方法