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内存中的数据全部丢失。
举例:
定义一个类,包含静态变量
class AClass{ public static int a = 1; }
使变量自加
![](http://common.cnblogs.com/images/copycode.gif)
public class ATest { public static void main(String[] args) { AClass a = new AClass(); a.a++; System.out.println("a的值:"+a.a);//输出:a的值:2 } }
![](http://common.cnblogs.com/images/copycode.gif)
另起一类,查看变量变化情况
![](http://common.cnblogs.com/images/copycode.gif)
public class ATest2 { public static void main(String[] args) { AClass a = new AClass(); System.out.println("a的值:"+a.a);//输出:a的值:1 } }
![](http://common.cnblogs.com/images/copycode.gif)
从输出对比可知,虽然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.解析 将类的二进制数据中的符号引用替换成直接引用。。(static final 好像跟这个有点关系????5.6.处有联系)
4.类初始化
主要对类变量(而非对象变量)的初始化
声明类变量的初始值 = 静态初始化块 他们是相同的,等效的。都会被当成类的初始化语句,JVM会按照这些语句在程序中的顺序依次执行他们
![](http://common.cnblogs.com/images/copycode.gif)
public class Test { static int a = 5; //初始化时赋值 static int b; //初始化时赋值--静态块 static int c; //连接时赋默认值值 static{ b = 6; } public static void main(String[] args) { System.out.println(a); System.out.println(b); System.out.println(c); }
![](http://common.cnblogs.com/images/copycode.gif)
输出:
5
6
0
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来运行某个主类。即cmd java 程序会先初始化该类。
特殊情形:final 类型的类变量,如果在编译时(转成.class文件)就可以确定,那么这个类变量就相当于“宏变量”,编译时,直接替换成值。
所以,即使使用这个类变量,程序也不会导致该类的初始化!!----相当于直接使用 常量
![](http://common.cnblogs.com/images/copycode.gif)
public class Test { public static void main(String[] args) { System.out.println(aClass.A); } } class aClass{ static final int A = 111;//可以确定 static{ System.out.println("静态块初始化"); } }
![](http://common.cnblogs.com/images/copycode.gif)
输出:111
![](http://common.cnblogs.com/images/copycode.gif)
public class Test { public static void main(String[] args) { System.out.println(aClass.A); } } class aClass{ static final long A = System.currentTimeMillis();//在编译时无法确定 static{ System.out.println("静态块初始化"); } }
![](http://common.cnblogs.com/images/copycode.gif)
输出:
静态块初始化
1468309845203
使用ClassLoader类的 loadClass方法来加载类时,只是加载该类,而不会执行该类的初始化!!使用Class的forName()静态方法,才会导致强制初始化该类。
6.类加载器
类加载器 负责加载所有的类,为被加载如内存中的类生成一个java.lang.Class实例。一旦类被载入内存,同一个类就不会再加载第二次
如何判断是同一个类:
java中 一个类用其 全限定类名标示--包名+类名
jvm中 一个类用其 全限定类名+加载器标示---包名+类名+加载器名
加载器层次结构:
JVM启动时,姓曾的三个类加载器组成的机构
1.Bootstrap ClassLoader 根类 ------引导类加载器,加载java核心类。非java.lang.ClassLoader子类,而是JVM自身实现
2.Extension ClassLoader 扩展类-----加载JRE的扩展目录中的JAR包的类(%JAVA_HOME%/jre/lib/ext或java.ext.dirs系统属性指定的目录)
3.System ClassLoader 系统类-----加载cmd java -cp **,环境变量指定的jar包和类路径。ClassLoader.getSystemClassLoader获得 系统类加载器。
4.用户类加载器。。。
![](http://common.cnblogs.com/images/copycode.gif)
public class BootstrapTest { public static void main(String[] args) { // 获取根类加载器所加载的全部URL数组 URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); //无需理会警告 //遍历 输出根类加载器 加载的全部URL for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); } } }
![](http://common.cnblogs.com/images/copycode.gif)
输出:
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.访问类加载器
![](http://common.cnblogs.com/images/copycode.gif)
public static void main(String[] args) throws IOException { ClassLoader systemLoader = ClassLoader.getSystemClassLoader();// get system loader 获得系统的类加载器 System.out.println("系统类加载器: "+systemLoader); /* * 获取 系统类加载器 的加载路径--通常由CLASSPATH 环境变量 指定, * 如果,操作系统 没有指定CLASSPATH环境变量, 则 默认以当前路径作为 系统类加载器 的家在路径 */ Enumeration<URL> eml = systemLoader.getResources(""); while(eml.hasMoreElements()){ System.out.println("SYSTEMClassLoader Route: "+eml.nextElement());//系统类加载器的加载路径 是程序运行的当前路径 } //获取 系统类加载器 的父类加载器,得到 扩展类加载器 ClassLoader extenionLoader = systemLoader.getParent(); System.out.println("扩展类加载器: "+extenionLoader); System.out.println("扩展类加载器的 路径: "+System.getProperty("java.ext.dirs")); ClassLoader baseLoader = extenionLoader.getParent(); System.out.println("扩展类加载器的 父类:"+baseLoader);//The parent Classload is Bootstrap ClassLoader.BC is not use Java Language...So.. //根加载器 没有 继承ClassLoader抽象类。所以,返回的是Null //但实际上 扩展类加载器 的 父类加载器 是 根类加载器,只是,根类加载器 并不是 java实现的。 }
![](http://common.cnblogs.com/images/copycode.gif)
输出:
系统类加载器: sun.misc.Launcher$AppClassLoader@73d16e93
SYSTEMClassLoader Route: file:/D:/WorkSpace1/Java_Test/bin/
扩展类加载器: sun.misc.Launcher$ExtClassLoader@7f31245a
扩展类加载器的 路径: C:\Program Files\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
![](http://common.cnblogs.com/images/copycode.gif)
/** * 读取一个文件 的 内容,返回byte[] * @param filename * @return * @throws IOException */ private byte[] getBytes(String filename) throws IOException { System.out.println("使用getBytes方法"); File file = new File(filename);//路径 bin/Chapter18/Test.class long len = file.length(); byte[] raw = new byte[(int)len]; try (FileInputStream fin = new FileInputStream(file)){ //一次读取Class文件的全部二进制数据 int r = fin.read(raw); if(r!=len) throw new IOException("无法读取全部文件:"+r+"!="+len); return raw; } }
![](http://common.cnblogs.com/images/copycode.gif)
![](http://common.cnblogs.com/images/copycode.gif)
/** * 编译指定JAVA文件,返回编译的结果 * @param javaFile * @return * @throws IOException */ private boolean compile(String javaFile) throws IOException{ System.out.println("myClassLoader:正在编译"+javaFile+".........."); //调用系统的javac命令--指定了.class生成的路径,注意空格。此处为何不加上Chapter18呢?难道是java命名唯一性的缘故,包名+类名,已经知道了包名?? Process p = Runtime.getRuntime().exec("javac -d d:/WorkSpace1/Java_Test/bin/ "+javaFile); try { //其他线程都等待这个线程完成 p.waitFor(); } catch (Exception e) { // TODO: handle exception System.out.println(e); } //获取javac线程的退出值 int ret = p.exitValue(); //返回编译是否成功 return res == 0; }
![](http://common.cnblogs.com/images/copycode.gif)
![](http://common.cnblogs.com/images/copycode.gif)
/** * 重写的findClass方法 */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException {//name = Test Class clazz = null; String javaFilename = "src/Chapter18/"+fileStub+".java"; String classFilename = "bin/Chapter18/"+fileStub + ".class"; File javaFile = new File(javaFilename); //相对路径,相对于项目的跟路径:D:\WorkSpace1\Java_Test File classFile = new File(classFilename); //当指定Java源文件存在,且,Class文件不存在,或者Java源文件的修改时间比class文件的修改时间更晚时,重新编译if(javaFile.exists() && (!classFile.exists()||javaFile.lastModified()>classFile.lastModified())){ try { //如果编译失败,或者该class文件不存在 if(!compile(javaFilename)||!classFile.exists()){ throw new ClassNotFoundException("ClassNotFoundException:"+javaFilename+ " or "+classFile+" is not exists"); } } catch (Exception e) { // TODO: handle exception 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 (Exception e) { // TODO: handle exception e.printStackTrace(); } } //如果clazz为null,表明加载失败,则抛出异常 if(clazz == null){ throw new ClassNotFoundException(name); } return clazz; }
![](http://common.cnblogs.com/images/copycode.gif)
![](http://common.cnblogs.com/images/copycode.gif)
/** * main方法 * @param args * @throws Exception */ public static void main(String[] args) throws Exception { ClassloaderMineTest mc = new ClassloaderMineTest(); Class<?> clazz1 = mc.findLoadedClass("Chapter18.Test"); Class<?> clazz = mc.loadClass("Test",false); //路径 Test? Class<?> clazz2 = mc.findLoadedClass("Chapter18.Test"); //路径 Chapter18.Test??,为什么又和loadClass的路径不一样呢 //得到Class对象后就可以反射了 Method main = clazz.getMethod("main", (new String[0].getClass())); String[] progArgs = {"Chapter18.Test"}; Object argsArray[] = {progArgs}; main.invoke(null, argsArray); }
![](http://common.cnblogs.com/images/copycode.gif)
11.URLClassLoader 类
java 为 ClassLoader 提供了一个 实现类 URLClassLoader ,该类 也是系统类加载器 和 扩展类加载器的 父类
URLClassLoader 的 两个构造器
URLClassLoader(URL[] urls)
URLClassLoader(URL[] urls, ClassLoader parent)
一旦 获得URLClassLoader对象后,就可以调用对象的 loadClass()方法 来加载指定的类。
下面展示的程序示范了如何直接从文件系统中加载MySQL驱动,并且使用该驱动来获取数据库连接。。通过这种方式,可以无须将MySQL驱动添加到CLASSPATH环境变量中
![](http://common.cnblogs.com/images/copycode.gif)
public class URLClassLoaderTest { private static Connection conn; private String url; private String driver; /** * 获得该用户下面的所有表 */ public static void getAllTableList(String schemaName,DatabaseMetaData dbMeta) { try { // table type. Typical types are "TABLE", "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM". String[] types = { "TABLE" }; ResultSet rs = dbMeta.getTables(null, schemaName, "%", types); while (rs.next()) { String tableName = rs.getString("TABLE_NAME"); //表名 String tableType = rs.getString("TABLE_TYPE"); //表类型 String remarks = rs.getString("REMARKS"); //表备注 System.out.println(tableName + "-" + tableType + "-" + remarks); } } catch (SQLException e) { e.printStackTrace(); } } //定义一个获取数据库 连接的方法 public static Connection getConn(String url,String user,String pass) throws Exception{ if(conn==null){ //file 表名是从 本地文件系统加载, //http:为前缀,表明从互联网通过HTTP来访问 //注意 URL 路径 // URLClassLoader loader = new URLClassLoader(new URL[]{new URL("file:D:/WorkSpaceMyEclipse/Meeting_Hall/WebRoot/WEB-INF/classes/")}); URL[] urls = {new URL("file:mysql-connector-java-5.1.30-bin.jar")};//从本地加载,jar包由外界导入,或者是放在项目的跟路径D:\WorkSpace1\Java_Test //以 默认的 ClassLoader 作为父 ClassLoader,创建 URLClassLoader URLClassLoader myClassLoader = new URLClassLoader(urls); //加载MYSQL的JDBC驱动, 并创建默认实例-------与 普通的通过 new 生成实例的方法 对比 Driver driver = (Driver)myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance(); //创建一个 设置JDBC连接属性的 Properties对象 Properties props = new Properties(); //至少需要为改对象传入 user和password两个属性 props.setProperty("user", user); props.setProperty("password", pass); //调用Driver 对象的 connect方法来取得数据库连接 conn = driver.connect(url, props); } return conn; } public static void main(String[] args) throws Exception { getConn("jdbc:mysql://localhost:3306/user_login", "root", "root"); DatabaseMetaData dbMeta = conn.getMetaData(); // 获取表中索引信息 getAllTableList(null, dbMeta); } }
![](http://common.cnblogs.com/images/copycode.gif)
输出:
affair-TABLE-
comment-TABLE-
sort-TABLE-
user-TABLE-
![](http://common.cnblogs.com/images/copycode.gif)
/** * 普通的 数据库连接方法 * @return */ public static Connection getDBConnection() { try { // 加载数据库 Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/user_login"; String user = "root"; String password = "root"; // 连接 conn = DriverManager.getConnection(url, user, password); } catch (Exception e) { e.printStackTrace(); } return conn; }
![](http://common.cnblogs.com/images/copycode.gif)
java加载机制整理的更多相关文章
- jvm系列(一):java类的加载机制
java类的加载机制 1.什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装 ...
- Java高级之虚拟机加载机制
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 1.0版本:2016-05-21 SubClass!! 执行结果说明一个问题:子类调用父类变量的时候 ...
- Java实现配置加载机制
前言 现如今几乎大多数Java应用,例如我们耳熟能详的tomcat, struts2, netty…等等数都数不过来的软件,要满足通用性,都会提供配置文件供使用者定制功能. 甚至有一些例如Netty这 ...
- java class加载机制及对象生成机制
java class加载机制及对象生成机制 当使用到某个类,但该类还未初始化,未加载到内存中时会经历类加载.链接.初始化三个步骤完成类的初始化.需要注意的是类的初始化和链接的顺序有可能是互换的. Cl ...
- Java ClassLoader加载机制
一.体系结构(自上向下) 1.Bootstrap ClassLoader(BootStrapClassLoader) --- 启动类加载器或者叫引导类加载器,加载jdk核心的APIs,这些APIs一般 ...
- 深入java虚拟机学习 -- 类的加载机制
当看到"类的加载机制",肯定很多人都在想我平时也不接触啊,工作中无非就是写代码,不会了可以百度,至于类,jvm是怎么加载的我一点也不需要关心.在我刚开始工作的时候也觉得这些底层的内 ...
- 深入java虚拟机学习 -- 类的加载机制(续)
昨晚写 深入java虚拟机学习 -- 类的加载机制 都到1点半了,由于第二天还要工作,没有将上篇文章中的demo讲解写出来,今天抽时间补上昨晚的例子讲解. 这里我先把昨天的两份代码贴过来,重新看下: ...
- java动态加载机制
假设有一个class,ClassLoader首先把它load到内存里的code segment(内存里存放代码段的),站在ClassLoader的角度,内存里的一个一个的class就是一个一个的对象, ...
- java内存加载机制
什么是java类加载? 类加载是指将.class类中的二进制数据存放到内存中,会在内存中的推中建立一个java.lang.String的引用对象来存放方法区的数据结构,而类中的数据会放到方法区中 类加 ...
随机推荐
- event.preventDefault()
<!doctype html> <html lang="en"> <head> <meta charset="utf-8&quo ...
- Linux文件系统的主要目录结构说明及分区方案
Linux操作系统有一些固定的目录.各种Linux发行版的目录结构虽然不会一模一样,但是不会有很大差异.知道了这些目录的作用,不仅对你进行磁盘分区规划很有帮助,而且会让你以后的日常维护工作变得轻松.只 ...
- php怎么解析utf-8带BOM编码的json数据,php解析json数据返回NULL
今天遇到一个问题,json_decode解析json数据返回null,试了各种方法都不行,最后发现,原来是json文件编码的问题. 当json_decode解析utf-8带BOM格式的json数据时, ...
- 登录成功返回登录前页面js代码
/*------ setCookie(name,value) -----------*/ function setCookie(name,value) { var Days = 30; //此 coo ...
- 对ASP.NET Entity FrameWork进行单元测试
添加一个测试用的类库:将Web.config中的connectionstrings节点下的东东复制一份到刚添加的类库的app.config下 使用NUint+TestDriven.net进行测试: 如 ...
- python去掉空行
#用strip(),split()两个方法都可以判断空行 infile=open('/.../','r') outfile=open('/.../','w') for li in infile.rea ...
- FLEX AS3.0 百度地图
window xp系统 FlashBuilder4.5 先上百度下载flash api 下载地址http://developer.baidu.com/map/flash.htm 新建一个flex项目 ...
- WPF数据双向绑定
设置双向绑定,首先控件要绑定的对象要先继承一个接口: INotifyPropertyChanged 然后对应被绑定的属性增加代码如下: 意思就是当Age这个属性变化时,要通知监听它变化的人. 即:Pr ...
- ED/EP系列4《圈存指令》
1. 圈存交易 通过圈存交易,持卡人可将其在银行相应账户上的资金划入电子存折或电子钱包中. 特点: 1)--必须在金融终端上联机进行; 2)--必须提交个人识别码(PIN) 步骤: 1) --终端: ...
- Mybatis 的日志管理
Mybatis通过日志工厂提供日志信息,Mybatis内置的日志模版是log4j,commons.log,jdk log也可以通过slf4j简单日志模版结合log4j使用日志信息输出.具体选择哪个日志 ...