配色: 字号:
java加载机制整理
2016-11-04 | 阅:  转:  |  分享 
  
java加载机制整理

本文是根据李刚的《疯狂讲义》作的笔记,程序有的地方做了修改,特别是路径,一直在混淆,浪费了好多时间!!希望懂的同学能够指导本人,感激尽。。。。。。。。。。。。



1.jvm和类的关系



当调用java命令运行一个java程序时,必会启动一个jvm即java虚拟机。(5.6.处有联系!!)

该java程序的所有线程,变量都处于jvm中,都使用该jvm的内存区



jvm终止的情况:

1.程序自然运行结束

2.遇到System.exit();Runtime.getRuntime.exit();

3.遇到未捕获异常或错误时

4.程序所在的平台强制结束了JVM进程

jvm终止,jvm内存中的数据全部丢失。



举例:



定义一个类,包含静态变量



classAClass{

publicstaticinta=1;

}

使变量自加



复制代码

publicclassATest{

publicstaticvoidmain(String[]args){

AClass.a++;

System.out.println("a的值:"+AClass.a);//输出:a的值:2

}



}

复制代码

另起一类,查看变量变化情况



publicclassATest2{

publicstaticvoidmain(String[]args){

System.out.println("a的值:"+AClass.a);//输出:a的值:1

}



}

从输出对比可知,虽然ATest1自加,改变的类变量a的值,但是ATest1与ATest2处于不用的JVM中,当ATest1结束时,对a的修改都丢失了



2.类的加载



类的加载又称为类的初始化,实际上可细分为类的加载、连接、初始化。下面将讲述着三个阶段的过程!



类的加载指.class文件读入内存,并为之创建一个java.lang.Class对象



类加载,是通过类加载器来完成的,类加载器通常由JVM提供,通常称为系统类加载器(也可以是自己写的加载器,只要继承ClassLoader基类)。



类加载无须等到“首次使用该类”时加载,jvm允许预加载某些类。。。。



加载来源:



1.本地.class文件

2.jar包的.class文件

3.网络.class文件

4.把一个java源文件动态编译,加载



3.类的连接



负责把类的二进制数据合并到JRE(java运行环境)中

1.验证检测被加载的类是否有正确的内部结构,并和其他类协调一致

2.准备负责为类的类变量(非对象变量)分配内存,并设置默认初始值

3.解析将类的二进制数据中的符号引用替换成直接引用。。(staticfinal好像跟这个有点关系????5.6.处有联系)



4.类初始化

主要对类变量(而非对象变量)的初始化

声明类变量的初始值=静态初始化块他们是相同的,等效的。都会被当成类的初始化语句,JVM会按照这些语句在程序中的顺序依次执行他们



复制代码

publicclassTest{

staticinta=5;//初始化时赋值

staticintb;//初始化时赋值--静态块

staticintc;//连接时赋默认值值

static{

b=6;

}

publicstaticvoidmain(String[]args){

System.out.println(a);

System.out.println(b);

System.out.println(c);



}

复制代码

输出:





JVM初始化一个类包含如下几个步骤:

1.假设类还没有被加载和连接,那么先加载和连接该类

2.假设该类的父类还没被初始化,那么先初始化父类----jvm总是最先初始化java.lang.Object类

3.假设类中有初始化语句,则一次执行这些初始化语句



当程序主动使用任何一个类时,系统会保证该类以及所有父类(直接父类和间接父类)都会被初始化



5.类初始化的时机:





1.创建类的实例。new,反射,反序列化

2.使用某类的类方法--静态方法

3.访问某类的类变量,或赋值类变量

4.反射创建某类或接口的Class对象。Class.forName("Hello");---注意:loadClass调用ClassLoader.loadClass(name,false)方法,没有link,自然没有initialize

5.初始化某类的子类

6.直接使用java.exe来运行某个主类。即cmdjava程序会先初始化该类。







复制代码

classTester{

publicstaticintvalue=10;

publicstaticStringname;



publicstaticvoidmethod(){

System.out.println("一个类方法");

}



static{

System.out.println("Tester的类的静态初始化。。。");

}

}



publicclassClassLoadTest{



staticStringclassPath="Chapter18.Tester";



publicstaticvoidmain(String[]args)throwsClassNotFoundException,

Exception,IllegalAccessException{

ClassLoadercl=ClassLoader.getSystemClassLoader();

Classa=cl.loadClass(classPath);//用到了Tester类加载一个类,并不会导致一个类的初始化



/初始化的时机/



//调用newInstance()方法

a.newInstance();//输出:____________静态块加载

//使用Class.forName方法

Class.forName(classPath);//输出:____________静态块加载

//调用类变量

intvalue=Tester.value;

Tester.name="jason";//输出:____________静态块加载

//调用类方法

Tester.method();//输出:____________静态块加载一个类方法

//new实例化

Testt=newTest();//输出:____________静态块加载

}

}

复制代码







特殊情形:final类型的类变量,如果在编译时(转成.class文件)就可以确定,那么这个类变量就相当于“宏变量”,编译时,直接替换成值。

所以,即使使用这个类变量,程序也不会导致该类的初始化!!----相当于直接使用常量



复制代码

publicclassTest{



publicstaticvoidmain(String[]args){

System.out.println(aClass.A);

}

}

classaClass{

staticfinalintA=111;//可以确定

static{

System.out.println("静态块初始化");

}

}

复制代码

输出:111



复制代码

publicclassTest{



publicstaticvoidmain(String[]args){

System.out.println(aClass.A);

}

}

classaClass{

staticfinallongA=System.currentTimeMillis();//在编译时无法确定

static{

System.out.println("静态块初始化");

}

}

复制代码

输出:



静态块初始化

1468309845203





使用ClassLoader类的loadClass方法来加载类时,只是加载该类,而不会执行该类的初始化!!使用Class的forName()静态方法,才会导致强制初始化该类。



6.类加载器



类加载器负责加载所有的类,为被加载如内存中的类生成一个java.lang.Class实例。一旦类被载入内存,同一个类就不会再加载第二次



如何判断是同一个类:

java中一个类用其全限定类名标示--包名+类名

jvm中一个类用其全限定类名+加载器标示---包名+类名+加载器名



加载器层次结构:

JVM启动时,姓曾的三个类加载器组成的机构

1.BootstrapClassLoader根类------引导类加载器,加载java核心类。非java.lang.ClassLoader子类,而是JVM自身实现

2.ExtensionClassLoader扩展类-----加载JRE的扩展目录中的JAR包的类(%JAVA_HOME%/jre/lib/ext或java.ext.dirs系统属性指定的目录)

3.SystemClassLoader系统类-----加载cmdjava-cp,环境变量指定的jar包和类路径。ClassLoader.getSystemClassLoader获得系统类加载器。

4.用户类加载器。。。



复制代码

publicclassBootstrapTest{

publicstaticvoidmain(String[]args){

//获取根类加载器所加载的全部URL数组

URL[]urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();//无需理会警告

//遍历输出根类加载器加载的全部URL

for(inti=0;i
System.out.println(urls[i].toExternalForm());

}

}

}

复制代码

输出:



file:/C:/Program%20Files/Java/jre1.8.0_20/lib/resources.jar

file:/C:/Program%20Files/Java/jre1.8.0_20/lib/rt.jar

file:/C:/Program%20Files/Java/jre1.8.0_20/lib/sunrsasign.jar

file:/C:/Program%20Files/Java/jre1.8.0_20/lib/jsse.jar

file:/C:/Program%20Files/Java/jre1.8.0_20/lib/jce.jar

file:/C:/Program%20Files/Java/jre1.8.0_20/lib/charsets.jar

file:/C:/Program%20Files/Java/jre1.8.0_20/lib/jfr.jar

file:/C:/Program%20Files/Java/jre1.8.0_20/classes







7.类的加载机制:

1.全盘负责。某类以及其所依赖的所有类,都由一个加载器负责加载。除非显示使用另外一个加载器。

2.父类委托。先父类加载器加载改Class,不行后,才尝试从自己的类路径中加载该类

3.缓存机制。缓存机制将会保证所有加载过的Class都会被缓存。。当程序需要Class时,先从缓存区中寻找Class对象,没有的话,才加载该类的.class对象。







8.访问类加载器



复制代码

publicstaticvoidmain(String[]args)throwsIOException{

ClassLoadersystemLoader=ClassLoader.getSystemClassLoader();//getsystemloader获得系统的类加载器



System.out.println("系统类加载器:"+systemLoader);

/

获取系统类加载器的加载路径--通常由CLASSPATH环境变量指定,

如果,操作系统没有指定CLASSPATH环境变量,则默认以当前路径作为系统类加载器的家在路径

/

Enumerationeml=systemLoader.getResources("");

while(eml.hasMorewww.baiyuewang.netElements()){

System.out.println("SYSTEMClassLoaderRoute:"+eml.nextElement());//系统类加载器的加载路径是程序运行的当前路径

}

//获取系统类加载器的父类加载器,得到扩展类加载器

ClassLoaderextenionLoader=systemLoader.getParent();

System.out.println("扩展类加载器:"+extenionLoader);

System.out.println("扩展类加载器的路径:"+System.getProperty("java.ext.dirs"));

ClassLoaderbaseLoader=extenionLoader.getParent();

System.out.println("扩展类加载器的父类:"+baseLoader);//TheparentClassloadisBootstrapClassLoader.BCisnotuseJavaLanguage...So..

//根加载器没有继承ClassLoader抽象类。所以,返回的是Null

//但实际上扩展类加载器的父类加载器是根类加载器,只是,根类加载器并不是java实现的。



}

复制代码

输出:



系统类加载器:sun.misc.Launcher$AppClassLoader@73d16e93

SYSTEMClassLoaderRoute:file:/D:/WorkSpace1/Java_Test/bin/

扩展类加载器:sun.misc.Launcher$ExtClassLoader@7f31245a

扩展类加载器的路径:C:\ProgramFiles\Java\jre1.8.0_20\lib\ext;C:\Windows\Sun\Java\lib\ext

扩展类加载器的父类:null







系统类加载器是AppClassLoader的实例,扩展类加载器是ExtClassLoader的实例,这两个类都是URLClassLoader的实例







9,类加载器加载Class大致要经过9个步骤:



1.检测此Class是否被载入过(即在缓存区中是否由此Class),有,则进入第8步,否则执行第2步。



2.如果父类加载器不存在(要么parent一定是根类加载器,要么本身就是根类加载器),则跳到第4步;如果父类加载器存在,则执行第3步。



3.请求使用父类加载器去载入目标类,如果成功则跳到第8步,否则执行第5步



4.请求使用根类加载器载入目标类,成功则跳到第8步,否则跳到第7步



5.当前类加载器尝试寻找Class文件(从与此ClassLoader相关的类路径中寻找),如果找到则执行第6步,否则跳到第7步。



6.从文件中载入Class,成功后跳到第8步。



7.抛出ClassNotFoundException异常。



8.返回对应的java.lang.Class对象。



其中第5,6步允许重写ClassLoader的findClass()方法来实现自己的载入策略,甚至重写loadClass()方法来实现自己的载入过程







10.一个自定义的ClassLoader



由于是用eclipse编写程序,所以.java和.class文件分别放于不同的文件夹。写此程序的时候要注意路径问题



本项目的包结构:



Java_Test|



|src|



|Chapter18|



|ClassloaderMineTest.java



|Test.java



项目路径:D:\WorkSpace1\Java_Test\bin;D:\WorkSpace1\Java_Test\src



复制代码

/

读取一个文件的内容,返回byte[]

@paramfilename

@return

@throwsIOException

/

privatebyte[]getBytes(Stringfilename)throwsIOException{

System.out.println("使用getBytes方法");

Filefile=newFile(filename);//路径bin/Chapter18/Test.class

longlen=file.www.wang027.comlength();

byte[]raw=newbyte[(int)len];



try(FileInputStreamfin=newFileInputStream(file)){

//一次读取Class文件的全部二进制数据

intr=fin.read(raw);

if(r!=len)thrownewIOException("无法读取全部文件:"+r+"!="+len);

returnraw;

}

}

复制代码

复制代码

/

编译指定JAVA文件,返回编译的结果

@paramjavaFile

@return

@throwsIOException

/

privatebooleancompile(StringjavaFile)throwsIOException{

System.out.println("myClassLoader:正在编译"+javaFile+"..........");

//调用系统的javac命令--指定了.class生成的路径,注意空格。此处为何不加上Chapter18呢?难道是java命名唯一性的缘故,包名+类名,已经知道了包名??

Processp=Runtime.getRuntime().exec("javac-dd:/WorkSpace1/Java_Test/bin/"+javaFile);

try{

//其他线程都等待这个线程完成

p.waitFor();



}catch(Exceptione){

//TODO:handleexception

System.out.println(e);

}

//获取javac线程的退出值

intret=p.exitValue();

//返回编译是否成功returnres==0;

}

复制代码

复制代码

/

重写的findClass方法

/

@Override

protectedClassfindClass(Stringname)throwsClassNotFoundException{//name=Test

Classclazz=null;

StringjavaFilename="src/Chapter18/"+fileStub+".java";

StringclassFilename="bin/Chapter18/"+fileStub+".class";





FilejavaFile=newFile(javaFilename);//相对路径,相对于项目的跟路径:D:\WorkSpace1\Java_Test

FileclassFile=newFile(classFilename);

//当指定Java源文件存在,且,Class文件不存在,或者Java源文件的修改时间比class文件的修改时间更晚时,重新编译if(javaFile.exists()&&(!classFile.exists()||javaFile.lastModified()>classFile.lastModified())){

try{

//如果编译失败,或者该class文件不存在

if(!compile(javaFilename)||!classFile.exists()){

thrownewClassNotFoundException("ClassNotFoundException:"+javaFilename+"or"+classFile+"isnotexists");

}

}catch(Exceptione){

//TODO:handleexception

e.printStackTrace();

}

}

//如果class文件存在,系统负责将该文件转换成class对象

if(classFile.exists()){

try{

//将class文件的二进制数据读入数组

byte[]raw=getBytes(classFilename);

//调用ClassLoader的defineClass方法将二进制数据转换成class对象

clazz=defineClass("Chapter18.Test",raw,0,raw.length);//路径Chapter18.Test

}catch(Exceptione){

//TODO:handleexception

e.printStackTrace();

}

}



//如果clazz为null,表明加载失败,则抛出异常

if(clazz==null){

thrownewClassNotFoundException(name);

}



returnclazz;

}

复制代码

复制代码

/

main方法

@paramargs

@throwsException

/

publicstaticvoidmain(String[]args)throwsException{



ClassloaderMineTestmc=newClassloaderMineTest();

Classclazz1=mc.findLoadedClass("Chapter18.Test");



Classclazz=mc.loadClass("Test",false);//路径Test?

Classclazz2=mc.findLoadedClass("Chapter18.Test");//路径Chapter18.Test??,为什么又和loadClass的路径不一样呢

//得到Class对象后就可以反射了

Methodmain=clazz.getMethod("main",(newString[0].getClass()));

String[]progArgs={"Chapter18.Test"};

ObjectargsArray[]={progArgs};

main.invoke(null,argsArray);

}

复制代码

11.URLClassLoader类



java为ClassLoader提供了一个实现类URLClassLoader,该类也是系统类加载器和扩展类加载器的父类



URLClassLoader的两个构造器



URLClassLoader(URL[]urls)



URLClassLoader(URL[]urls,ClassLoaderparent)



一旦获得URLClassLoader对象后,就可以调用对象的loadClass()方法来加载指定的类。



下面展示的程序示范了如何直接从文件系统中加载MySQL驱动,并且使用该驱动来获取数据库连接。。通过这种方式,可以无须将MySQL驱动添加到CLASSPATH环境变量中







复制代码

publicclassURLClassLoaderTest{

privatestaticConnectionconn;



privateStringurl;

privateStringdriver;



/

获得该用户下面的所有表

/

publicstaticvoidgetAllTableList(StringschemaName,DatabaseMetaDatadbMeta){

try{

//tabletype.Typicaltypesare"TABLE","VIEW","SYSTEMTABLE","GLOBALTEMPORARY","LOCALTEMPORARY","ALIAS","SYNONYM".

String[]types={"TABLE"};

ResultSetrs=dbMeta.getTables(null,schemaName,"%",types);

while(rs.next()){

StringtableName=rs.getString("TABLE_NAME");//表名

StringtableType=rs.getString("TABLE_TYPE");//表类型

Stringremarks=rs.getString("REMARKS");//表备注

System.out.println(tableName+"-"+tableType+"-"+remarks);

}

}catch(SQLExceptione){

e.printStackTrace();

}

}



//定义一个获取数据库连接的方法

publicstaticConnectiongetConn(Stringurl,Stringuser,Stringpass)throwsException{

if(conn==null){



//file表名是从本地文件系统加载,

//http:为前缀,表明从互联网通过HTTP来访问

//注意URL路径

//URLClassLoaderloader=newURLClassLoader(newURL[]{newURL("file:D:/WorkSpaceMyEclipse/Meeting_Hall/WebRoot/WEB-INF/classes/")});

URL[]urls={newURL("file:mysql-connector-java-5.1.30-bin.jar")};//从本地加载,jar包由外界导入,或者是放在项目的跟路径D:\WorkSpace1\Java_Test



//以默认的ClassLoader作为父ClassLoader,创建URLClassLoader

URLClassLoadermyClassLoader=newURLClassLoader(urls);

//加载MYSQL的JDBC驱动,并创建默认实例-------与普通的通过new生成实例的方法对比

Driverdriver=(Driver)myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance();

//创建一个设置JDBC连接属性的Properties对象

Propertiesprops=newProperties();



//至少需要为改对象传入user和password两个属性

props.setProperty("user",user);

props.setProperty("password",pass);

//调用Driver对象的connect方法来取得数据库连接

conn=driver.connect(url,props);

}

returnconn;

}

publicstaticvoidmain(String[]args)throwsException{

getConn("jdbc:mysql://localhost:3306/user_login","root","root");

DatabaseMetaDatadbMeta=conn.getMetaData();

//获取表中索引信息

getAllTableList(null,dbMeta);

}



}

复制代码

输出:



affair-TABLE-

comment-TABLE-

sort-TABLE-

user-TABLE-



复制代码

/

普通的数据库连接方法

@return

/

publicstaticConnectiongetDBConnection(){

try{

//加载数据库

Class.forName("com.mysql.jdbc.Driver");

Stringurl="jdbc:mysql://localhost:3306/user_login";

Stringuser="root";

Stringpassword="root";

//连接

conn=DriverManager.getConnection(url,user,password);

}catch(Exceptione){

e.printStackTrace();

}



returnconn;

}

复制代码

献花(0)
+1
(本文系thedust79首藏)