从一个诡异的问题说起
测试案例一:
package ecut.classloader;
public class Sun {
protected static int a = 100 ;
protected static int b ;
protected static Sun instance = new Sun() ;
public Sun() {
a++ ;
b++ ;
}
}
package ecut.classloader;
public class SunTest {
@SuppressWarnings("unused")
public static void main(String[] args) {
Sun s = Sun.instance;
System.out.println( Sun.a );
System.out.println( Sun.b );
}
}
运行结果如下:
101
1
测试案例二:
package ecut.classloader;
public class Moon {
protected static Moon instance = new Moon() ;
protected static int a = 100 ;
protected static int b ;
public Moon() {
a++ ;
b++ ;
}
}
package ecut.classloader;
public class MoonTest {
@SuppressWarnings("unused")
public static void main(String[] args) {
Moon s = Moon.instance;
System.out.println( Moon.a );
System.out.println( Moon.b );
}
}
运行结果如下:
100
1
类的生命周期
1、 JVM 的生命周期 ( 在线程部分 ) :当一个 Java 程序执行时,将启动一个 JVM 进程 ,当程序执行结束或抛出异常时 JVM 退出。
2、对象的声明周期: 当使用 new 关键字 创建一个类的实例时,一个对象(实例)的生命周期即宣告开始
Student s = new Student(); // 将导致创建一个Student实例并对该实例中的实例属性进行初始化
实例属性的初始化:
private int id = 0 ;
private String studentNo ;
{
studentNo = "ECUT-00000000" ;
}
private String name ;
public Student( String name ){
this.name = name ;
}
使用对象 ( 使用对象的 属性 、方法 等 )
当某个对象不再被任何一个引用变量所引用时,它可能会被GC回收,如果被回收,它的生命周期将宣告结束
3、类的生命周期
java.lang.Object 是整个 Java 类继承体系的根类
java.lang.Class 类的实例表示正在运行的 Java 应用程序中的类和接口
类的加载
类的链接
类文件的结构检查: 确保文件遵循Java 文件的固定格式
语义检查: 确保类本身符合Java 语言的语法规定
字节码验证: 确保字节码流可以被JVM 安全地执行
» 字节码流代表Java 方法(含静态和非静态),它是被称作操作码的单字节指令组成的序列,每个操作码后都跟着一个或多个操作数
» 字节码验证会检查每个操作码是否合法,即是否有合法的操作数二进制兼容的验证: 确保相互引用的类之间协调一致
» 比如A 类中调用B 类的b() 方法,检查B 中是否有b() 方法存在
类的初始化
static {
a = 10000 ;
}
如果该类还没有被加载和连接,那么先加载和连接该类
如果该类存在直接父类,但该父类还未初始化,则先初始化其直接父类
如果该类中存在初始化语句,则依次执行这些初始化语句
JVM 只有在首次主动使用某个类或接口时才会初始化它
被动使用不会导致本类的初始化
诡异的问题 解析测试案例:
package ecut.classloader;
public class Sun {
protected static int a = 100 ;//链接(准备):0//初始化: a:100
protected static int b ;//链接(准备):0//初始化: b:0
protected static Sun instance = new Sun() ;//链接(准备):null//初始化: a:101 b:1
public Sun() {
a++ ;
b++ ;
}
}
package ecut.classloader;
public class Moon {
protected static Moon instance = new Moon() ;//链接(准备):null//初始化: a:1 b:1
protected static int a = 100 ;//链接(准备):0//初始化: a:100
protected static int b ;//链接(准备):0//初始化: b:1
public Moon() {
a++ ;
b++ ;
}
}
类的使用
a>、创建类的实例 ( new 、反射、反序列化 、克隆 )
b>、调用类的静态方法
c>、访问类 或 接口的 静态属性 ( 非常量属性 ) ( 取值 或 赋值 都算 )
访问类 或 接口 的 非编译时常量,也将导致类被初始化:
public static final long time = System.currentTimeMillis();
d>、调用反射中的某个些方法,比如 Class.forName( "edu.ecut.Student" );
e>、初始化某个类时,如果该类有父类,那么父类将也被初始化
f>、被标记为启动类的那些类(main)
a>、程序中对编译时常量的使用视作对类的被动使用
对于final 修饰的变量,如果编译时就能确定其取值,即被看作编译时常量
» 编译时常量如: public static final int a = 2 * 3 ;
» JVM 的加载和连接阶段,不会在方法区内为某个类的编译时常量分配内存
对于final 修饰的变量,如果编译时就不能确定其取值,则不被看作编译时常量
» 非编译时常量如: public static final long time = System.currentTimeMillis() ;
» 使用该类型的静态变量将导致当前类被初始化( 主动使用)
b>、JVM初始化某个类时,要求其所有父类都已经被初始化,但是 该规则不适用 于 接口 类型
一个接口不会因为其子接口或实现类的初始化而初始化,除非使用了该接口的静态属性
c>、只有当程序访问的静态变量或静态方法的确在当前类或接口定义时,
才能看作是对类或接口的主动使用:
比如使用了 Sub.method() ,而 method() 是继承自 Base ,则只初始化 Base 类
d>、调用 ClassLoader 的 loadClass( ) 加载一个类,不属于对类的主动使用
主动使用和被动使用测试案例一:
package ecut.classloader;
public class Panda {
// 编译时常量(对于final 修饰的变量,如果编译时就能确定其取值,即被看作编译时常量)
public static final String HOMETOWN = "中国" ;
// 非编译时常量(对于final 修饰的变量,如果编译时就不能确定其取值,则不被看作编译时常量)
public static final long time = System.currentTimeMillis();
public static int a ;
static {
System.out.println( "static code , a = " + a );
a = 100 ;
System.out.println( "static code , a = " + a );
}
}
package ecut.classloader;
public class PandaTest {
public static void main(String[] args) {
System.out.println( Panda.HOMETOWN ); // 编译时常量被动使用
//System.out.println( Panda.a ); // 访问静态变量(不是常量) 主动使用
//使用该类型的静态变量将导致当前类被初始化( 主动使用)
System.out.println( Panda.time );//非编译时常量主动使用,静态代码块只执行一次因为初始化操作只执行一次
System.out.println( Panda.time );//只有第一次使用才完成初始化操作,所以值是固定的不变的
}
}
运行结果如下:
中国
static code , a = 0
static code , a = 100
1522485492485
1522485492485
主动使用和被动使用测试案例二:
package ecut.classloader;
public class InitTest {
public static void main(String[] args) {
//比如使用了 Child.hometown ,而 hometown是继承自 Father ,则只初始化 Father 类
//System.out.println(Child.hometown);
//初始化某个类时,如果该类有父类,那么父类将也被初始化
//System.out.println(Child.name);
//new Father();
//new Father();
new Child();
new Child();
}
}
class Father {
protected static String hometown ;
static{
System.out.println( "Father : static code block." );
hometown = "Sinaean" ;
}//new Fater()时静态代码块最先执行,只执行一次
{ System.out.println( "Father : non-static code block." );}//每一次new Fater()都执行,仅此静态代码块执行
public Father(){
System.out.println( "Father construction." );
}//每一次new Fater()都执行,最后执行
}
class Child extends Father {
protected static String name ;
static{
System.out.println( "Child : static code block." );
name = "Child" ;
}
{ System.out.println( "Child : non-static code block." );}
public Child(){
System.out.println( "Child construction." );
}
}
运行结果如下:
Father : static code block.
Child : static code block.
Father : non-static code block.
Father construction.
Child : non-static code block.
Child construction.
Father : non-static code block.
Father construction.
Child : non-static code block.
Child construction.
类的卸载:当一个类不再被任何对象所使用时,JVM会卸载该类。
类加载器
1、类加载器用来把类加载到JVM 中
从JDK 1.2 版本开始,类的加载过程采用父亲委托机制
父亲委托机制中,每个类加载器都有且只有一个父加载器,除了JVM 自带的根类加载器( Bootstrap Loader )
2、JVM 的三种主要类加载机制
全盘负责
父类委托
缓存机制
3、JVM 自带的类加载器
根类加载器(BootstrapLoader)
扩展类加载器(ExtClassLoader)
系统类加载器(AppClassLoader)
ClassLoader测试案例一:
package ecut.classloader;
import java.util.ArrayList;
public class ClassLoaderTest1 {
public static void main(String[] args) {
Class> c = String.class; // java.lang.String
ClassLoader loader = c.getClassLoader();
System.out.println(loader); // null ( Bootstrap Loader )
Object o = new ArrayList<>(); // java.util.ArrayList
c = o.getClass();
loader = c.getClassLoader();
System.out.println(loader); // null ( Bootstrap Loader )
c = ClassLoaderTest1.class;
loader = c.getClassLoader(); // 获得 ClassLoaderTest1 这个类的类加载器
System.out.println(loader); // AppClassLoader
// 获得 loader 这个 "类加载器" 的 父加载器
ClassLoader parent = loader.getParent();
System.out.println(parent); // ExtClassLoader
ClassLoader root = parent.getParent();
System.out.println( root ); // null ( Bootstrap Loader )
}
}
运行结果如下:
null
null
sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742
nul
4、类加载器的层次
注意这里的层次关系不是类与类的继承关系
各层次的类加载器加载的类
ClassLoader测试案例二:
package ecut.classloader;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
public class ClassLoaderTest2 {
public static void main(String[] args) {
Properties props = System.getProperties();
System.out.println(props);
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~");
Set
运行结果如下:
..........
~~~~~~~~~~~~~~~~~~~~~~~
C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
D:\java_workspace\Java\JavaAdvanced\bin
5、自定义类加载器
JVM 允许开发者开发自己的类加载器
ClassLoader 中的关键方法
部分源码:
public Class> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查类是否已经加载。
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);//看父类加载器有没有加载该类(父委托机制)
} else {
c = findBootstrapClassOrNull(name);//父类加载器为空,看根加载器(Bootstrap Loader)有没有加载
}
} catch (ClassNotFoundException e) {
//如果类没有发现抛出ClassNotFoundException
}
if (c == null) {
//如果仍然没有找到,然后调用findClass为了找到类。
long t1 = System.nanoTime();
c = findClass(name);
// 这是定义类装入器;记录统计数据
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
除了 Bootstrap Loader 之外,其它的所有的类加载器对应的类的父类都是 java.lang.ClassLoader,loadClass方法最终调用的是findClass方法,因此自定义加载器时应该继承java.lang.ClassLoader并重写findClass方法。
自定义加载器测试案例:
用记事本新建一个Student.java,再使用命令行生成Student.class文件
Student.java有包名,直接运行Java命令会无法加载主类,因为用Javac 虽然可以编译但是没有生成正确的目录结构,包结构不对,main方法无法执行,应该带着包一起编译。并且运行java 命令需要在包名的上级目录下运行,且带上完整类名(包名.类名)
错误的编译方式:
正确的编译方式:
package ecut.classloader.entity;
public class Student{
private String name;
private int id;
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setId (int id){
this.id = id;
}
public int getId(){
return id;
}
public static void main(String[] args) {
System.out.println("Hello World");
}
}
package ecut.classloader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 除了 Bootstrap Loader 之外,
* 其它的所有的类加载器对应的类的父类都是 java.lang.ClassLoader
*/
public class EcutClassLoader extends ClassLoader {
private String path ;
public EcutClassLoader(String path) {
super();
this.path = path;
}
@Override
protected Class> findClass(final String name) throws ClassNotFoundException {
Class> c = null ;
System.out.println( "将要加载的类: " + name );
String s = name.replace( '.', '/' ) + ".class";
Path p = Paths.get( path , s );
if( Files.exists( p ) ){
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream in = Files.newInputStream( p );
int n ;
byte[] bytes = new byte[1024];
while( ( n = in.read( bytes ) ) != -1 ){
baos.write( bytes , 0 , n );
}
final byte[] byteCode = baos.toByteArray(); // 获得 ByteArrayOutputStream 内部的数据
c = this.defineClass( name , byteCode , 0 , byteCode.length );
} catch (IOException e) {
e.printStackTrace();
}
} else {
throw new ClassNotFoundException( "类: " + name + " 未找到." );
}
return c ;
}
}
package ecut.classloader;
import java.lang.reflect.Field;
public class EcutClassLoaderTest {
public static void main(String[] args) throws Exception {
final String path = "D:/Amy" ;
// 创建一个自定义的类加载器 ( 实例 )
EcutClassLoader loader = new EcutClassLoader( path );
final String className = "ecut.classloader.entity.Student" ;
Class> c = loader.loadClass( className );
System.out.println( c );
System.out.println( c.getName() );
System.out.println( c.getSimpleName() );
System.out.println( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" );
Object o = c.newInstance();
System.out.println( o );
Field idField = c.getDeclaredField( "id" );
idField.setAccessible( true );
Object value = idField.get( o ); // o.id
System.out.println( value );
idField.set( o , 250 ); // o.id = 250 ;
value = idField.get( o ); // o.id
System.out.println( value );
}
}
运行结果如下:
将要加载的类: ecut.classloader.entity.Student
class ecut.classloader.entity.Student
ecut.classloader.entity.Student
Student
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ecut.classloader.entity.Student@4e25154f
0
250
java.net.URLClassLoader 类
常用构造
获得实例的静态方法
待解决问题
URLClassLoader
转载请于明显处标明出处
http://www.cnblogs.com/AmyZheng/p/8647217.html