从classloader的变更说起

简介: 从classloader的变更谈到锁分离

classloader从1.6到1.7整体分成了两个版本。重点区别就是并行类加载。

1.6版本

    protected synchronized Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        ……
        return c;
    }

1.6版本加了一个方法锁。

1.7版本

  private final ConcurrentHashMap parallelLockMap;
  
    protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            ……
            return c;
        }
    }

    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;
    }

1.7之后就是用了两种模式getClassLoadingLock方法中,我们可以看出有两种模式,一种是parallelLockMap为空,锁的对象是classloader本身,另外一种是parallelLockMap不为null。这里会根据classname去获取锁,如果有返回的object不为null。说明已经有class使用过了,如果为null,就把新建的object当做锁,达到了一个classname一个锁的效果。

classloader加锁的原因

有很多classloader的例子直接复写了loadClass但是没有加锁,只有读取文件加载的过程,这种classloader都是特定场合使用的。并不具备通用性。众所周知的一个规则,一个classloader不能加载相同类的字节码,第二次加载就会在defineclass的时候报错。
场景1
不同的线程可以是相同的classloader,两个线程的都是A classloader加载的,当里面的有个方法里都有B类时,两个线程都会触发A加载B类
场景2
双亲委托的情况,A loader是B loader的parent。A能加载到C类。B loader加载c的时候委托给A,A也在同时加载C。此时触发了两次A加载C。

锁分离的好处

锁分离加快了并发,这个是显而易见的。还有一个好处是减少了死锁。在编写javaagent的时候,只要在transform加点锁,特别容易和classloader还有类初始化时候的锁造成死锁。基本死锁的场景都在1.6。

锁分离的案例

jdk中锁分离的实现特别多

读写分离

双锁读写分离
LinkedBlockingQueue有两把锁takeLock和putLock。
由于队列的特性——FIFO。在写入的时候,竞争takeLock。读取的时候竞争putLock。以此达到同事读写的增加吞吐量的目的。
副本机制读写分离
副本机制读写分离的典型就是copyonwrite。
CopyOnWriteArrayList。只有一把lock。但是使用的是数组存储。用volatile修饰。写入的时候获取锁,先用一个新的数组把旧的数据拷贝过来,然后把要加入的数据放入新数组中。最后替换 volatile的引用。读取的时候就直接获取volatile的数组。
这样读取的时候只是那一刻的副本,一旦开始遍历,直到结束都不会有新的数据加入了。

写写分离

写写分离的场景是锁细化。classloader的改进算是写写分离的情况。典型的场景就是ConcurrentHashMap。ConcurrentHashMap的每个槽位一把锁,当没有hash冲突的时候,元素的写的过程是并行的。

集群锁分离

集群的锁分离的场景我们也用到的特别多。主要目的是提供并发。

  1. kafka 消费(读写分离)。kafka写log,达到一定限制就会开启新的文件,消费的时候,从旧的开始,就会从已经写好的开始读取。这种情况下,读文和写的文件是两个不同的文件。
  2. kafka topic分区。(写写分离)。kafka写入根据分区算法。分别写入不同的partition。类似的还有分库,分表。只要是水平扩展的基本都是写写分离的。
  3. 主从机制(副本机制读写分离)。数据库使用主备方案,并且备份库只提供读取操作。至于新入的数据的空缺,一般由redis等缓存弥补。
目录
相关文章
|
7月前
|
Java 编译器
【JAVA】我们常常谈到的方法是指什么?
【JAVA】我们常常谈到的方法是指什么?
86 0
|
4月前
|
Java
【Java基础面试三十五】、谈谈你对面向接口编程的理解
这篇文章讨论了面向接口编程的概念,强调接口作为一种规范和实现分离的设计哲学,可以降低程序模块间的耦合度,提高系统的可扩展性和可维护性。
|
4月前
|
安全 前端开发 Java
【JVM 探秘】ClassLoader 类加载器:揭秘 Java 类加载机制背后的秘密武器!
【8月更文挑战第25天】本文全面介绍了Java虚拟机(JVM)中的类加载器,它是JVM的核心组件之一,负责将Java类加载到运行环境中。文章首先概述了类加载器的基本工作原理及其遵循的双亲委派模型,确保了核心类库的安全与稳定。接着详细阐述了启动、扩展和应用三种主要类加载器的层次结构。并通过一个自定义类加载器的例子展示了如何从特定目录加载类。此外,还介绍了类加载器的完整生命周期,包括加载、链接和初始化三个阶段。最后强调了类加载器在版本隔离、安全性和灵活性方面的重要作用。深入理解类加载器对于掌握JVM内部机制至关重要。
184 0
|
存储 设计模式 Java
八股文-如何理解Java中的多态
多态是面向对象编程的一个重要概念,它允许一个对象以不同的形式表现。也就是说,在父类中定义的属性和方法,在子类继承后,可以有不同的数据类型或表现出不同的行为。这可以使得同一个属性或方法,在父类及其各个子类中,可能会有不同的表现或含义。主要包括两种类型:编译时多态(静态多态)和运行时多态(动态多态)。
137 1
八股文-如何理解Java中的多态
|
Java 数据库 数据安全/隐私保护
【面试题精讲】JVM-类加载器-类加载器分类
【面试题精讲】JVM-类加载器-类加载器分类
|
前端开发 Java 数据库连接
【面试题精讲】JVM-类加载器-扩展类加载器
【面试题精讲】JVM-类加载器-扩展类加载器
|
Java 数据库 数据安全/隐私保护
【Java面试】谈谈你对自定义类加载器的理解
【Java面试】谈谈你对自定义类加载器的理解
201 0
面试官:Java 类加载过程是怎么样的?又被问麻了。。
面试官:Java 类加载过程是怎么样的?又被问麻了。。
108 0
面试官:Java 类加载过程是怎么样的?又被问麻了。。
|
Java C语言
Java面试必问:类加载过程与类加载器
Java面试必问:类加载过程与类加载器
115 0
Java面试必问:类加载过程与类加载器
|
存储 缓存 前端开发
【ClassLoader】深入浅出的核心源码分析|周末学习
【ClassLoader】深入浅出的核心源码分析|周末学习
182 0
【ClassLoader】深入浅出的核心源码分析|周末学习