`
22cgreen
  • 浏览: 53511 次
  • 性别: Icon_minigender_2
  • 来自: 广州
社区版块
存档分类
最新评论

类是怎样被执行的研究

阅读更多
1)类是以编译后的字节码.class类型存于硬盘或其它存储介质上.我们打开被编译的文件也能看出一些
东东,上面的部分看不懂,但从能看得懂的来分析有几下几点
1、class文件中,把(.)点号转成了(/),也就是真实的路径。我们在类中定义private Pstring good;
在class中变为: good dlp/oa/pub/Pstring 很显然java文件在编译后,会把各此的属性配置全路径,
也就是虽然我们在程序中用Pstring good,然后import dlp.oa.pub.Pstring 但实际上编译成class后
会变成 good dlp/oa/pub/Pstring
2、文件存在自身的路径,还有继承类的路径
3、有LineNumberTable LocalVariableTable this /dlp/oa/test/Three
4、有源码名称。

2)类文件.class文件是怎样进入内存的。
我们知道JVM有几个类加载器,事实上所有的类文件都是通过加载器进行加载的,JVM中有默认的几个类加载器
1、加载核心类的加载器,2、加载ext目录的加载器,3、加载claspath目录的加载器。
以tomcat为例,看tomcat是怎样启动的。tomcat中有一个startup.bat 批处理文件,这个文件会启动另一个批处理文件
catalina.bat 而这个批处理会设置set CLASSPATH=%CLASSPATH%;%CATALINA_HOME%\bin\bootstrap.jar
也就是把bootstrap.jar所在目录加入classpath中,这样就可以通过jre来运行bootstrap.jar,我们看到在这个包中的
MANIFEST.MF的文件内容如下:
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.6.5
Created-By: 1.5.0_06-b05 (Sun Microsystems Inc.)
Main-Class: org.apache.catalina.startup.Bootstrap
Specification-Title: Catalina
Specification-Version: 6.0
Class-Path: commons-daemon.jar commons-logging-api.jar tomcat-juli.jar
  tomcat-coyote.jar
  这里Main-Class: org.apache.catalina.startup.Bootstrap 指定了主函数所在的类,
  tomcat在最后的批处理中执行了一个命令如下:
  start "Tomcat" "C:\Java\jdk1.5.0_16\bin\java"  -Djava.util.logging.manager=org.a
pache.juli.ClassLoaderLogManager -Djava.util.logging.config.file="D:\apache-tomc
at-6.0.14\conf\logging.properties"   -Djava.endorsed.dirs="D:\apache-tomcat-6.0.
14\endorsed" -classpath "C:\Java\jdk1.5.0_16\lib\tools.jar;D:\apache-tomcat-6.0.
14\bin\bootstrap.jar" -Dcatalina.base="D:\apache-tomcat-6.0.14" -Dcatalina.home=
"D:\apache-tomcat-6.0.14" -Djava.io.tmpdir="D:\apache-tomcat-6.0.14\temp" org.ap
ache.catalina.startup.Bootstrap  start
从中可以看出就是通过设置classpath,然后用java org.apache.catalina.startup.Bootstrap
设置classpath是为了找到包D:\apache-tomcat-6.0.14\bin\bootstrap.jar 及主方法类:java org.apache.catalina.startup.Bootstrap
如此可知tomcat的启动主函数类,是通过java.exe运行的,也就是通过java的类加载器加载的。
其实可以发现其实main方法也是java的一种约定,就像接口定义一样,以便于加载他的程式能知道从那里进入进行操作。
我们可以认为java.exe 执行一个类或jar是这样的,他先用类加载器进行加载,并得到class类型的引用,因为main方法是类方法
不用实例化,因此他就用反射机制取main方法,如果存在就进入执行,这也是java运行其它程式的一种约定,而tomcat实际上也就有一个
类加载器来加载新的类来运行,类加载器知道加载什么类,但并不知道应运行什么方法,我们自己开发的程式可能知道,如:
A a = new A();
a.show();
很自然加载类A,然后实例化,最后从show()方法进入执行。但是对于tomcat运行servlet时,他并不知道从那个方法进入,如果系统和用户编程时
没有事先约定,tomcat加载servlet类后,他从什么地方去执行呢?因此我们常用必须继际某些接口或类,或实现虚方法的情况,这些从系统画出
的框框要编程者去准遵守。

因此在servlet中我们必须继承一些类,然后要实现get或post方法之类。这些就是tomcat知道从约定的方法进入执行。

3)类是怎样被加载器加载进系统的。
我们还以tomcat为例,tomcat也有多个类加载器,他们中有些是加载tomcat的一些类库和包,位于tomcat的lib目录下,但我们关心的是
web类(也就自己开发的J2EE)应于的类加载器,这个类加载器加载的目录指向相应项目的class目录,如:
D:\apache-tomcat-6.0.14\webapps\dlp_oa\WEB-INF\classes
我们自定义类加载器时主要是实现findClass方法,
public Class findClass(String name){
  byte[] data=loadClassData(name);
  return defineClass(name,data,0,data.length);
}
很显然每个类加载器都有这么一个方法,其目的就是找到指定的class文件并读取然后转成byte[]字节流,最后用defineClass方法把字节流
转换成class的实例。

defineClass方法属性本地方法,非java写成,class是字节码,很显然,JVM是按字节解释执行。
在测试中我现,我自己开发的一个类加载器,他的类查找顺序,也是委托的方式,从最顶层类加载哭进行加载,
在测试中我把类放在java中的ext目眼中显示如下:
D:\java>C:\Java\jdk1.5.0_16\bin\java FileClassLoader
One
sun.misc.Launcher$ExtClassLoader
显示发现加载器并非自定义的加载器,而是上层的加载器 ExtClassLoader,在自定义的类加载器中如果不指定父加载器,默认情况是系统类加载
器,也就是appClassLoader.

4)类加载后是怎样区分的。
类加载器被加载后,有一个列表来记录类的唯一标示,也就是类的全名(包含包名+类加载器实例)来标示这个类的类型。

5)类加载器要注意的几点。
  1、类加载器的父加载器,不是指继承父类的实例,也不是加载自己身的加载器,而是在编程时设定的。其实一般继承ClassLoader的加载器
  的父加载器为AppClassLoader
  2、系统中的ExtClassLoader与AppClassLoader都是Launcher的内部类,位于:sun.misc 包下,并都继承java.net.URLClassLoader,类
  java.net.URLClassLoader类继承java.security.SecureClassLoader,而java.security.SecureClassLoader又继承ClassLoader
  从源码分所知AppClassLoader,把this.val$extcl,一直使用super来执行父类构造方法,最后在类,ClassLoader,把值传给了this.parent
  protected ClassLoader(ClassLoader paramClassLoader)
  {
    SecurityManager localSecurityManager = System.getSecurityManager();
    if (localSecurityManager != null)
      localSecurityManager.checkCreateClassLoader();
    this.parent = paramClassLoader;
    this.initialized = true;
  }
  其实parent也是一个ClassLoader类型,
  3、我在继承ClassLoader时,执行父类的带参构造方法就能把自定义类加载器的父加载器进行设定。
 
6)类是怎样被执行的。
java的类当然是JVM虚拟机去执行,类被加载器加载时,以文件读取的方式进入内存,当然java读取文件,也是依赖于本地操作系统的API进行
读取的,读取文件后转换成byte[],也就字节数组,最后JVM读取并解释这些字节码,并转换成相应本地系统的API调用,而完成执行的过程。
从而可知可以用勾子程序拦截java的API调用。当然jvm中维护着一个表,类及类加载器的列表,那是JVM的规则,JVM使用这套字节码规则来
转换成api调用和得到相应参数和数据结果等。

7)类加载器为什么不会重复加载类?
其它在ClassLoader中的loadClass方法中有一段代码是用于检测此类是否已加载,
Class localClass = findLoadedClass(paramString);
最后调用了。
private final native Class findLoadedClass0(String paramString);
不过此方法是一个本地方法,并非java实现。
如此看来,如果自定义类加载器不重写loadClass方法,是很难重新加载一个新版本的同样路径和名称的类的。

8)类的加载方式。
类的加载有下面的2种方式:
1、Class c1 = Class.forName ("java.lang.String");

2、Class a = Oa120.class;
Two.class.getClassLoader().loadClass("One");

9)怎样重新加载新版本类
package com.chen.test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


public class Fcl extends ClassLoader {
public static final String drive="D:\\temp\\";
public static final String fileType=".class";

public Class findClass(String name){
  byte[] data=loadClassData(name);
  return defineClass(name,data,0,data.length);
}

private byte[] loadClassData(String name) {
  FileInputStream fis=null;
  byte[] data = null;
  try {
   fis = new FileInputStream(new File(drive+name+fileType));
   ByteArrayOutputStream baos=new ByteArrayOutputStream();
   int ch=0;
   while((ch=fis.read())!=-1){
    baos.write(ch);
   }
   data=baos.toByteArray();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  return data;
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class localClass = null;
try
    {
      if (this.getParent()!= null)
        localClass = this.getParent().loadClass(name);
      else
        localClass = findBootstrapClass(name);
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      localClass = findClass(name);
    }
    return localClass;
}

private native Class findBootstrapClass(String paramString)
throws ClassNotFoundException;

}

注:主要是重写loadClass方法,不要其执行Class localClass = findLoadedClass(paramString);这行代码。在测试中发现
在加载类时,系统会每次都会去加载class文件。

10)类加载器测试(1)
------------------------------------------------------
在测试过程中,我发现重启后tomcat后,类加载器的地址是一样的,如:
vvvvvvvvv
com.chen.test.Fcl@13f136e
dddddddddddddddddddd
WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@12b3374
每次都是13f136e和12b3374,是不是程序在编辑时已确定了这个地址呢?有待研究。
如果重新请求,因为com.chen.test.Fcl这个加载器是用new的方法产生了一新的,因此后地址发生了变化,如:
vvvvvvvvv
com.chen.test.Fcl@14d5bc9
dddddddddddddddddddd
WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@12b3374

如此可看出StandardClassLoader还是同一个。我把自定义的类加载器改成了static,并只实例化一次,结果发现
第一次的com.chen.test.Fcl@18f7386 地址发生了变化,不过这些定义为static的后,多次调用地址没有发生变化
也就说明只有一个实例,同时测试时发现这次没有去加载新版本的Class了,而是用系统已存在class模板。

vvvvvvvvv
com.chen.test.Fcl@18f7386
dddddddddddddddddddd
WebappClassLoader
  delegate: false
  repositories:
    /WEB-INF/classes/
----------> Parent Classloader:
org.apache.catalina.loader.StandardClassLoader@12b3374

  类加载器测试(2)
  我把loadClass重写,同时不管有没有加载过,一样执行findClass,这时发现第一次加载时没问题,第二次加载时出错,
  说“不能重复加载一个同样的类”,别外在测试时发现当代码修改过后Fcl@18f7386后面地址会发生变化
  测试发现如果我们不用
  if(fl==null)
{
fl = new Fcl();
}
而是每次都实例化的话,加载器的地址也会发生变化。
 
11)tomcat的在项目发布时,类加载器没有发生变化,他是怎样重新加类的呢?
---------------------------------------------------------------------------------------
我自已写的类加载器,在重写loadClass方法时发现当加载器相同的情况下,无法加载同样的类,但tomcat是怎样办到的呢?
很显然他要去掉内存中存在模板。
0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics