没有我,你还想执行java程序?

作者:立体的萌

来源:程序员架构原创作品。

引子

咱不卖关子,这个让java程序执行的关键大佬叫做:ClassLoader(类加载器),别看人家叫这个名字,但是加载的不是类,而是class文件

在这里是我说点题外话,我在学完java之后跟着网上的视频复习,故意没用IDE,而是用的是记事本(Editplus)+DOS命令的方式。

这种方式必须手动编译运行,这样做的好处就是更加底层,不像IDE每次运行的时候按个debug就完事儿了,根本就不知道背后发生了些啥,java程序必须先编译然后再运行,编译之后形成的就是class文件:

形成class文件之后再运行,就可以得到结果了,那么,编译成class文件之后谁来负责解析这火星文?当然是JVM啦!那class文件又是怎么跑到JVM里的?你要知道JVM可不会主动去找的,这就要靠ClassLoader了。

ClassLoader主要都干些啥?

刚才说了把class文件加载到JVM中,那么这里面就涉及到一个问题:JVM那么大,到底该去哪儿?

ClassLoader是一种父优先的等级加载机制,如果把class比喻成VIP,那么JVM中对应的执行class文件的就是VIP大厅,不同等级的VIP是必须要进入到不同的VIP大厅的,并且一个VIP去一个VIP大厅而且也只能去一次。

VIP大厅里可以有一个也可以多个VIP,但是一个VIP不能既属于这个VIP大厅又属于那个VIP大厅,也就是说VIP和VIP大厅是一对多的关系,ClassLoader还得为不知去哪里的VIP指路,同时还要防止有的VIP浑水摸鱼去更高级别的VIP大厅。

ClassLoader是怎么干活的?

这么重要而又艰难的任务 ClassLoade是如何解决的呢?

第一步:如果一个VIP到达了一个VIP大厅,那么VIP大厅会首先检查这个VIP是否已经被自己接待过,如果是,当然是拒绝,如果不是的话也不能急着放行,因为VIP根本不知道自己该去哪一个VIP大厅,很有可能会出错!

第二步:向上级询问这个VIP是不是应该去级别更高的VIP大厅,那么上级也会先检查是否接待过,是的话直接拒绝并把结果反馈下去,如果不是就继续向上级的上级询问。

接着上级的上级继续重复这些检查,直到一个级别的VIP大厅说可以接待,并且也不能比他(”他“指的就是说可以接待的)低一级的接待以及比他上一级的大厅说这个VIP级别不够,那么,这个VIP就会进入到刚才确定的VIP大厅,同时也会标记上相应的等级。

麻烦是麻烦了点,不过可以保证安全。

嗯,有点儿讲究

JVM主要有以下几种ClassLoader:

第一种:Booststrap ClassLoader

这种的是为大厅服务的更高级的大厅,如同我们在反射学的总领一切类的Class,这个类,别人是访问不到的,必须是JVM来控制,**因为它主要加载JVM自身工作需要的类!**这个类是不用遵循刚才说的繁琐的规则的,而且没有父加载器也没有子加载器。

第二种:AppClassLoader

这才是为VIP服务的大厅,刚才说的VIP大厅其实就是指的他,他的父类是ExtClassLoader,可以加载System.getProperty(“java.class.path”)目录下的所有的类,如果要实现自己的类加载器的话,父类必须是AppClassLoader!

第三种:ExtClassLoader

这个有点特殊,怎么说呢,他有点类似于与VIP大厅合作的VIP,主要加载System.getPorperty(“java.ext.dirs”)目录下的类。

ClassLoader手底下的兵

让我们来JVM大楼来参观下

JVM把Class文件加载到内存中主要有两种方式:

第一种,显式加载:也就是通过调用ClassLoad来加载

第二种,隐式加载:顾名思义,也就是不通过调用ClassLoad来加载需要的类,而是通过JVM来自动加载

这两种方式可以混合着使用

那么,还是开头说的那个问题,Class是怎么被加载到JVM中的?一图胜千言!

加载完了并不意味着工作结束了,还有验证解析

  1. 验证:类装入器对类的字节码的验证是为了保证格式正确

  2. 准备:主要是准备好类所需要的字段、函数以及实现接口所必须的数据结构

  3. 解析:类装入器需要装入类所引用得其他类

这些工作完成之后就可以初始化Class对象,也就是在类中包含的所有的静态初始化器都会被执行。

不自己动手实现一个怎么好意思结束?

我们已经知道ClassLoader可以做什么了:

  1. 在自定义路径下查找自定义的Class类文件

  2. 对于要加载的类走特殊处理,比如:加密、解密

做程序员的谁还没自己手写过几个技术?我们这就自己实现一个ClassLoader。

1.在自定义路径下查找自定义的Class类文件

public class PathClassLoader extends ClassLoader{
private String classPath;
public PathClassLoader(String calssPath){
this.classPath=classPath;
}
protected Class<?>findClass(String name)throws ClassNotFoundException{
if(packageName.startsWith(name)){
byte[] classData=getData(name);
if(classData=null){
throw new ClassNotFoundException();
}else{
return defineClass(name,classData,0,classData.length);
}
}else{
return super.loadClass(name);
}
}
private byte[] getData(String className){
String path=classPath+File.seperatorChar+
className.replace('.',File.separatorChar)+'.class';
try{
InputStream is=new FileInputStream(path);
ByteArrayOutputStream stream=new ByteArrayOutputStream();
byte[] buffer=new byte[2048];
int num=o;
while((num=is.read(buffer))!=-1){
stream.write(buffer,0,num);
}
return stream.toByteArray();
}catch(IOException e){
e.printStackTrace();
}
return null;
}
}

2.对于要加载的类走特殊处理,比如:加密、解密

public class NetClassLoader extends ClassLoader{private String classPath;private String packageName='net.lmc.classloader';public NetClassLoader(String classPath){this.classPath=classpath;}protected Class<?> findClass(String name) throws ClassNotFoundException{Class<?> aClass= findLoadedClass(name);if(aClass!=null){return aClass;}if(packageName.startsWith(name)){byte[] classData=getData(name);if(classData==null){throw new ClassNotFoundException();}else{return defineClass(name,classData,0,classData.length);}}else{return super.loadClass(name);}}private byte[] getData(String className){String path=classPath+File.separatorChar+className.replace('.',File.separatorChar+'.class');try{URL rul=new URL(path);InputStream is=url.openStream();ByteArrayOutoutStream stream =new ByteArrayOutputStream();byte[] buffer=new byte[2048];int num=0;while(Exception e){e.printStackTrace();}return null;}private byte[] decode(byte[] src){byte[] decode=null;//对src字节码进行解码处理return decoce;}}

结束之前,我问一句,你可知道ClassLoader还有热部署这回事?

JVM在加载类之前都会调用findLoadedClass()函数来检查是否能返回类实例,同时还会判断一下一个类是否是同一个类:

一个是看这个类的类名和包名是否一样,

再一个就是看类加载器是否是同一个(这里主要是看实例)

public class ClassReloader extends ClassLoader{
private String classPath;
String classname='compile.lmc';
public ClassReloader(String classPath){
this.classPath=classPath;
}
protected Class<?> findClass(String name) throws ClassNotFoundException{
byte[] classData=getData(name);
if(classData==null){
throws new ClassNotFoundException();}
}else{
return defineClass(classname,classData,0,classData.length);
}
}
private byte[] getData(String className){
String path=classPath+className;
try{
InputStream is=new FileInputStream(path);
ByteArrayOutoutStream stream=new ByteArrayOutputStream();
byte[] buffer =new byte[2048];
int num=0;
while((num=is.read(buffer))!=-1){
stream.write(buffer,0,num);
}
return stream.toByteaArray();
}catch(IOException e){
e.printStackTrace();
}
return null;
}
public  static void main(String [] args)[
try{
    //这里是一句伪代码,明白意思就好
String path='路径';
    
ClassReloader reloader= new ClassReloader(path);
Class r= reloader.findClass('lmc.class');
System.out.println(r2.newInstance());
}catch(Exception e){
e.printStackTrace();
}
}
}

结束了?

做Java开发的都知道java有个痛点:每修改一个类就必须重启一次。

java的优势是基于共享对象这个机制的,也就是通过保存并持有对象的状态而省去类信息的重复创建和回收,也就是重新的信息咱们来个合并同类项,如果能动态加载类了,还能实现对象的平滑过度吗?

JVM中对象只有一份,理论上可以直接替换这个对象,做起来并不难吧,JVM只要更新一下java栈中所有对原对象的引用关系就行,好了,你够了!别忘了对象的引用关系只能是对象的创建者持有和使用,JVM没那个权力干涉,JVM只知道对象的编译时类型,对于对象的运行时类型一无所知!

那是不是就没办法了?办法还是有的,办法总比问题多嘛,可以先不保存对象的状态,对象被创建使用后就被释放掉,等到下次修改的时候,对象就是新的了,听起来是不是很耳熟?对了,这就是JSP!其实不仅仅是JSP这么做,所有的解释型语言都这样做!

总结

java的“write one,run anywhere ”的超级优点能够实现,ClassLoader可以说功不可没,我没感觉不到他的存在,但他却默默地为我们提供着便捷!

关于作者

笔名立体的萌,是个工作还不到一年就辞职考研的的菜鸟,上学学习的是java,但是工作用的是C#,C#用途远远没有java范围广,再加上java是我的母语,所以很像回归java,不仅仅是提升技术能力,也有往大数据等高端领域发展的想法。

请作者吃糖

本文作者:立体的萌

(0)

相关推荐