在JDK 7之前,ClassLoader存在并发(多线程)加载时可能导致死锁的问题(deadlock);在JDK 7通过对ClassLoader类内部进行了增强和优化,解决了此风险。我们先了解一下这个问题发生的原因,以及JDK 7通过何种技巧又解决了此问题。
我们开发自定义的ClassLoader,通常继承java.lang.ClassLoader类,通过重写findClass(String name) 或者直接重写loadClass(String name)来实现,当然实际而言loadClass方法最终面向调用者、以及其内部仍然会调用findClass方法。(备注,通常通过重新findClass方法)。此外,众所周知,ClassLoader通过“双亲委派”方式来查找、加载类,其“委派”这就是其parent加载器,自定义ClassLoader可以指定parent,默认parent为系统的启动类加载器(提醒:不是线程类的加载器,此处不再赘言)。
引起死锁的原因:
1)JDK 6中loadClass方法是同步的,且同步操作修饰在方法级别,这意味着同步锁将在classLoader实例上。
2)因为双亲委派关系,多个自定义的classLoader可以使用不同的、相同的parent,甚至交叉;比如CL1将CL2作为parent,那么CL2也可以将CL1作为parent;本质上这种设计思想上,并没有错;只是,这种委派方式,在全局来看,并没有遵守“单向、层级的”委派关系。
3)CL1、CL2两个加载器的实例(通常一种类加载器只有一个实例),分别在两个(或者多个)线程中并发的load类时,死锁可能就会发生;因为1)loadClass方法是同步的,所以Thread1使用CL1加载类时首先获取CL1实例锁、loadClass方法中还需要获取CL2的锁(调用CL2的loadClass),那么Thread2使用CL2加载类也出现相同的问题,只是对象锁的顺序不同。
//JDK 6 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { .... }
有关deadLock的示例,可以参考:https://www.ibm.com/developerworks/library/j-dclp4/index.html
即使他们并发加载不同的class,仍然有死锁的风险。所以,很多设计者,要么将自定义的classLoader委派关系层级设计的很“长”,要么就是彻底单线程加载,尽管此时加载的速度很慢,比如Tomcat 7之前的设计(Tomcat启动加载速度很慢)。
其实JDK本身解决这个问题并不复杂,却懒散的拖延到了JDK 7才算考虑解决。
1)移除了loadClass方法级别的同步,转而在内部设计了一个私有的(非静态)concurrentHashMap,K为className,V为Object对象锁;在执行loadClass时,首先检测map中是否已经有此className的对象锁,如果有则根据此对象锁进行同步,如果没有则创建。简单而言,就是降低了锁的粒度;此后,即使交叉委派,也不会再出现死锁的问题。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { ..... } } protected Object getClassLoadingLock(String className) { Object lock = this; if (parallelLockMap != null) { Object newLock = new Object(); lock = parallelLockMap.putIfAbsent(className, newLock); if (lock == null) { lock = newLock; } } return lock; }
2)为了向后兼容,JDK并没有默认开启并发加载特性,我们需要关注一个方法:
protected static boolean registerAsParallelCapable()
此方法在JDK 7新增,表示将此classLoader注册为“并发的”;只有调用了此方法,1)中的concurrentHashMap才会被初始化,即支持className级别的并发锁;否则(map == null)仍然将classLoader实例作为同步对象。
此方法,需要在所有的自定义ClassLoader中都要调用(包括委派的parent是自定义时),否则将不会生效。
public class CustomizeClassLoader extends ClassLoader { static { boolean result = ClassLoader.registerAsParallelCapable(); if (!result) { system.out.println("pClassLoaderParallel.registrationFailed"); } } ... }
实现原理并不复杂,不过如果你的程序是JDK 6升级迁移而来,那么还需要注意几个问题:
1)如果你通过重写loadClass(String name)来实现类加载,那么你需要移除方法级别或者其内部的对象同步区块,同时还需要模仿JDK 7的方式,实现基于className级别的并发(可以参考loadClass实现);确保defineClass方法对于同一个className只能调用一次。
2)原则上,重新findClass是不需要额外使用同步词修饰,如果有,你也应该移除。
3)如果你的程序,在并发加载时有意外的兼容性问题,可以通过JVM参数来全局关闭此特性:-XX:+AlwaysLockClassLoader。
在Tomcat 8之后,已经默认支持了并发加载。
有没有什么特殊的场景,是不能使用“并发加载”的呢?这个问题就留给大家了!
相关推荐
ClassLoader类加载机制和原理详解
【图解版】深入分析ClassLoader类加载工作机制,从原理到JVM的装载过程,详情分析了ClassLoader加载类以及自定义类加载器的过程,不可用于商业用途,如有版权问题,请联系删除!
NULL 博文链接:https://ldbjakyo.iteye.com/blog/1046984
类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java Applet 需要从远程下载 Java 类文件到浏览器中并执行。现在类加载器在 Web 容器和 OSGi 中得到了广泛的使用。一般来说,Java ...
通过对ClassLoader中两个子类加载dex,来熟悉安卓中的apk加载流程
ClassLoader的API使用和自定义
ClassLoader类加载器讲解,理解JAVA类加载机制
理解Java ClassLoader机制
ClassLoader动态加载类 简单示例 包装tank.test; 导入java.util.Scanner; 导入tank.classloader.ClassLoaderManager; 导入tank.classloader.MyClassLoaderManager; 导入tank.classloader.SystemClassLoaderManager...
本篇文章主要给大家讲述了Java中ClassLoader类加载的原理以及用法总结,一起学习下。
下面小编就为大家带来一篇classloader类加载器_基于java类的加载方式详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
该电子书详细介绍了java虚拟机类加载机制,对于深入理解jvm工作原理有很好的帮助作用,对于初学java,有一定工作经验的小伙伴来说是一本提高自身java素养,夯实自己java基本技能的“葵花宝典”。
jvm运行的过程中,需要载入类,而类的加载需要类加载器,本文章提供了java的类加载器的工作原理。可以使读者更加理解jvm的运行机制。
1. 加载(Loading):classpath,jar包,网络,磁盘位置下的类的class以二进制字节流读进来,在内存 2. 验证(Verification)
ClassLoader类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。从而只有class文件被载入到了内存之后,才能被其程序所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。 ...
Java 虚拟机中ClassLoader 相关简介 双亲委托机制 Android 中ClassLoader 简介
包括commons-logging commons-beanutils commons-lang ezmorph json-lib-2.4-jdk15 commons-collections-3.2.1的jar包,可以解决 org/apache/commons/lang/exception/NestableRuntimeException的问题
1. ClassLoader(类加载机制) 3. findLoadedClass (查找JVM已经加载过的类) 4. defineClass (定义一个Java
java自定义类加载classloader文档,包括代码,以及详细的原理及过程
主要为大家解析了ClassLoader类加载源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下