一:加载过程

大致的流程
image.png
类加载的几个步骤
加载—>验证—>准备—> 解析—> 初始化—> 使用—>卸载

  1. 加载:把字节码读取进内存,加载的时机有:调用main,new Demo(), 会生成一个java.lang.Class的对象
    作为方法去这个类的各种数据的访问入库,注意:单纯申明变量是不会加载的,如:Demo demo = null;
  2. 验证:校验字节码的正确性
  3. 准备:给类的静态变量分配内存,赋默认值
  4. 解析:符号引用替换为直接引用,就是将完成,变量、对象,与内存地址完成映射(静态链接)
    动态链接是在程序运行期间,完成符号引用的
  5. 初始化:对类的静态变量初始化为代码声明的值,执行静态代码

注意:加载是懒加载的方式

二:类加载器

  • 引导加载器 bootstrapLoader :加载JRE lib下的jar
  • 扩展加载器 extClassloader :加载JRE lib 下的ext 包中的jar
  • 应用程序加载器 appClassLoader : classPath下的类包,就是用户开发的jar
  • 自定义加载器:用户可以自己定义加载自定义包下的jar

三:双亲委派

image.png
为什么双亲委派:
1:沙箱安全:核心类防止为篡改
2:唯一性,保证加载类的唯一性
3:绝大部分的代码是由应用程序类加载器加载
全盘委托机制
双亲委派的代码

  1. //ClassLoader的loadClass方法,里面实现了双亲委派机制
  2. protected Class<?> loadClass(String name, boolean resolve)
  3. throws ClassNotFoundException
  4. {
  5. synchronized (getClassLoadingLock(name)) {
  6. // First, check if the class has already been loaded
  7. 检查当前类加载器是否已经加载了该类
  8. Class<?> c = findLoadedClass(name);
  9. if (c == null) {
  10. long t0 = System.nanoTime();
  11. try {
  12. if (parent != null) {
  13. //如果当前加载器父加载器不为空则委托父加载器加载该类
  14. c = parent.loadClass(name, false);
  15. } else {
  16. //如果当前加载器父加载器为空则委托引导类加载器加载该类
  17. c = findBootstrapClassOrNull(name);
  18. }
  19. } catch (ClassNotFoundException e) {
  20. // ClassNotFoundException thrown if class not found
  21. // from the non-null parent class loader
  22. }
  23. if (c == null) {
  24. // If still not found, then invoke findClass in order
  25. // to find the class.
  26. long t1 = System.nanoTime();
  27. // 调用 URLClassLoader的findClass在加载器的类路径中查找并加载该类
  28. c = findClass(name);
  29. // this is the defining class loader; record the stats
  30. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  31. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  32. sun.misc.PerfCounter.getFindClasses().increment();
  33. }
  34. }
  35. if (resolve) {
  36. resolveClass(c);
  37. }
  38. return c;
  39. }
  40. }

四:自定义加载器

1:继承 java.lang.ClassLoader
2:重写 findClass方法

自定义类加载器code

  1. public class MyClassLoaderTest {
  2. static class MyClassLoader extends ClassLoader {
  3. private String classPath;
  4. public MyClassLoader(String classPath) {
  5. this.classPath = classPath;
  6. }
  7. private byte[] loadByte(String name) throws Exception {
  8. name = name.replaceAll("\\.", "/");
  9. FileInputStream fis = new FileInputStream(classPath + "/" + name
  10. + ".class");
  11. int len = fis.available();
  12. byte[] data = new byte[len];
  13. fis.read(data);
  14. fis.close();
  15. return data;
  16. }
  17. protected Class<?> findClass(String name) throws ClassNotFoundException {
  18. try {
  19. byte[] data = loadByte(name);
  20. //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
  21. return defineClass(name, data, 0, data.length);
  22. } catch (Exception e) {
  23. e.printStackTrace();
  24. throw new ClassNotFoundException();
  25. }
  26. }
  27. }
  28. public static void main(String args[]) throws Exception {
  29. //初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
  30. MyClassLoader classLoader = new MyClassLoader("D:/test");
  31. //D盘创建 test/com/tuling/jvm 几级目录,将User类的复制类User1.class丢入该目录
  32. Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
  33. Object obj = clazz.newInstance();
  34. Method method = clazz.getDeclaredMethod("sout", null);
  35. method.invoke(obj, null);
  36. System.out.println(clazz.getClassLoader().getClass().getName());
  37. }
  38. }

打破双亲委派机制需要注意每个bean的父类都是Object的加载情况

  1. public class MyClassLoaderTest {
  2. static class MyClassLoader extends ClassLoader {
  3. private String classPath;
  4. public MyClassLoader(String classPath) {
  5. this.classPath = classPath;
  6. }
  7. private byte[] loadByte(String name) throws Exception {
  8. name = name.replaceAll("\\.", "/");
  9. FileInputStream fis = new FileInputStream(classPath + "/" + name
  10. + ".class");
  11. int len = fis.available();
  12. byte[] data = new byte[len];
  13. fis.read(data);
  14. fis.close();
  15. return data;
  16. }
  17. protected Class<?> findClass(String name) throws ClassNotFoundException {
  18. try {
  19. byte[] data = loadByte(name);
  20. return defineClass(name, data, 0, data.length);
  21. } catch (Exception e) {
  22. e.printStackTrace();
  23. throw new ClassNotFoundException();
  24. }
  25. }
  26. /**
  27. * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
  28. * @param name
  29. * @param resolve
  30. * @return
  31. * @throws ClassNotFoundException
  32. */
  33. protected Class<?> loadClass(String name, boolean resolve)
  34. throws ClassNotFoundException {
  35. synchronized (getClassLoadingLock(name)) {
  36. // First, check if the class has already been loaded
  37. Class<?> c = findLoadedClass(name);
  38. if (c == null) {
  39. // If still not found, then invoke findClass in order
  40. // to find the class.
  41. long t1 = System.nanoTime();
  42. if(!name.startsWith("就是如果加载到父类,就用原来的类加载器")){
  43. c = this.getParent.loadClass(name);
  44. }else{
  45. c = findClass(name);
  46. }
  47. // this is the defining class loader; record the stats
  48. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  49. sun.misc.PerfCounter.getFindClasses().increment();
  50. }
  51. if (resolve) {
  52. resolveClass(c);
  53. }
  54. return c;
  55. }
  56. }
  57. }
  58. public static void main(String args[]) throws Exception {
  59. MyClassLoader classLoader = new MyClassLoader("D:/test");
  60. //尝试用自己改写类加载机制去加载自己写的java.lang.String.class
  61. Class clazz = classLoader.loadClass("java.lang.String");
  62. Object obj = clazz.newInstance();
  63. Method method= clazz.getDeclaredMethod("sout", null);
  64. method.invoke(obj, null);
  65. System.out.println(clazz.getClassLoader().getClass().getName());
  66. }
  67. }

五:Tomcat打破双亲委派

原因:

  1. web下可能为部署两个应用程序,依赖同一个jar 的不同版本,保证隔离
  2. 同一个web容器的相同类库可以共享
  3. web依赖的库,不能与应用的库混淆
  4. web容器支持jsp修改,不用重启