在以下几种状况下,Java虚拟机将结束生命周期:html
–执行了System.exit()方法java
–程序正常执行结束git
–程序在执行过程当中遇到了异常或错误而异常终止程序员
–因为操做系统出现错误而致使Java虚拟机进程终止github
•加载:查找并加载类的二进制数据(java编译后的.class文件)数据库
•链接编程
–验证:确保被加载的类的正确性数组
–准备:为类的静态变量分配内存,并将其初始化为默认值安全
–解析:把类中的符号引用转换为直接引用网络
•初始化:为类的静态变量赋予正确的初始值(=号后面的值)
–主动使用
–被动使用
–建立类的实例
–访问某个类或接口的静态变量,或者对该静态变量赋值
–调用类的静态方法
–反射(如Class.forName(“com.shengsiyuan.Test”))
–初始化一个类的子类
–Java虚拟机启动时被标明为启动类的类(Java Test),就是用java命令执行的那个带有main方法入口的类。
除了以上六种状况,其余使用Java类的方式都被看做是对类的被动使用,都不会致使类的初始化。
•类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,而后在堆区建立一个该类的java.lang.Class对象,用来封装类在方法区内的数据结构。
JVM运行时数据区的解释:
运行时数据区能够理解为java虚拟机内存。
JVM就是一个特殊的进程, 咱们执行的java程序, 都运行在一个JVM进程中, 这个进程的做用就是加载class文件, 而且执行class文件中的代码。 固然, 从一个class文件的加载, 到准备好可执行以前, 还有一段很长的路要走。 既然虚拟机做为一个虚拟的计算机, 来执行咱们的程序:
那么在执行的过程当中,必然要有地方存放咱们的代码(class文件);
在执行的过程当中, 总会建立不少对象, 必须有地方存放这些对象;
在执行的过程当中, 还须要保存一些执行的状态, 好比, 将要执行哪一个方法, 当前方法执行完成以后, 要返回到哪一个方法等信息;
因此, 必须有一个地方来保持执行的状态。 上面的描述中, “地方”指的固然就是内存区域, 程序运行起来以后, 就是一个动态的过程, 必须合理的划份内存区域, 来存放各类数据(内存,用于存放程序运行时数据)。
事实上,JVM在执行Java代码时都会把内存分为几个部分,即数据区来使用,这些区域都拥有本身的用途,并随着JVM进程的启动或者用户线程的启动和结束创建和销毁。接下去,经过下面的这幅图,咱们一个一个细数一下JVM运行时的数据区结构。
线程私有的数据区
程序计数器
· 做用
记录当前线程所执行到的字节码的行号。字节码解释器工做的时候就是经过改变这个计数器的值来选取下一条须要执行的字节码指令。
· 意义
JVM的多线程是经过线程轮流切换并分配处理器来实现的,对于咱们来讲的并行事实上一个处理器也只会执行一条线程中的指令。因此,为了保证各线程指令的安全顺利执行,每条线程都有独立的私有的程序计数器。
· 存储内容
当线程中执行的是一个Java方法时,程序计数器中记录的是正在执行的线程的虚拟机字节码指令的地址。
当线程中执行的是一个本地方法时,程序计数器中的值为空。
· 可能出现异常
此内存区域是惟一一个在JVM上不会发生内存溢出异常(OutOfMemoryError)的区域。
虚拟机栈
· 做用
描述Java方法执行的内存模型。每一个方法在执行的同时都会开辟一段内存区域用于存放方法运行时所需的数据,成为栈帧,一个栈帧包含如:局部变量表、操做数栈、动态连接、方法出口等信息。
· 意义
JVM是基于栈的,因此每一个方法从调用到执行结束,就对应着一个栈帧在虚拟机栈中入栈和出栈的整个过程。
· 存储内容
局部变量表(编译期可知的各类基本数据类型、引用类型和指向一条字节码指令的returnAddress类型)、操做数栈、动态连接、方法出口等信息。
值得注意的是:局部变量表所需的内存空间在编译期间完成分配。在方法运行的阶段是不会改变局部变量表的大小的。
· 可能出现的异常
若是线程请求的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError(栈溢出错误)异常。
若是在动态扩展内存的时候没法申请到足够的内存,就会抛出OutOfMemoryError(内存溢出错误)异常。
本地方法栈
· 做用
为JVM所调用到的Nativa即本地方法服务。
· 可能出现的异常
和虚拟机栈出现的异常很相像。
全部线程共有的数据区
Java堆
· 做用
全部线程共享一块内存区域,在虚拟机开启的时候建立。
· 意义
一、存储对象实例,更好地分配内存。
二、垃圾回收(GC)。堆是垃圾收集器管理的主要区域。更好地回收内存。
-存储内容
存放对象实例,几乎全部的对象实例都在这里进行分配。堆能够处于物理上不连续的内存空间,只要逻辑上是连续的就能够。
值得注意的是:在JIT编译器[z1] [z2] 等技术的发展下,全部对象都在堆上进行分配已变得不那么绝对。有些对象实例也能够分配在栈中。
· 可能出现的异常
实现堆能够是固定大小的,也能够经过设置配置文件设置该为可扩展的。
若是堆上没有内存进行分配,并没有法进行扩展时,将会抛出OutOfMemoryError异常。
方法区
· 做用
用于存储运行时常量池、已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
· 意义
对运行时常量池、常量、静态变量等数据作出了规定。
· 存储内容
运行时常量池(具备动态性)、已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
· 可能出现的异常
当方法区没法知足内存分配需求时,将抛出OutOfMemoryError异常。
–从本地系统中直接加载
–经过网络下载.class文件
–从zip,jar等归档文件中加载.class文件
–从专有数据库中提取.class文件
–将Java源文件动态编译为.class文件
将Java源文件动态编译为.class文件的理解:
简单理解:
就是在程序运行的时候,咱们只有.java文件,没有已经编译好的.class文件,这时,在程序运行的时候,咱们须要经过javax.tools. JavaCompiler接口得到系统编译器,而且经过它的run方法读取源代码,编译诊断,输出class,这叫动态编译。(系统运行以前就存在于磁盘等存储介质中的一个个文件,叫作静态的。系统运行的时候才会有的,经过系统的运行在内存中产生的,就叫动态。系统已经运行产生并放到了存储介质中的,叫静态的。)
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); int compilationResult = compiler.run(null, null, null, '/path/to/Test.java'); |
详解:
Java动态编译
程序产生过程
下图展现了从源代码到可运行程序的过程,正常状况下先编译(明文源码到字节码),后执行(JVM加载字节码,得到类模板,实例化,方法使用)。本文来探索下当程序已经开始执行,但在.class甚至.java还未就绪的状况下,程序如何得到指定的实现。这就是咱们下面的主题,动态编译。
相关类介绍
JavaCompiler: 负责读取源代码,编译诊断,输出class
JavaFileObject: 文件抽象,表明源代码或者编译后的class
JavaFileManager: 管理JavaFileObject,负责JavaFileObject的建立和保存位置
ClassLoader: 根据字节码,生成类模板
使用方式
因为代码在编译的时候,类定义甚至类名称还不存在,因此无法直接声明使用的。只能定义一个接口代替之,具体实现留给后面的动态编译。
public
interface Printer {
public
void
print();
}
源代码的文件级动态编译
java源码以文件的形式存在本地,程序去指定路径加载源文件。
String classPath = File2Class.class.getResource(
"/").getPath();
//在这里咱们是动态生成定义,而后写入文件。也能够直接读一个已经存在的文件
String str =
"import classloader.Printer;"
+
"public class MyPrinter1 implements Printer {"
+
"public void print() {"
+
"System.out.println(\"test1\");"
+
"}}";
//
将类的内容的字符串写入
MyPrinter1.java文件
FileWriter writer =
new FileWriter(classPath +
"MyPrinter1.java");
writer.write(str);;
writer.close();
//得到系统编译器,获取此平台提供的 Java™ 编程语言编译器工具。
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//获取一个标准文件管理器实现的新实例
StandardJavaFileManager fileManager = compiler.getStandardFileManager(
null,
null,
null);
//读入源文件,获取表示给定java文件的文件对象。
Iterable fileObject = fileManager.getJavaFileObjects(classPath +
"MyPrinter1.java");
//编译, 使用给定组件和参数建立编译任务。该编译可能没有完成。
JavaCompiler.CompilationTask task = compiler.getTask
[z3] (
null, fileManager,
null,
null,
null, fileObject);
task.call();
//
执行此编译任务
fileManager.close();
//指定class路径,默认和源代码路径一致,加载class,该类加载器用于从指向 jar文件和目录的 URL 的搜索路径加载类和资源。
//这里假定任何以 '/' 结束的 URL 都是指向目录的。
URLClassLoader classLoader =
new URLClassLoader(
new URL[]{
new URL(
"file:" + classPath)});
Printer printer = (Printer)classLoader.loadClass(
"MyPrinter1").newInstance();
//
加载目标
class
文件,并建立类的实例
printer.print();
源代码的内存级动态编译
上一节是经过java源文件动态编译加载的状况,这节让咱们看下源代码和class全程都在内存中操做,如何实现动态编译。
思路:
是生成源代码对应的JavaFileObject时,从内存string读取;
生成class对应的JavaFileObject时,以字节数组的形式存到内存。
JavaFileObject是一个interface, SimpleJavaFileObject是JavaFileObject的一个基本实现,当自定义JavaFileObject时,继承SimpleJavaFileObject,而后改写部分函数。
1,自定义JavaSourceFromString,做为源代码的抽象文件(来自JDK API文档)
/**
*用于表示来自字符串的源的文件对象。
*/
public
class JavaSourceFromString extends SimpleJavaFileObject {
/**
*这个“文件”的源代码。
*/
final String code;
/**
*构造一个新的JavaSourceFromString。
* @param name此文件对象表示的编译单元的名称
* @param code该文件对象所表明的编译单元的源代码
*/
JavaSourceFromString(String name, String code) {
super(URI.create(
"string:///" + name.replace(
'.',
'/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
public CharSequence
getCharContent(
boolean ignoreEncodingErrors) {
return code;
}
}
2,JavaClassFileObject,表明class的文件抽象
public
class JavaClassFileObject extends SimpleJavaFileObject {
//用于存储class字节
ByteArrayOutputStream outputStream;
public
JavaClassFileObject(String className, Kind kind) {
super(URI.create(
"string:///" + className.replace(
'.',
'/') + kind.extension), kind);
outputStream =
new ByteArrayOutputStream();
}
public OutputStream
openOutputStream()
throws IOException {
return outputStream;
}
public
byte[]
getClassBytes() {
return outputStream.toByteArray();
}
}
3,ClassFileManager,修改JavaFileManager生成class的JavaFileObject的行为,另外返回一个自定义ClassLoader用于返回内存中的字节码对应的类模板
public
class ClassFileManager extends ForwardingJavaFileManager {
private JavaClassFileObject
classFileObject
;
/**
*建立ForwardingJavaFileManager的新实例。
*
* @param fileManager委托给这个文件管理器
*/
protected
ClassFileManager(JavaFileManager fileManager) {
super(fileManager);
}
/**
*获取要输出的JavaFileObject文件对象
*表明给定位置中指定类名的指定类别。
*/
@Override
public JavaFileObject
getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind,
FileObject sibling)
throws IOException {
classFileObject
=
new JavaClassFileObject(className, kind);
return classFileObject;
}
@Override
//得到一个定制ClassLoader,返回咱们保存在内存的类
public ClassLoader
getClassLoader(Location location) {
return
new ClassLoader() {
@Override
protected Class<?>
findClass(String name)
throws ClassNotFoundException {
byte[] classBytes =
classFileObject
.getClassBytes();//
获取
class
文件对象的字节数组
return
super.defineClass(name, classBytes,
0, classBytes.length);//
定义
Class
对象
}
};
}
}
4,下面来偷梁换柱,用自定义的JavaFileObject/JavaFileManager来动态编译
String str = "import Printer;"
+ "public class MyPrinter2 implements Printer {"
+ "public void print() {"
+ "System.out.println(\"test2\");"
+ "}}";
//生成源代码的JavaFileObject
SimpleJavaFileObject fileObject = new JavaSourceFromString("MyPrinter2", str);
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//被修改后的JavaFileManager
JavaFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(null, null, null));
//执行编译
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, Arrays.asList(fileObject));
task.call();
//得到ClassLoader,加载class文件
ClassLoader classLoader = fileManager.getClassLoader(null);
Class printerClass = classLoader.loadClass("MyPrinter2");
//得到实例
Printer printer = (Printer) printerClass.newInstance();
printer.print();
Java运行时动态生成class的方法(动态生成代理对象)
Java是一门静态语言,一般,咱们须要的class在编译的时候就已经生成了,为何有时候咱们还想在运行时动态生成class呢?
由于在有些时候,咱们还真得在运行时为一个类动态建立子类。
应用场景(问题)的产生:
好比,编写一个ORM框架,如何得知一个简单的JavaBean是否被用户修改过呢?
以User
为例:
public
class User {
private String id;
private String name;
public String getId() {
return id;
}
public
void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public
void setName(String name) {
this.name = name;
}
}
其实UserProxy
实现起来很简单,就是建立一个User
的子类,覆写全部setXxx()
方法,作个标记就能够了:
public
class UserProxy extends User {
private
boolean dirty;
public
boolean isDirty() {
return
this.dirty;
}
public
void setDirty(
boolean dirty) {
this.dirty = dirty;
}
@Override
public
void setId(String id) {
super.setId(id);
setDirty(
true);
}
@Override
public
void setName(String name) {
super.setName(name);
setDirty(
true);
}
}
可是这个UserProxy
就必须在运行时动态建立出来了,由于编译时ORM框架根本不知道User
类。
解决方式一:本身动态生成字节码
如今问题来了,动态生成字节码,难度有多大?
若是咱们要本身直接输出二进制格式的字节码,在完成这个任务前,必须先认真阅读JVM规范第4章,详细了解class文件结构。估计读完规范后,两个月过去了。
因此,第一种方法,本身动手,从零开始建立字节码,理论上可行,实际上很难。
解决方式二:使用已有的一些能操做字节码的库,帮助咱们建立class。
目前,可以操做字节码的开源库主要有CGLib和Javassist两种,它们都提供了比较高级的API来操做字节码,最后输出为class文件。
好比CGLib,典型的用法以下:
Enhancer e =
new Enhancer();
e.setSuperclass(...);
e.setStrategy(
new DefaultGeneratorStrategy() {
protected ClassGenerator transform(ClassGenerator cg) {
return
new TransformingGenerator(cg,
new AddPropertyTransformer(
new String[]{
"foo" },
new Class[] { Integer.TYPE }));
}});
Object obj = e.create();
比本身生成class要简单,可是,要学会它的API仍是得花大量的时间,而且,上面的代码很难看懂对不对?
有木有更简单的方法?
有!
解决方式三:动态生成.java文件,而后编译该文件,在加载编译后的.class文件
换一个思路,若是咱们能建立UserProxy.java
这个源文件,再调用Java编译器,直接把源码编译成class,再加载进虚拟机,任务完成!
毕竟,建立一个字符串格式的源码是很简单的事情,就是拼字符串嘛,高级点的作法能够用一个模版引擎。
如何编译?
Java的编译器是javac
,可是,在很早很早的时候,Java的编译器就已经用纯Java重写了,本身能编译本身,行业黑话叫“自举”。从Java 1.6开始,编译器接口正式放到JDK的公开API中,因而,咱们不须要建立新的进程来调用javac
,而是直接使用编译器API来编译源码。
使用起来也很简单:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int compilationResult = compiler.run
[z4] (
null,
null,
null,
'/path/to/Test.java');
这么写编译是没啥问题,问题是咱们在内存中建立了Java代码后,必须先写到文件,再编译,最后还要手动读取class文件内容并用一个ClassLoader加载。
有木有更简单的方法?
有!
其实Java编译器根本不关心源码的内容是从哪来的,你给它一个String
看成源码,它就能够输出byte[]
做为class的内容。
因此,咱们须要参考Java Compiler API的文档,让Compiler直接在内存中完成编译,输出的class内容就是byte[]
。
代码改造以下:
Map<String,
byte[]> results;
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager stdManager = compiler.getStandardFileManager(
null,
null,
null);
try (MemoryJavaFileManager manager =
new MemoryJavaFileManager(stdManager))
[z5] {
JavaFileObject javaFileObject = manager.makeStringSource(fileName, source);
CompilationTask task = compiler.getTask(
null, manager,
null,
null,
null, Arrays.asList(javaFileObject));
if (task.call()) {
results = manager.getClassBytes();
}
}
上述代码的几个关键在于:
1. 用自定义的MemoryJavaFileManager
替换JDK默认的StandardJavaFileManager
,以便在编译器请求源码内容时,不是从文件读取,而是直接返回String
;
2. 用自定义的MemoryOutputJavaFileObject
替换JDK默认的SimpleJavaFileObject
,以便在接收到编译器生成的byte[]
内容时,不写入class文件,而是直接保存在内存中。
最后,编译的结果放在Map<String, byte[]>
中,Key是类名,对应的byte[]
是class的二进制内容。
为何编译后不是一个byte[]
呢?
由于一个.java
的源文件编译后可能有多个.class
文件!只要包含了静态类、匿名类等,编译出的class确定多于一个。
如何加载编译后的class呢?
加载class相对而言就容易多了,咱们只须要建立一个ClassLoader
,覆写findClass()
方法:
class MemoryClassLoader extends URLClassLoader {
Map<String,
byte[]> classBytes =
new HashMap<String,
byte[]>();
public MemoryClassLoader(Map<String,
byte[]> classBytes) {
super(
new URL[
0], MemoryClassLoader.class.getClassLoader());
this.classBytes.putAll(classBytes);
}
@Override
protected Class<?> findClass(String name)
throws ClassNotFoundException {
byte[] buf = classBytes.get(name);
if (buf ==
null) {
return
super.findClass(name);
}
classBytes.remove(name);
return defineClass(name, buf,
0, buf.length);
}
}
除了写ORM用以外,还能干什么?
能够用它来作一个Java脚本引擎。实际上本文的代码主要就是参考了Scripting项目的源码。
完整的源码呢?
在这里:https://github.com/michaelliao/compiler,连Maven的包都给你准备好了!compiler-master(动态建立class的代码).zip
也就200行代码吧!动态建立class不是梦!
•类的加载的最终产品是位于堆区中的Class对象
•Class对象封装了类在方法区内的数据结构,而且向Java程序员提供了访问方法区内的数据结构的接口
•有两种类型的类加载器
–Java虚拟机自带的加载器
•根类加载器(Bootstrap)
•扩展类加载器(Extension)
•系统类加载器(System)
–用户自定义的类加载器
•java.lang.ClassLoader的子类
•用户能够定制类的加载方式,只要继承ClassLoader类和重写它的findClass方法就能够了。固然,也能够根据业务需求重写其它的相关方法。
•类加载器并不须要等到某个类被“首次主动使用”时再加载它,首次主动使用只是初始化的条件,不是加载的条件。咱们能够手动调用类加载器的loadClass方法,主动加载想加载的类。
•JVM规范容许类加载器在预料某个类将要被使用时就预先加载它,若是在预先加载的过程当中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError连接错误)
•若是这个类一直没有被程序主动使用,那么类加载器就不会报告错误
只有在首次主动使用被加载的类的时候,才会报告类在加载过程当中发生的错误,LinkageError连接错误
•类被加载后,就进入链接阶段。链接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。
•类的验证的内容
–类文件的结构检查
–语义检查
–字节码验证
–二进制兼容性的验证
•主动使用(六种)
–建立类的实例
–访问某个类或接口的静态变量,或者对该静态变量赋值
–调用类的静态方法
–反射(如Class.forName(“com.shengsiyuan.Test”))
–初始化一个类的子类
–Java虚拟机启动时被标明为启动类的类(Java Test)
除了上述六种情形,其余使用Java类的方式都被看做是被动使用,不会致使类的初始化。
注意:
1, 只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才能够认为是对类或接口的主动使用,若是程序访问的静态变量或静态方法是在该类的父类或其余类中定义的,并不认为是对该类的主动使用也不会初始化该类。而是对定义这个静态变量或静态方法的类的主动使用,会初始化这个定义了该静态变量或方法的类。
2, 调用ClassLoader类的loadClass方法加载一个类,并非对类的主动使用,不会致使类的初始化。
只有当程序首次使用接口的静态变量时,才会致使该接口的初始化;
根加载器默认加载rt.jar中的类,若是一个类是由根类加载器加载的,咱们去获取这个类的类加载器对象,将返回一个空。由于根类加载器不对程序员暴露,java不容许程序员去获取根类加载器。
父委托机制:当某个类加载器要加载一个类时,他首先会去到本身的命名空间去查找这个类是否已经被加载,若是已经被加载,就返回这个类的Class对象的引用,若是没有被它加载,它不会立刻去加载这个类,而是委托本身的父加载器去加载这个类,固然,父加载器也是先到本身的命名空间中去查找,看这个类是否已经加载,若是已经加载了,将这个类的Class对象的引用直接返回给到本身的子加载器,若是没有加载,继续向上委托,就这样,直到根加载器,若是根加载器在本身的命名空间中找到了这个类的Class对象,则证实根加载器已经加载了这个类,根加载器会将这个类的Class对象的引用返回给到本身的子加载器,这样一级一级返回,直到返回到要加载这个类的那个类加载器。若是根加载器仍是没有加载过这个类,那么它会尝试去加载这个类,加载到了,就将这个类的Class对象的引用根据父子关系,依次接力向本身的子加载器返回,若是仍是没有加载到,就会往下依次接力委托子加载器去加载,直到加载到这个类,而后返回这个类的Class对象的引用,依次传递给到要加载这个类的加载器。若是最终一直到要加载这个类的加载器都没有加载到这个类,那么程序抛出类找不到异常。
定义类加载器:成功加载了那个类的类加载器。那个类的Class对象是由它定义生成的,存放在它的命名空间中。
初始类加载器:定义类加载器以及它的全部子孙加载器都是初始类加载器。它们都能成功返回该类的Class对象的引用。
包装关系:类加载器是一个对象,而不是一个类。包装关系只得是一个对象里面持有另外一个对象得引用,在类加载器的父子关系中,就是子加载器持有了父加载器的引用。父加加载器对象的实例经过子加载器的构造方法传入,若是不传,默认的自定义加载器的父加载器就是系统类加载器。
package com.shengsiyuan.classloader;
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream;
public class MyClassLoader extends ClassLoader { private String name; // 类加载器的名字
private String path = "d:\\"; // 加载类的路径
private final String fileType = ".class"; // class文件的扩展名
public MyClassLoader(String name) { super(); // 让系统类加载器成为该类加载器的父加载器
this.name = name; }
public MyClassLoader(ClassLoader parent, String name) { super(parent); // 显式指定该类加载器的父加载器
this.name = name; }
@Override public String toString() { return this.name; }
public String getPath() { return path; }
public void setPath(String path) { this.path = path; }
@Override public Class<?> findClass(String name) throws ClassNotFoundException { byte[] data = this.loadClassData(name);
return this.defineClass(name, data, 0, data.length); }
private byte[] loadClassData(String name) { InputStream is = null; byte[] data = null; ByteArrayOutputStream baos = null;
try { this.name = this.name.replace(".", "\\");
is = new FileInputStream(new File(path + name + fileType));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) { baos.write(ch); }
data = baos.toByteArray(); } catch (Exception ex) { ex.printStackTrace(); } finally { try { is.close(); baos.close(); } catch (Exception ex) { ex.printStackTrace(); } }
return data; }
public static void main(String[] args) throws Exception { MyClassLoader loader1 = new MyClassLoader("loader1");
loader1.setPath("d:\\myapp\\serverlib");
MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
loader2.setPath("d:\\myapp\\clientlib");
MyClassLoader loader3 = new MyClassLoader(null, "loader3");
loader3.setPath("d:\\myapp\\otherlib");
test(loader2); test(loader3); }
public static void test(ClassLoader loader) throws Exception { Class clazz = loader.loadClass("Sample");
Object object = clazz.newInstance(); }
}
|
package com.shengsiyuan.classloader;
public class Dog { public Dog() { System.out.println("Dog is loaded by : " + this.getClass().getClassLoader()); } }
|
package com.shengsiyuan.classloader;
public class Sample { public int v1 = 1;
public Sample() { System.out.println("Sample is loaded by: " + this.getClass().getClassLoader());
new Dog(); } }
|
反射访问:不建立该类型的引用,直接经过该类的Class对象去获取该类的成员属性,方法等的描述对象,而后操做。
[z1]JIT 是 just in time 的缩写, 也就是即时编译编译器。使用即时编译器技术,可以加速 Java 程序的执行速度。下面,就对该编译器技术作个简单的讲解。
首先,咱们你们都知道,一般经过 javac 将程序源代码编译,转换成 java 字节码,JVM 经过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译。很显然,通过解释执行,其执行速度必然会比可执行的二进制字节码程序慢不少。为了提升执行速度,引入了 JIT 技术。
在运行时 JIT 会把翻译过的机器码保存起来,以备下次使用,所以从理论上来讲,采用该 JIT 技术能够接近之前纯编译技术。
[z2]JIT程序有两种运行方式:静态编译与动态解释。静态编译的程序在执行前所有被翻译为机器码,而解释执行的则是一句一句边运行边翻译。
把Java的字节码(包括须要被解释的指令的程序)转换成能够直接发送给处理器的指令的程序。当你写好一个Java程序后,源语言的语句将由Java编译器编译成字节码,而不是编译成与某个特定的处理器硬件平台对应的指令代码(好比,Intel的Pentium微处理器或IBM的System/390处理器)。字节码是能够发送给任何平台而且能在那个平台上运行的独立于平台的代码。
参数:
Writer out - 用于来自编译器的其余输出的 Writer;若是为 null,则使用 System.err
JavaFileManager fileManager - 文件管理器;若是为 null,则使用编译器的标准文件管理器
DiagnosticListener<? super JavaFileObject> diagnosticListener - 诊断侦听器;若是为 null,则使用编译器的默认方法报告诊断信息
Iterable<String> options - 编译器选项;null 表示没有选项
Iterable<String> classes - 类名称(用于注释处理),null 表示没有类名称
Iterable<? extends JavaFileObject> compilationUnits - 要编译的编译单元;null 表示没有编译单元
返回:
表示编译的对象,编译任务对象
int run(InputStream in,
OutputStream out,
OutputStream err,
String... arguments)
使用给定 I/O 通道和参数运行工具。按照惯例,工具若是运行成功,则返回 0;若是出现错误,则返回非 0 值。任何生成的诊断都将以某种未指定的格式写入 out 或 err。
参数:
in - “标准”输入;若是为 null,则使用 System.in
out - “标准”输出;若是为 null,则使用 System.out
err - “标准”错误;若是为 null,则使用 System.err
arguments - 要传递给工具的参数,要编译的java文件。
返回:
若是成功,则返回 0;不然返回非 0 值
抛出:
NullPointerException - 若是参数数组包含任何 null 元素。
这是Try-with-resources结构。是java7中一个新的异常处理机制,它可以很容易地关闭在try-catch语句块中使用的资源。
应用场景:用于关闭资源。是一个可以确保资源能被正确地关闭的强大方法。
特色:try-with-resources 语句会确保在try语句结束时关闭全部资源。实现了java.lang.AutoCloseable或java.io.Closeable的对象均可以作为资源。
语法:将资源对象的建立写在try后面的括号中,括号中能够声明多个资源对象,用;号隔开,最后一个不用加分号。资源被关闭的顺序与它们被建立的顺序相反。
注意:try-with-resources 也能够有catch和finally语句块,就像使用一个普通的try语句同样。在try-with-resources 语句中,catch或者finally将在资源被关闭后执行。若是没有catch语句块,try中的异常将被抛出,可是从try-with-resources抛出的异常被禁止。在java7或更晚的版本中,咱们能够获取到这些被禁止的异常。