在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之后,已经默认支持了并发加载。
有没有什么特殊的场景,是不能使用“并发加载”的呢?这个问题就留给大家了!
相关推荐
1. Bootstrap ClassLoader:这是JVM启动时的第一个ClassLoader,负责加载JDK核心类库(如rt.jar)。 2. Extension ClassLoader:加载JDK扩展目录(如$JAVA_HOME/jre/lib/ext)中的类。 3. System ClassLoader(也...
Java的类加载机制是通过类加载器完成的,包括启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader)。这部分源代码可以帮助我们理解如何...
《深入解析JDK7 Hotspot源码》 Hotspot是Oracle JDK中的一个关键组件,它是一个高性能的Java虚拟机(JVM)。Hotspot的名字源于其核心理念:在运行时识别出程序的“热点”代码,然后对其进行优化,以提供最佳的执行...
10. **类加载机制**:JDK11的类加载机制仍然遵循“双亲委派模型”,源码中的`java.lang.ClassLoader`类及其子类展示了如何加载和查找类。 通过深入研究JDK11源码,开发者不仅可以了解到Java语言的最新发展,还能...
- **类加载机制**: 如何通过ClassLoader找到并加载类。 - **垃圾收集**: 了解Java内存管理,包括对象分配、引用计数、可达性分析等。 - **线程模型**: 学习Java并发编程的基础,包括Thread类、synchronized关键字、...
Java开发人员在面对面试时,经常会遇到关于JDK源码的问题。然而,对于大多数开发者来说,能够详细解答这些源码问题的人并不多。阅读并理解JDK源码,无疑能提升我们的技术水平,增强问题解决能力,但这需要一定的基础...
JDK 1.6中的类加载器包括Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。它们协同工作,按照双亲委托模型加载类,确保类的唯一性。源码分析可以帮助理解类加载的过程,以及如何自定义类加载器。 ...
例如,`java.lang.ClassLoader`类展示了如何动态加载类,`java.lang.reflect`包提供了对反射的支持。 2. **内存管理**:JDK 1.8的垃圾收集器(Garbage Collector, GC)的实现,包括新生代、老年代的划分以及不同...
│ 高并发编程第二阶段49讲、自定义类加载器ClassLoader顺便问候了一下世界.mp4 │ 高并发编程第二阶段50讲、ClassLoader父委托机制详细介绍.mp4 │ 高并发编程第二阶段51讲、加密解密类加载实战演示.mp4 │ 高...
1. **类加载器(ClassLoader)** - Java的类加载机制是基于委托的双亲委派模型,源码中展示了如何动态加载类,并解释了不同类型的类加载器如BootstrapClassLoader、ExtensionClassLoader和AppClassLoader的工作方式。...
了解ClassLoader的工作原理,可以优化类加载顺序,减少不必要的资源消耗。Java 9引入的模块系统(Jigsaw)通过封装和隔离,可以降低程序的启动时间和内存占用。 在工具使用上,JDK的JMX(Java Management ...
- 虽然JDK源码不包含JVM本身,但它包含了很多与JVM交互的类,如`ClassLoader`、`StackTraceElement`等,通过这些可以了解到类加载和执行的细节。 9. **国际化与本地化** - `java.text`和`java.util.Locale`等类...
1. 类加载机制:如何通过`java.lang.ClassLoader`加载类。 2. 内存管理:`java.lang.Runtime`和`sun.misc.GC`揭示了JVM的内存分配和垃圾回收策略。 3. 并发编程:`java.util.concurrent`包提供了高级并发工具,而`...
3. **类加载机制**:JDK中的`ClassLoader`类负责加载类到JVM。通过阅读`java.lang.ClassLoader`及其子类的源码,我们可以学习到类加载的双亲委派模型,理解如何自定义类加载器以及动态加载类。 4. **集合框架**:`...
通过上述分析,我们可以看出JDK动态代理是一种非常强大且灵活的技术,它不仅可以帮助我们解决实际开发中遇到的问题,还能够提高代码的可维护性和可扩展性。理解其工作原理对于深入学习Java编程有着重要意义。
│ 高并发编程第二阶段49讲、自定义类加载器ClassLoader顺便问候了一下世界.mp4 │ 高并发编程第二阶段50讲、ClassLoader父委托机制详细介绍.mp4 │ 高并发编程第二阶段51讲、加密解密类加载实战演示.mp4 │ 高...
在Java 7之前,类加载器在并发环境下存在性能问题,因为它们在加载类时可能会产生竞争条件,导致不必要的同步开销。Java 7引入了一种称为“平行可扩展”(parallel-capable)的类加载器设计,允许类加载器并行工作,...
7. **类加载器(ClassLoader)**:了解"sun"包中的类加载器实现,可以深入理解Java的类加载过程,包括双亲委派模型和自定义类加载器的实现。 8. **并发(Concurrency)**:"sun"包中的并发工具类源码展示了Java如何...
10. **类加载器**: java.lang.ClassLoader,负责加载类到JVM。 通过熟练掌握这些API,开发者能够高效地编写出稳定且功能丰富的Java应用程序。同时,随着JDK版本的更新,开发者应关注新版本带来的变化,以便及时更新...
2. **类加载器(ClassLoader)**:在JDK 1.6.0中,ClassLoader的设计与实现也是一大亮点。它负责加载类文件,理解其加载机制、双亲委托模型以及自定义类加载器的应用,对于理解和解决类冲突、优化应用启动速度等问题...