Tomcat学习笔记四--- tomcat类加载机制

Jvm的类加载机制

java应用程序在运行的过程中,会将使用到的类加载到jvm中。
完成的加载过程有如下几步:

  • 加载 :

从磁盘读入字节码文件(磁盘io),类被使用时才会加载。例如:new Xialu(),new 对象时。

  • 验证 :

校验字节码文件的正确性。例如:文件格式验证、元数据验证、字节码验证、符号引用验证等。

  • 准备:

给类的静态变量分配内存,并赋予默认值。

  • 解析 :

将符号引用替换为直接引用,此时会把一些静态方法(符号引用,比如
main()方法)替换为指向数据所在内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成)

  • 初始化:

将类的静态变量初始化为指定的值,并执行静态代码块(执行顺序为代码顺序)。

  • 使用:

使用加载完成的对象

  • 卸载

gc回收对象(非实时,由jvm控制)

jvm类加载过程

Jvm类加载器

JVM 的类加载机制中有一个非常重要的⻆色叫做类加载器(ClassLoader),上面的加载流程就是由类加载器完成的。
java中主要有以下几种类加载器:

  • 引导类加载器 BootstrapClassLoader:

负责加载JRE的lib目录下的核心类库,例如rt.jar等

  • 扩展类加载器 ExtClassLoader:

负责加载扩展库JAVA_HOME/lib/ext目录下的jar中的类,如classpath中的jre,javax.*或者java.ext.dir指定位置中的类

  • 系统类加载器 AppClassLoader:

负责加载ClassPath路径下的类包,主要就是加载自己定义的那些类

  • 自定义加载器 :

负责加载自定义路径下的类

Jvm类加载器的父子(委托)关系

什么是双亲委派机制

当某个类被加载时,当前的类加载器会先把这个任务委托给父类加载器,递归这个操作,只有父类加载器找不到这类的时候,才会自己去加载这个类。
例如加载Xialu对象 AppClassLoader会先把这个任务交给ExtClassLoader,ExtClassLoader又会上交给BootstrapClassLoader,因为BootstrapClassLoader负责加载核心类库的类,ExtClassLoader负责加载扩展类,因此在它们负责的路径下是找不到这个类的,最后返回给AppClassLoader完成加载。

为什么要设计双亲委派机制

  • 防止类被重复加载:

向上委托的时候会判断该类是否已经被加载过了,如果父类加载器已经加载了,就不用再重复加载了.

  • 防止核心Api被随意篡改:

例如我们自己写的java.lang.String就不会被加载.

Jvm类加载器源码

直接来看类加载器的loadClass方法:java.lang.ClassLoader#loadClass(java.lang.String, boolean)

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
  • getClassLoadingLock;对目标类进行加锁,防止多个线程同时加载同一个类,造成重复加载。
  • findLoadedClass(name); 判断目标类是否已经被加载,还未被加载则返回null。
  • 双亲委派关键逻辑:
if (parent != null) {
       c = parent.loadClass(name, false);
} else {
      c = findBootstrapClassOrNull(name);
}

可以看到在这里获取当前类加载器的父类加载器,如果不等于null则由父类加载器加载目标类,并且父类加载器也同样实现了该逻辑。
如果为null则调用findBootstrapClassOrNull方法。
那么什么时候或者什么样的类加载器,父类加载器才会为空呢?



从图中可以看到BootstrapClassLoader为空,也就是扩展类加载器的上级加载器为null,这是因为启动类加载器不是由java编写的,所以在jvm中为null.
回到上面的源码,也就是当目标类委托到扩展类加载器之后继续向上委托时就会执行findBootstrapClassOrNull方法。

  • c = findClass(name);当目标类c为null是,则会调用findClass查找该类再自己的加载路径范围之类。如果没有找到,则返回给下级类加载器。
简单总结就是先找父亲加载,不行就给儿子加载。#####

Tomcat的类加载机制

tomcat不是使用的双亲委派加载机制,这主要是因为tomcat是一个web服务器需要支持部署多个应用程序,不同的应用程序可能会依赖同一个第三方类 库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的 类库都是独立的,保证相互隔离。

Tomcat类加载器

tomcat类加载器.png
  • commonClassLoader

通用类加载器加载Tomcat使用以及应用通用的一些类,位于CATALINA_HOME/lib下, 比如servlet-api.jar

  • catalinaClassLoader

omcat容器私有的类加载器,加载路径中的class对于Webapp不可见

  • sharedClassLoader

各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见

  • WebappClassLoader

各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;

每个 webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器,打破了双 亲委派机制。

Tomcat类加载器源码

直接来看WebappClassLoaderBase的loadClass方法:org.apache.catalina.loader.WebappClassLoaderBase#loadClass(java.lang.String, boolean)

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        synchronized (getClassLoadingLock(name)) {
            if (log.isDebugEnabled()) {
                log.debug("loadClass(" + name + ", " + resolve + ")");
            }
            Class<?> clazz = null;

            // Log access to stopped class loader
            checkStateForClassLoading(name);

            // (0) Check our previously loaded local class cache
            /**
             * 检查该类是否已经被webapp类加载器加载。
             */
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled()) {
                    log.debug("  Returning class from cache");
                }
                if (resolve) {
                    resolveClass(clazz);
                }
                return clazz;
            }

            // (0.1) Check our previously loaded class cache
            /**
             * 该方法直接调用findLoadedClasso本地方法,findLoadedClass0方法会检查jvm缓存中是否加载过此类(jvm内存)
             */
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled()) {
                    log.debug("  Returning class from cache");
                }
                if (resolve) {
                    resolveClass(clazz);
                }
                return clazz;
            }

            // (0.2) Try loading the class with the system class loader, to prevent
            //       the webapp from overriding Java SE classes. This implements
            //       SRV.10.7.2
            String resourceName = binaryNameToPath(name, false);

            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
                // Use getResource as it won't trigger an expensive
                // ClassNotFoundException if the resource is not available from
                // the Java SE class loader. However (see
                // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
                // details) when running under a security manager in rare cases
                // this call may trigger a ClassCircularityError.
                // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
                // details of how this may trigger a StackOverflowError
                // Given these reported errors, catch Throwable to ensure any
                // other edge cases are also caught
                URL url;
                if (securityManager != null) {
                    PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
                    url = AccessController.doPrivileged(dp);
                } else {
                    url = javaseLoader.getResource(resourceName);
                }
                tryLoadingFromJavaseLoader = (url != null);
            } catch (Throwable t) {
                // Swallow all exceptions apart from those that must be re-thrown
                ExceptionUtils.handleThrowable(t);
                // The getResource() trick won't work for this class. We have to
                // try loading it directly and accept that we might get a
                // ClassNotFoundException.
                tryLoadingFromJavaseLoader = true;
            }

            /**
             * 尝试通过系统类加载器(AppClassLoader)加载类,防止webapp重写jdk的类。
             * 例如:webapp加载一个java.lang.String类,是不被允许的。
             */
            if (tryLoadingFromJavaseLoader) {
                try {
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        if (resolve) {
                            resolveClass(clazz);
                        }
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }

            // (0.5) Permission to access this class when using a SecurityManager
            if (securityManager != null) {
                int i = name.lastIndexOf('.');
                if (i >= 0) {
                    try {
                        securityManager.checkPackageAccess(name.substring(0,i));
                    } catch (SecurityException se) {
                        String error = sm.getString("webappClassLoader.restrictedPackage", name);
                        log.info(error, se);
                        throw new ClassNotFoundException(error, se);
                    }
                }
            }

            boolean delegateLoad = delegate || filter(name, true);

            // (1) Delegate to our parent if requested
            /**
             * 是否委派给父类加载.
             */
            if (delegateLoad) {
                if (log.isDebugEnabled()) {
                    log.debug("  Delegating to parent classloader1 " + parent);
                }
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled()) {
                            log.debug("  Loading class from parent");
                        }
                        if (resolve) {
                            resolveClass(clazz);
                        }
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }

            // (2) Search local repositories
            /**
             * 使用webapp加载
             */
            if (log.isDebugEnabled()) {
                log.debug("  Searching local repositories");
            }
            try {
                clazz = findClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("  Loading class from local repository");
                    }
                    if (resolve) {
                        resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }

            // (3) Delegate to parent unconditionally
            /**
             * 如果webapp类加载器没有找到,则交给父加载器
             */
            if (!delegateLoad) {
                if (log.isDebugEnabled()) {
                    log.debug("  Delegating to parent classloader at end: " + parent);
                }
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled()) {
                            log.debug("  Loading class from parent");
                        }
                        if (resolve) {
                            resolveClass(clazz);
                        }
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
        }

        throw new ClassNotFoundException(name);
    }


从源码可以知道,WebappClassLoader再加载目标类时,会判断delegate是否启动父加载器(也就是双亲委派机制),默认是false,也就是不使用,而是直接使用WebappClassLoader加载目标类,如果WebappClassLoader找不到目标类,然后会尝试委托给父类加载器加载。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容