CAS底层原理 | JVM相关知识

第一阶段 开源框架源码剖析

该阶段是通过纯手写持久层、IoC&AOP等框架来培养框架思维和自定义框架的能力,通过SSM源码剖析进一步理解设计模式的具体应用。

模块一 持久层框架设计实现及MyBatis源码分析
通过持久层框架的衍生分析,推导出开发步骤进而纯手写持久层框架,对MyBatis技术系统复习后进行源码剖析。

模块二 IoC容器设计实现及Spring源码分析
从分析代码耦合到IoC思想演进,从功能代码抽取到面向切面AOP思想演进,最后会手写Spring和对Spring进行源码深入分析。

模块三 MVC框架设计实现及SpringMVC源码分析、通用数据操作接口设计及SpringData 接口规范
本模块从MVC设计模式及前端控制器模型分析,手写属于自己的MVC框架,并对SpringMVC源码分析;对Spring Data 接口规范进行深入剖析。

模块四 约定优于配置设计范式及Spring Boot源码剖析
本模块将会剖析约定优于配置设计范式,并分析SpringBoot自动装配实现原理,并对SpringBoot源码进行剖析,探秘底层实现原理及框架设计思想。

第二阶段 Web服务器深度应用及调优

该阶段是对Web应用服务器进行深入使用,对Tomcat、Nginx性能调优进行讲解,以及Cluster模式潜在问题及解决方案深入讲解。

模块一 Tomcat深度剖析及性能调优、Nginx深度剖析及性能调优
本模块会对Tomcat工作原理及架构进行剖析、性能调优,分析Tomcat漏洞防护与安全加固策略,及Nginx进程模型及产线配置学习。

模块二 Cluster模式潜在问题及解决方案、Web服务综合解决方案
本模块会对集群状态下一致性Hash和Session共享提出解决方案,并对页面动态模块化渲染、CDN等加以说明。

第三阶段 分布式架构设计&微服务深入剖析

该阶段为分布式学习提供理论基础,如分布式一致性协议Paxos、远程过程调用RPC等,并对分布式中间件Dubbo和SpringCloud进行深入剖析。

模块一 分布式理论、架构设计(自定义RPC)
本模块会对分布式架构的理论、架构设计、网络通信等进行讲解,并设计一个RPC远程过程调用框架并进行代码编写。

模块二 分布式服务治理、分布式协调服务Zookeeper深入
本模块会对分布式治理中遇到的问题,例如权重、降级、容错路由等进行讲解,并对分布式协调服务Zookeeper进行系统的说明。

模块三 高性能RPC框架Apache Dubbo
本模块对Apache的Dubbo框架进行深入讲解,涉及架构设计、SPI机制、高级使用及其源码分析等内容

模块四 SpringCloud组件设计原理及实战(上)
本模块对SpringCloud的一些高级特性,例如链路追踪设计原理及Sleuth+Zipkin、Spring Cloud Alibaba等进行讲解。

模块五 SpringCloud组件设计原理及实战(下)
本模块对SpringCloud的一些高级特性,例如链路追踪设计原理及Sleuth+Zipkin、Spring Cloud Alibaba等进行讲解。

第四阶段 大型分布式存储系统架构进阶

数据存储瓶颈是架构师重要技能之一,该阶段是对市场上分布式存储进行深入剖析,有分布式数据存储MySQL,分布式文件存储DFS系列,分布式云存储OSS等。

模块一 MySQL海量数据存储与优化(上)
本模块对MySQL体系架构、运行机制、存储引擎、索引原理、事务和锁以及集群架构设计等方面的内容进行深入系统的介绍,并对SQL和架构进行分析及提出性能优化方案。

模块二 MySQL海量数据存储与优化(下)
本模块主要对MySQL海量数据处理中的分库分表架构、ShardingSphere、MyCat中间件实战应用、数据库实战规范、以及一些运维分析工具等内容进行讲解。

模块三 分布式文档存储独角兽MongoDB、知识图谱存储数据库Neo4j
本模块对MongoDB的存储原理以及replica sets & Sharded Cluster等、对Neo4j数据模型及图形理论等进行深入讲解。

模块四 轻量级分布式文件系统FastDFS、阿里云OSS云存储平台
本模块对分布式文件系统FastDFS集群架构与原理剖析,使用FastDFS+Nginx搭建高吞吐文件服务器,并对阿里云OSS云存储平台系统讲解。

模块五 Hadoop分布式文件系统HDFS、海量列式存储数据库HBase
本模块对大数据的基石HDFS进行系统讲解,对HBase 性能提升策略与读写速率优化提出解决方案。

第五阶段 大型分布式系统缓存架构进阶

大型互联网项目必备分布式缓存,该阶段对市场上主流的及有一定潜力的缓存服务中间件进行重点讲解,最终可以达到根据不同业务进行分布式缓存选型的能力。

模块一 高性能分布式缓存Redis、分布式 Redis 解决方案Codis(Twemproxy替代方案)
本模块对Redis的持久化方案、删除策略、IO多路复用模型、Redis集群模式、预热、雪崩、击穿、穿透等进行系统剖析。

模块二 Guava Cache、EVCache、Tair、Aerospike
本模块对市场上其他缓存服务进行讲解,例如:Guava Cache、EVCache、Tair、Aerospike等,可以提高在缓存方面的架构选型能力。

第六阶段 分布式消息服务中间件进阶

该阶段是对高效可靠的消息传递机制进行理论讲解,并对市场上常用的中间件进行讲解,并结合业务场景,完成服务解耦、削峰填谷、分布式事务等实际场景应用。

模块一 Apache开源消息中间件RabbitMQ、RocketMQ
本模块对市场上常用的开源消息中间件RabbitMQ、RocketMQ进行深度剖析、并对ACK、限流、TTL、死信、延迟等高级应用进行讲解。

模块二 高吞吐消息中间件Kafka
本模块对Kafka集群原理和消息流处理流程、组件机制、流处理基础等进行深入讲解,对从架构选型角度对三种MQ进行比较。

第七阶段 分布式搜索引擎进阶

该阶段通过检索工具包Lucene对进行分词、倒排索引等概念进行理论讲解,并使用Elasticsearch对拉勾亿级数据进行搜索,使你成为搜索专家。

模块一 引擎工具包Lucene、搜索应用服务器Solr
本模块将对Lucene倒排索引机制和底层存储结构深入讲解、对搜索服务引擎Solr高级特性进行剖析,并使用SolrCloud+Zookeeper进行集群化管理。

模块二 海量日志分析平台Elastic Stack
本模块将针对Elasticsearch的数据模型分析、构建和算法扩展进行深入讲解,结合拉勾网亿级数据量进行搜索实战,最后对es进行性能调优。

第八阶段 分布式实时流式计算引擎Flink

通过该阶段学习,我们可以掌握在实际的生产过程中有大量的数据实时性分析需求,例如实时推荐,异常告警,传感器信令分析等需求。

模块一 分布式实时流式计算引擎Flink
本模块对实时流式计算引擎Flink的Flink流处理特性、Flink编程模型及实践进行讲解,并介绍基于Flink的物联网数据实时监控系统。

第九阶段 容器技术&CI/CD、DevOps

通过该阶段学习,我们可以使用Docker&K8s打包应用以及依赖包到一个轻量级容器中,方便移植,该阶段还包括其他一系列运维工具的学习。

模块一 容器虚拟化技术、CI/CD、DevOps、服务质量治理
本模块主要深入讲解容器引擎Docker & K8s容器编排系统,对续集成工具Jenkins、代码质量管理工具Sonar、APM管理工具Skywalking等进行讲解。

第十阶段 底层调优与算法深入

底层调优和算法是架构师必备技能之一,有时项目性能瓶颈是要通过底层调优实现的,而一些高级的内核和引擎开发往往是需要一些精良算法才能完成的。

模块一 数据结构、算法
本模块会讲解算法高级内容,例如高级数据结构、排序、递归与回溯、深度与广度优先搜索、动态规划、二分搜索与贪婪算法等。

模块二 并发编程、JVM优化、Linux优化
本模块会深入讲解线程高级部分,例如线程状态机制分析与线程池实现原理、抽象队列化同步器AQS等,还会深入JVM分析与调优,Linux性能监控与调优。

第十一阶段 大型互联网项目实战和业务解决方案

该阶段会结合真实的大型互联网项目,将前10个阶段学到的内容与该实际业务相结合,根据实际业务进行架构选型,技术选型等。

模块一 大型互联网项目实战
本模块会以真实开发流程,从需求分析、概要设计、详细设计、编码、测试、上线等几个环节对大型互联网项目进行讲解,夯实之前所学技术。

模块二 主流业务解决方案
本模块是对互联网公司常用的一些解决方案进行讲解,例如:秒杀、SSO、IM、Security、LBS、推送、三方支付等进行拓展讲解。

第十二阶段 进大厂必备-面试求职深度辅导

该阶段会从专项能力突击、如何打造一份让人无法拒绝的简历、如何在面试中发挥应有的实力三部分进行剖析。

模块一 进大厂必备-面试求职深度辅导
本模块会教你怎么编写一份优秀的简历,怎样应对面试,也会有模拟面试环节,让你跟大厂技术面试官有提前训练的机会。  

技术干货

多线程与高并发之synchronized底层实现之CAS

synchronized在JDK1.2之前是重量级锁。后面逐步优化,直到JDK1.6已经优化为下面讲解的情况了。
Compare And Swap (Compare And Exchange) / 自旋锁 / 轻量级锁

自旋理解:一个线程先读取到值A,然后修改为V,更新之前再读取一遍,判断和第一次读取到的A是否一致,从而判断是否有其他线程修改过。如果一直不一致,就要不断循环这个过程。
轻量级理解:这里又会涉及到操作系统的知识。可以理解为加锁过程不经过OS操作系统。

CAS的缺点:
1、循环时间长开销很大耗费CPU。(这个很好理解,因为每次对比发现不一致,当前线程要一直循环)
2、只能保证一个共享变量的原子操作。(这个涉及计算机理论基础,本篇博客先不做解释)
3、ABA问题。(在图示第3步,尽管在比较值的时候是一样的,但其实在当前线程第一次读取到值之后第二次读取对比之前,可能已经被其他线程修改成其他值又修改回原值。)


通过AtomicInteger源码跟踪查看CAS底层实现:

import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class Test02_AtomicInteger {

    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        // 创建了100个线程数组
        Thread[] threads = new Thread[100];
        // 计数器,为了阻塞主线程等待所有线程执行完毕。
        // await方法会阻塞当前线程,直到计数器变为0。每调用一次countDown方法,计数器会减1
        CountDownLatch countDownLatch = new CountDownLatch(threads.length);

        // 创建了100个线程,每个线程分别对atomicInteger子增加1,循环10000次
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    //自增+1
                    atomicInteger.incrementAndGet();
                }
                countDownLatch.countDown();
            });
        }

        // 开始执行线程
        Arrays.stream(threads).forEach(thread -> thread.start());
        // 阻塞当前线程
        countDownLatch.await();
        // 输出最后atomicInteger的值,预期是100,0000
        System.out.println(atomicInteger);
    }
}

我们来跟踪下atomicInteger.incrementAndGet();

 这里得compareAndSwapInt就是CAS操作,可以看到是native修饰的方法,底层实现是C++,别急依然可以追踪

所以最终调用的指令就是:lock cmpxchg(cmpxchg=cas修改变量的值) 

cmpxchg是CPU的指令,这个指令也是和前面讲的一样,修改前要比较值。涉及到计算机的理论基础,有两颗CPU,其中一颗通过cmpxchg指令要去内存里修改一个值,这个过程是会被另一个CPU打断的,所以就无法保证CAS操作的原子性。所以多核CPU要加lock指令,简单理解就是锁总线(CPU是通过主板上的一组线访问内存,锁住禁止其他CPU访问)。

好啦,以上就是CAS实现源码追踪的过程,简单链路如下:
AtomicInteger.incrementAndGet() --> Unsafe.GetAndAddInt() --> Unsafe.compareAndSwapInt() --> lock cmpxchg(锁总线) 


 synchronized锁升级过程

(new 对象)无锁 -> 偏向锁 -> 轻量级锁 (自旋锁)-> 重量级锁

无锁 -> 偏向锁:如果有线程上锁 ,上偏向锁,指的就是,把markword的线程ID改为自己线程ID的过程 。
默认情况,偏向锁有个时延,默认是4秒。为什么呢?因为JVM虚拟机自己有一些默认启动的线程,里面有好多sync代码,这些sync代码启动时就知道肯定会有竞争,如果使用偏向锁,就会造成偏向锁不断的进行锁撤销和锁升级的操作,效率较低。

偏向锁 -> 轻量级锁:如果有线程竞争,撤销偏向锁,升级轻量级锁。线程在自己的线程栈生成LockRecord ,用CAS操作将markword设置为指向自己这个线程的LR的指针,设置成功者得到锁。

轻量级锁 -> 重量级锁:1、竞争加剧(有线程超过10次自旋)。2、自旋线程数超过CPU核数的一半。(1.6之后,加入自适应自旋 Adapative Self Spinning,JVM自己控制升级重量级锁。)

重量级锁,线程挂起,进入等待队列,等待操作系统的调度,不消耗CPU资源。


通过markword看锁升级过程

通过JOL(Java Object Layout)工具查看对象内存布局。

import org.openjdk.jol.info.ClassLayout;

public class Test01_HelloJOL {

    public static void main(String[] args) throws InterruptedException {

//        Thread.sleep(5000);

        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());

        synchronized (o) {
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }
}

我来运行展示一下:(图片中的错误,对齐padding)

可以观察到Object o = new Object();直接打印后三位是001,无锁态。
加了synchronized之后,后三位是000,轻量级锁。

 在线程睡了5s后,后三位是101,偏向锁。

自旋锁什么时候升级为重量级锁? 为什么有自旋锁还需要重量级锁? 偏向锁是否一定比自旋锁效率高? 我相信你已经懂了吧。

另外需要注意的是,synchronized锁升级之后是不可降级的。实际应用中要注意场景。


锁重入

sychronized是可重入锁,重入次数必须记录,因为要解锁几次必须得对应 。
为什么是可重入锁呢?因为继承,父类方法被sychronized修饰,子类在方法中调用super.该方法,所以可以上两边锁,锁重入。


锁消除 lock eliminate

public void add(String str1,String str2){
         StringBuffer sb = new StringBuffer();
         sb.append(str1).append(str2);
}

我们都知道 StringBuffer 是线程安全的,因为它的关键方法都是被 synchronized 修饰过的,但我们看上面这段代码,我们会发现,sb 这个引用只会在 add 方法中使用,不可能被其它线程引用(因为是局部变量,栈私有),因此 sb 是不可能共享的资源,JVM 会自动消除 StringBuffer 对象内部的锁。


锁粗化 lock coarsening

public String test(String str){
       int i = 0;
       StringBuffer sb = new StringBuffer():
       while(i < 100){
           sb.append(str);
           i++;
       }
       return sb.toString():
}

JVM 会检测到这样一连串的操作都对同一个对象加锁(while 循环内 100 次执行 append,没有锁粗化的就要进行 100 次加锁/解锁),此时 JVM 就会将加锁的范围粗化到这一连串的操作的外部(比如 while 虚幻体外),使得这一连串操作只需要加一次锁即可。

OK了,就先讲这些吧,希望你已经对锁的原理有个深刻的了解。

JVM相关知识

如何定位垃圾?

  • 对象头计数。对象有一个引用,计数便加1,减少一个引用,计数减1,计数为0,就认为是垃圾。但是对象间循环引用,其实都是垃圾,便无法被回收。如下图:

  • 根可达。凡是被根对象直接或间接引用的,便不是垃圾。哪些是跟变量呢?如上图:

 垃圾回收算法

  • Mark-Sweep标记清除(两遍扫描效率偏低,位置不连续,产生碎片)。
  • Copying拷贝(一次扫描,没有碎片,浪费空间,要调整对象引用)。
  • Mark-Compact标记压缩(两遍扫描效率偏低,没有碎片,指针需要调整)。

垃圾回收器

注意看图片去记忆,左半边,上面部分,是Young年轻代的垃圾回收器。下面部分是Old老年代的垃圾回收器。

右半边,是内存不分代的垃圾回收器。本篇就是初步介绍概念,详细的内容还要去自行了解。

下面分别介绍下以上垃圾回收器:

  • Serial与Serial Old,是串行的垃圾收集器。只会使用一个CPU或一个收集线程去完成垃圾收集工作。而且它进行垃圾回收的时候,必须暂停其他所有的用户线程(Stop The World,STW),直到它收集完成。
    它适合Client模式的应用,在单CPU环境下,它简单高效,由于没有线程交互的开销,专心垃圾收集自然可以获得最高的单线程效率。通过-XX:+UseSerialGC可以开启上述回收模式。
    新生代采用Serial,是利用复制算法;老年代使用Serial Old采用标记-整理算法。

  •  ParNew、Parallel Scavenge(关注吞吐量)与Parllel Old,是并行的垃圾收集器。是通过多线程运行垃圾收集的,默认开启的收集线程数和CPU数量一样,运行数量可以通过修改ParallelGCThreads设定。也会Stop The World。
    适合Server模式以及多CPU环境。使用-XX:+UseParNewGC和Serial Old收集器组合进行内存回收。
    新生代采用ParNew、Parallel Scavenge,是利用复制算法;老年代使用Parllel Old采用标记-整理算法。

  • CMS(Concurrent Mark Sweep) ,是并发的垃圾收集器。是一种以获得最短回收停顿时间为目标的收集器。从名字就能直到其是给予标记-清除算法的。但是它比一般的标记-清除算法要复杂一些,分为以下4个阶段:
    初始标记:标记一下GC Roots能直接关联到的对象,会“Stop The World”。
    并发标记:GC Roots Tracing,可以和用户线程并发执行。
    重新标记:标记期间产生的对象存活的再次判断,修正对这些对象的标记,执行时间相对并发标记短,会“Stop The World”。
    并发清除:清除对象,可以和用户线程并发执行。

  •  G1从整体看还是基于标记-清除算法的,但是局部上是基于复制算法的。这样就意味者它空间整合做的比较好,因为不会产生空间碎片。G1还是并发与并行的,它能够充分利用多CPU、多核的硬件环境来缩短“stop the world”的时间。G1还是分代收集的,但是G1不再像上文所述的垃圾收集器,需要分代配合不同的垃圾收集器,因为G1中的垃圾收集区域是“分区”(Region)的。G1的分代收集和以上垃圾收集器不同的就是除了有年轻代的ygc,全堆扫描的full GC外,还有包含所有年轻代以及部分老年代Region的Mixed GC。G1还可预测停顿,通过调整参数,制定垃圾收集的最大停顿时间。

G1的堆区在分代的基础上,引入分区的概念。G1将堆分成了若干Region,以下和”分区”代表同一概念。(这些分区不要求是连续的内存空间)Region的大小可以通过G1HeapRegionSize参数进行设置,其必须是2的幂,范围允许为1Mb到32Mb。 JVM的会基于堆内存的初始值和最大值的平均数计算分区的尺寸,平均的堆尺寸会分出约2000个Region。分区大小一旦设置,则启动之后不会再变化。 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值