Java 类的生命周期

     

先了解下JAVA堆栈

JAVAJVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)

堆区:
1.
存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm
只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身。
栈区:
1.
每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中。
2.
每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.
栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)
方法区:
1.
又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的classstatic变量。
2.
方法区中包含的都是在整个程序中永远唯一的元素,如classstatic变量。

Java语言里堆(heap)和栈(stack)里的区别:

1.(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

2. 栈中存放局部变量(基本类型的变量)和对象的reference。栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈是跟随线程的,有线程就有栈。

3.堆中存放对象,包括对象变量以及对象方法堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。堆是跟随JVM的,有JVM就有堆内存。

 

一、类的加载、连接和初始化

当使用一个类的时候,要保证按照一下顺序进行:

1.        加载:查找并加载JAVA编译的.class二进制数据;

类的加载是先把类的.class文件加载入内存,放到运行时数据区的方法区内,然后在堆区创建一个java.lang.classs对象,用来封装类在方法区的数据结构。

Java读取多种来源的二进制文件,包括:

(1)      本地系统加载类的.CLASS文件;

(2)      网络下载类的.class文件;

(3)      通过zip或.jar包的文件中提取的.class文件;

(4)      专有数据库中提取.class文件;

(5)      把一个java源文件动态编译为.class文件。

类的加载最终产品是位于运行时数据区的的堆区的CLASS文件,其封装了方法区内的数据结构,并提供了java程序提供了访问类在方法区内的数据接口。

类的加载是由JAVA类加载器完成的:分为两种:

(1)java虚拟机自带的类加载器,包括启动类的加载器、扩展类的加载器和系统类加载器;

(2)用户自定义的类加载器,可以由java.lang.ClassLoader类的子类的实现,用户用它可以定制类加载方式。

类加载器并不是在首次使用类的时候才加载它,java虚拟机规范允许类加载器在预料到某个类将要调用的时候预先加载它,如果预先加载中文件缺失,类加载器在首次调用该类才报告错误(LinkageError错误)。若一直未被程序主动调用,则类加载器不会报错。

2.        连接:包括验证:确保被加载的类的正确性;准备:为类的静态变了分配内存空间,并初始化默认值;解析:把类中的符号引用转成直接引用。

(1)      类的验证

   当类加载器加载完二进制代码后,就进入连接阶段,第一步是类的验证,保证加载的类内部结构与其他类的协调一致。若检查到错误,就会抛出Error对象。

   验证主要包括:类文件结构检查;语义检查,确保符合JAVA规范,比如final类没有子类,以及final类型的方法没有被覆盖;字节码检查,字节码代表JAVA方法(包括静态方法和实例方法),他是由被称作操作码的单字节指令组成的序列;二进制兼容性验证,确保相互引用指教协调一致。如Worker类的goWork()方法会调用Car类的run()方法,若不存在NoSuchMethodError错误

(2)      类的准备

     类的准备阶段为类的静态变量分配内存,并设置初始值,例如:

public myProgram}{
  Pirvate staticint a=1;
  Private staticint b;
  Static{
    b=2;
   }
}


类中分别为静态变量分配值;

(3)      类的解析

就是把二进制数据中的符号引用替换为直接引用,如在Worker类中的gotoWork()方法会引用Car类的run()方法

Public void gotoWork(){
  Car.run();   //该类的二进制数据中表示为符号引用
}

     该类的二进制数据中,包括了Car类的run()方法的符号引用,解析的时候符号引用替换为一个指针,该指针指向Car类的run()方法在方法去内的内存位置,指针就是直接引用

3.        初始化:给类的静态变量赋予初始值。

Java虚拟机初始化一个类分为以下步骤:

(1)      若未加载和连接则先加载和连接;

(2)      若有父类未初始化,则初始化之;

(3)      若有初始化化语句则执行之;一般为静态变量或者构造方法;

4.        类的初始化时机

以上为程序首次访问一个类或接口时候才初始化它。以下6种情况看做程序对类或接口的主动使用。

(1)      创建类的实例。包括用new语句创建实例,或通过反射、克隆及反序列化来创建;

(2)      调用类的静态方法;

(3)      访问某个类或接口的静态变量,或者对静态变量赋值;

(4)      反射方法调用,如调用Class.forName(“Worker”)方法,若该类未初始化,那么改程序就会初始化之,饭后Worker类的实例。

(5)      初始化子类的时候,会主动初始化父类;

(6)      被JAVA虚拟机标明的启动类的类,

   除以上情况外,其他情况都会被认为被动使用,都不会导致类的的初始化,如:

(1)      final类的静态变量,如果在编译就能计算出变量的取值,那么该变量就叫做编译时常量,就会被看做对该类的被动调用,不会导致该类初始化,

package com.bin.proxy;
class Test{
  public static final int a=2*3;
  static{
     System.out.println("static method");
  }
  public Test(){
     System.out.println("init");
  }
}
public class Test1 {
   public static void main(String[] args){
      System.out.println(Test.a);
   }
}

   结果仅打印一个 “6”,编译生成.class文件时,不会在main()方法的字节码流中保持一个Test.a符号引用,而是在字节码流中嵌入常量值6。并不会为编译时的常量a分配内存。

(2)      若对于final类型的静态变量,编译时候不能计算出变量的取值,那么该类就会看做对类的主动调用,会导致初始化,如:

   public static final inta=(int)(Math.random()*10)/10+1;

结果该类被初始化。

(3)      虚拟机初始化类时候,要求父类优先初始化,但是不适用于接口。

该类的接口不会初始化,而且初始化一个接口时,同样不会初始化它的父接口,只有首次使用接口的静态变量时才会初始化;

(4)      调用ClassLoader类的loadClass方法加载一个类,不是对类的主动使用,不会初始化,当调用forName(“classA”)方法时才会初始化,

public class Test2 {
   static{
      System.out.println("static");
   }
   public static void main(String[] args) throws ClassNotFoundException {
      ClassLoaderloader=ClassLoader.getSystemClassLoader();
      Class odClass=loader.loadClass("Test2");
   }
}

    Java对类的使用方式分为主动使用和被动使用

二、类加载器

   类加载器就是指吧类加载到JAVA虚拟机中去,从JDK1.2开始,采用父亲委托机制,可以保证JAVA平台安全性,在委托机制中,除了虚拟机自带的跟类加载器以外,其余的类加载器都有一个父加载器,当程序请求加载器loader1加载Sample类时候,loader1首先委托自己的父加载器去加载Sample类,若加载成功,则由类加载器完成,否则由loader1本身加载该类。

Java虚拟机自带了一下集中加载器。

(1)      根(Bootstrap)类加载器:没有父加载器,主要负责加载虚拟机的核心类库,如java.lang.*等。跟类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器依赖于底层操作系统,属于虚拟机的一部分

(2)      扩展(Extension)类加载器:他的父类为跟了加载器,从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK安装目下的jre/lib/ext子目录下加载类库,若吧JAR文件放到该目录下,会自动由扩展类加载,扩展类加载器是纯JAVA类,是java.lang.ClassLoader类的子类,跟类加载器则不是。

(3)      系统(System)类加载器,也成应用类加载,父加载器为扩展类加载器。他从环境变量classpath或者系统属性java.class.path所指定目录加载类,是用户自定义的类加载器的默认父加载器,是纯java类,是java.lang.ClassLoader类的子类。

(4)      用户可以定制自己的类加载器,java提供了抽象类java.lang.ClassLoader,用户自定义类继承自该类就可以了。

  

package com.bin.proxy;
public class Test2 {
	public static void main(String[] args) throws ClassNotFoundException {
	   Class c;
	   ClassLoader c1,c2;
	   c1=ClassLoader.getSystemClassLoader(); //获得系统类加载器
	   System.out.println(c1);  //打印系统类加载器
	   while(c1 != null){
		   c2=c1;
		   c1=c1.getParent();
		   System.out.println("c2的父加载类为:"+c1);  //打印扩展类加载器
	   }
	   c= Class.forName("java.lang.Object"); //object的类class实例
	   c1= c.getClassLoader();          
	   System.out.println("java.lang.Object的类加载为:"+c1);
	   c= Class.forName("com.bin.proxy.Test2"); //object的类class实例,类加载为根加载器,java为了虚拟机安全,不同加载器引用,用NULL表示根加载器
	   c1= c.getClassLoader();          
	   System.out.println("com.bin.proxy.Test2:"+c1); //系统类加载器
	}
}
    类加载器加载一个类的时候,先查找自己命名空间中是否加载,若没有从系统类加载器中加载,若没有在到扩展类加载器,直至到根加载器,若加载不到,则返回ClassNotFoundException异常。
    创建用户自定义的类加载器

    用户只需扩展java.lang.ClassLoader类,然后覆盖他的findClass(String name)方法即可,该方法根据参数指定的类名字,返回得Class对象引用。

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MyClassLoader extends ClassLoader {

	private String name;
	private String path="d:\\";
	private final String fileType=".class";
	public MyClassLoader(String name){
		super();
		this.name=name;
	}
	public MyClassLoader(ClassLoader parent,String name){
		super(parent);
		this.name = name;
	}
	public String toString(){return name;}
	public void setPath(String path){this.path=path;}
	public String getPath(){return path;}
	
	@Override
	protected Class findClass(String name){
		byte[] data=null;
		try {
			data = loadClassDate(name);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return defineClass(name,data,0,data.length);
	}
	//读入二进制文件
	private byte[] loadClassDate(String name) throws IOException{
		FileInputStream fis=null;
		byte[] data=null;
		ByteArrayOutputStream baos=null;
		//把包名转换为路径名,既com.bin.song转为com/bin/song
		name=name.replace("\\.", "\\\\");
		fis=new FileInputStream(new File(path+name+fileType));
		baos=new ByteArrayOutputStream();
		int ch=0;
		while((ch=fis.read())!=-1){
			baos.write(ch);
		}
		data=baos.toByteArray();
		return data;
	}
	public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
		MyClassLoader loader1=new MyClassLoader("loader1");//loader1加载器,父类为系统加载器
		loader1.setPath("d:\\myapp\\serverlib\\");
		MyClassLoader loader2 =new MyClassLoader(loader1,"loader2");//loader2加载器,父类是加载器loader1
		loader2.setPath("d:\\myapp\\clientlib\\");
		MyClassLoader loader3 =new MyClassLoader(null,"loader3");//loader3加载器,父类根加载器
		loader3.setPath("d:\\myapp\\otherlib\\");
		//test(loader1);
		test(loader2);
		test(loader3);
	}
	public static void test(ClassLoader loader) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
		Class objClass=loader.loadClass("Sample"); //加载Sample类
		Object obj=objClass.newInstance();
	}
}

然后还有2个调用类:

public class Sample {
	private int v1=1;
	public Sample(){
		System.out.println("sample is load by "+this.getClass().getClassLoader());
		new Dog();
	}
}

public class Dog {
public Dog(){
	System.out.println("Dos is load by "+this.getClass().getClassLoader());
}
}

把上面两个调用类编译成.CLASS文件放到   d:\\myapp\\serverlib\\和d:\\myapp\\otherlib\\下面

  然后把编译好的MyClassLoader 文件放到你的d:\myapp\syslib下面,以它为classpath,使得MyClassLoader类由系统类加载器。当执行loader2.loadClass("sample")时,先由上层所有父加载器加载Sample类,其父类loader1从d:\\myapp\\serverlib目录下成功加载了Sample类,loader1为Sample类的定义类加载器,当执行loader2.loadClass("sample")时,也是其父类尝试加载,loader3父加载器为根类加载器,无法加载Sample类,接着从loader3加载,成功加载,因此loader3为Sample类的定义类加载器及初始类的加载器。

     URLClassLoader类

    该类扩展了ClassLoader类,使用该类,不仅可以从本地文件系统中加载类,还可以从网上下载类。URLClassLoader类提供了以下构造方法:

         URLClassLoader(URL[] urls)  //父加载器为系统类加载

         URLClassLoader(URL[] ulrs,ClasssLoader parent) //parent为父加载器

     URLClassLoader将从urls从这个将从这个路径中加载类。

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class df {
	public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException{
		URL url=new URL("http://www.java.org/java");
		URLClassLoader loader = new URLClassLoader(new URL[]{url});
		Class object=loader.loadClass("Sample");
		Object obj=object.newInstance();
	}
}


三、 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值