jvm调优一番外:自定义类加载器和打破双亲委派机制

简述

本文主要是对jvm调优一的补充
jvm调优一

自定义类加载器

假设从H盘下面加载一个UserJvm类。
UserJvm代码

package entity;
public class UserJvm {
	private String name;
	public void say(){
		System.out.println("========This is UserJvm==========");
	}
}

在这里插入图片描述

自定义类加载器代码

package common;

import java.io.File;
import java.io.FileInputStream;

public class MyClassLoader extends ClassLoader{
	private String classPath;

	public MyClassLoader(String classPath) {
		this.classPath = classPath;
	}

	// name 全限定名
	private byte[] loadByte(String name) throws Exception {
		name = name.replaceAll("\\.","/");
		FileInputStream fis = new FileInputStream(classPath + File.separator + name +".class");
    	byte[] data = new byte[fis.available()];
		fis.read(data);
		fis.close();
		return data;
	}

	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		try {
			byte[] bytes = loadByte(name);
			Class<?> c = defineClass(name, bytes, 0, bytes.length);
			return c;
		} catch (Exception e) {
			e.printStackTrace();
			throw new ClassNotFoundException();
		}
	}

	@Override
	public Class<?> loadClass(String name) throws ClassNotFoundException {
		return super.loadClass(name);
	}
}

主要继承ClassLoader,ClassLoader有两个重要的方法一个是loadClass 这个方法主要是实现双亲委派机制的。另一个是findClass ,这个方法主要是加载类。
所以继承ClassLoader 重写findClass 方法即可。

测试代码

package test;

import common.MyClassLoader;

import java.lang.reflect.Method;

public class TestMyClassLoader {

	public static void main(String[] args) throws Exception{
		MyClassLoader myClassLoader = new MyClassLoader("H:/");
		Class<?> uc = myClassLoader.loadClass("entity.UserJvm");
		Object o = uc.newInstance();
		Method say = uc.getDeclaredMethod("say", null);
		say.invoke(o,null);
		System.out.println(uc.getClassLoader().getClass().getName());
	}
}

运行结果
在这里插入图片描述
注意:如果你的运行结果是
在这里插入图片描述
记得把idea里面的UserJvm 给删掉。不然根据双亲委派机制,AppclassLoader 加载过了 自定义的类加载器就不会再加载。
这里有个问题就是:自定义的类加载器的父加载器是哪个?
答案是:AppClassLoader。
自定义类加载器的顶层父类都是ClassLoader,不管你是继承了AppClassLoader 还是继承ExtClassLoader 。最终的顶层父类都是ClassLoader,所以在初始化自定义类加载器时,都是调用到ClassLoader的构造方法。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
根据一系列源码最终 通过l.getClassLoader(); 方法返回类加载器。在jvm调优一的时候 说过 这个方法会返回 AppClassLoader。所以自定义的类加载器的父加载器是AppClassLoader

打破双亲委派机制

UserJvm不变 并且在idea中有这个类。所以如果根据双亲委派如果AppClassLoader会去加载这个类,那么自定义的类加载器就不回去加载。那么按照上面的测试结果就应该是
在这里插入图片描述
自定义类加载器打破双亲委派机制

package common;
import java.io.File;
import java.io.FileInputStream;
public class MyClassLoaderNoP extends ClassLoader {
	private String classPath;
	public MyClassLoaderNoP(String classPath) {
		this.classPath = classPath;
	}
	// name 全限定名
	private byte[] loadByte(String name) throws Exception {
		name = name.replaceAll("\\.", "/");
		FileInputStream fis = new FileInputStream(classPath + File.separator + name + ".class");
		byte[] data = new byte[fis.available()];
		fis.read(data);
		fis.close();
		return data;
	}
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		try {
			byte[] bytes = loadByte(name);
			Class<?> c = defineClass(name, bytes, 0, bytes.length);
			return c;
		} catch (Exception e) {
			e.printStackTrace();
			throw new ClassNotFoundException();
		}
	}
	protected Class<?> loadClass(String name, boolean resolve)
			throws ClassNotFoundException {
		synchronized (getClassLoadingLock(name)) {
			Class<?> c;
			if (name.endsWith("UserJvm")){
				c = findLoadedClass(name);
				if (c == null) {
					c = findClass(name);
				}
			}else {
				c = super.loadClass(name, resolve);
			}
			return c;
		}
	}
}

这个代码和上面的自定义类加载器代码差不多。差别再去loadClass 方法 。之前说过了父类的loadClass方法是实现双亲委派的逻辑。所以我们需要实现自己loadClass方法。在这个loadClass方法中 我做了判断 如果类名是UserJvm的话 就通过这个类加载器直接加载。如果是其他类的话,就通过父类的loadClass方法,这样如果是其他类就走双亲委派机制;如果是UserJvm 这个类就走MyClassLoaderNoP这个类加载器。
看下运行的结果,
在这里插入图片描述
现在就是自定义的类加载器。这就是成功了。

tomcat如何打破双亲委派机制

以Tomcat类加载为例
我们思考一下:Tomcat是个web容器, 那么它要解决什么问题:

  1. 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是 独立的,保证相互隔离。
  2. 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。
  3. web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的 类库和程序的类库隔离开来。
  4. web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中 运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。

所以Tomcat 如果使用默认的双亲委派类加载机制行不行?答案是不行的
第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认 的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
第三个问题和第一个问题一样。
我们再看第四个问题,我们想我们要怎么实现jsp文件的热加载,jsp 文件其实也就是class文 件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp 是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想 到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载 器。重新创建类加载器,重新加载jsp文件。

在这里插入图片描述
tomcat的几个主要类加载器:
commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容 器本身以及各个Webapp访问; catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不 可见;
sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有 Webapp可见,但是对于Tomcat容器不可见; WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前 Webapp可见,比如加载war包里相关的类,每个war包应用都有自己的

tomcat 为了实现隔离性,每个 webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器,打破了双亲委派机制。
用一个案例来实现tomcat打破双亲委派机制。
改下UserJvm代码
在这里插入图片描述
把它放到
在这里插入图片描述
在修改下UserJvm代码
在这里插入图片描述
把它放到
在这里插入图片描述
自定义的加载器用的是MyClassLoaderNoP 。代码在打破双亲委派机制那一部分。
测试代码

package test;

import common.MyClassLoaderNoP;

import java.lang.reflect.Method;

public class TestTomcatClassLoader {

	public static void main(String[] args) throws Exception{
		MyClassLoaderNoP myClassLoader1 = new MyClassLoaderNoP("H:/test/");
		Class<?> uc = myClassLoader1.loadClass("entity.UserJvm");
		Object o = uc.newInstance();
		Method say = uc.getDeclaredMethod("say", null);
		say.invoke(o,null);
		System.out.println(uc.getClassLoader().getClass().getName());
		System.out.println("========================================");

		MyClassLoaderNoP myClassLoader2 = new MyClassLoaderNoP("H:/test1/");
		Class<?> uc1 = myClassLoader2.loadClass("entity.UserJvm");
		Object o1 = uc1.newInstance();
		Method say1 = uc1.getDeclaredMethod("say", null);
		say1.invoke(o1,null);
		System.out.println(uc.getClassLoader().getClass().getName());
		System.out.println("========================================");

	}
}

假设有两个服务部署到Tomcat中,每个服务都有一个WebappClassLoader 负责去加载。myClassLoader1 假设就是一个WebappClassLoader ,myClassLoader2 就是另一个WebappClassLoader 。如果根据双亲委派机制,只要全限定名相同,那么jvm就只有一个class。然后我们看看运行结果
在这里插入图片描述
结果明显就是打破了双亲委派机制,因为我们测试用到的自定义加载器MyClassLoaderNoP是已经实现了打破双亲委派机制。

如果我们使用第一个案例中的自定义加载器MyClassLoader在看看运行结果。
在这里插入图片描述
这就是双亲委派机制,jvm中只存在一个UserJvm。并且是由AppClassLoader加载的。

双亲委派机制到此就结束了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值