Java内存和三级缓存

友们!我们乘胜追击、承上启下、攻坚克难、勇往直前……  好吧,废话不多说,之前总是说道线程安全,今天就讲一下线程安全的铺垫知识,咱们从根来:

    首先,我们要知道在CPU物理硬件层面上是有3级缓存的,注意这个是物理硬件存在的。三级缓存结构如图:

  

  1. 一个CPU中就有L1缓存和L2缓存,L1缓存的2部分与L2缓存可以读写连通,实现L1的两部分缓存可连通(注意这个原理也是缓存一致性的根本)。
  2. L3缓存不属于任何CPU,它是内存的代表,即内存的数据刷写到L3加快读写,且也通过连通性,实现了不同CPU的数据读写。
  3. 一般情况下,缓存的数据仅用于读取,但为了提高性能就想到了数据写入缓存中,缓存用于读与写不仅是读,可这样就会出现缓存一致性问题,顾提出解决:

(1).写直达:同时刷写缓存和内存,但无论缓存是否存在,每次都刷写内存,性能低。

(2).写回:要写入的数据,已在缓存中就更新缓存并标记;要写的数据不在缓存中并且无标记,则写入内存后再刷写缓存并标记;要写的数据不在缓存中并且有标记,被标记数据和要写的数据都写入内存,再将要写的数据刷写缓存中并标记。但在多CPU下,L1/L2的数据仅本身CPU可见,数据未写入L3/内存,其他CPU可能读旧数据,仍存在一致性问题。

(3).写传播 (总线嗅探):解决写回问题,1个CPU更新缓存,通过广播,其他CPU监测到也更新自身缓存,但无论数据是否相同都更新,性能下降。

(4).MESI事务串行化 (总线锁Lock/缓存锁-实现):保证CPU的顺序操作对其他CPU可见。MESI协议有4种状态:已修改(缓存已更新-内存未更新)、已失效(非最新)、独占 (因单CPU占用-写操作不广播)、共享(此状态下需要先广播再修改); 独占时,其他CPU从内存读最新数据时会降为共享,比写传播性能高。

(5).以上解释了为了性能去实现写入缓存操作、为了解决写入缓存的一致性问题在4种解决方法中选择最完善的MESI协议。

  1. MESI协议的缓存一致性底层都是缓存锁和总线锁Lock完成的,Lock指令相当于内存屏障(内存栅栏),内存屏障3功能:  (注:volatile用内存屏障底层Lock实现)

(1).避免指令重排:防止系统可能会将后面的指令先执行仅仅是结果有序的状况。

(2).强制缓存无效:将数据写入内存并让所有缓存无效,CPU只能去内存中读取在。

(3).缓存锁:是对L3级缓存的锁,但对于跨L3缓存、不支持缓存锁、不在缓存中的数据还是要用Lock总线锁。(后面再细化)

操作系统(即内核态):

  1. 功能:决定哪个进程/线程使CPU、管理内存、管理硬件、提供-程序与系统间的接口。
  2. 操作系统分区:用户空间(其代码仅访问此空间)、用户空间-供应用程序访问-用户态;

               内核空间(其代码可访问全空间)、 内核空间-供内核程序访问-内核态。

  1. 程序在系统调用时,CPU中断,切换到内核态执行,完成后CPU切回用户态中断处执行。
  2. 虚拟内存:多进程共用内存时,为了互不影响会分配虚拟内存 (映射不同段/页的内存)。

     (1).内存分段:程序在虚拟内存中被分-(栈, 堆, 数据, 代码)4段,加偏移量找对应内存位置,缺点是内存碎片化、内存交换效率低。

        (2).内存分页:程序用的虚拟内存为整体,通过页表映射内存,进程将虚拟内存页数据存到对应内存页中,内存连续。但都有缺页异常问题

(3).缺页异常:访问的页数据在磁盘中(不在内存中/内存不足未载入),操作系统则复制数据到内存,更新页表,恢复进程运行。如果是内存不足就会涉及页面置换。

(4).页面置换:内存不足时,将(最近未使用)数据存到磁盘,腾出内存空间,供新数据使用。默认(最近未使用的页面置换算法)。

(5).页面置换算法:LRU (老化, NRU, NFU)算法:最近未使用的先置换; FIFO算法:先进先出,最早数据先置换(即便正在使用); 第二次机会算法:FIFO的优化,要置换数据正在使用会保留; 时钟算法:第二次机会算法优化,更少时间执行算法; 工作集/工作集时间算法:合理开销,性能提高但实现复杂; 工作集算法:类似滑动窗口的,将局部的工作空间利用。

     (6).简单页表:用于映射内存,多进程下,每个进程都有自己的页表,造成占内存大。

(7).多级页表:(页表分级) 一级页表含全映射,当虚拟内存在一级中但未找到内存映射时,先不创建,使用时创建二级页表。多级页表占内存逐级减小,一级占用最大,二级次之,内存整体可用空间提高。但多级页表进行虚拟内存与内存转址更繁琐 (消耗时间)。

(8).TLB (页表缓存/转址旁路缓存/快表):封装在CPU中的转址对应表,CPU寻址先查TLB,因为常访问的页较固定,TLB命中率高。

(9).段页式内存管理:分段/分页并非对立,分段与分页+TLB 组合使用,提高内存利用率,但增加硬件成本和系统开销。

  1. DMA:通过CPU授权IO权限在无CPU下可执行,通过系统和内存总线传数据-并发强。

(1).通常:CPU将数据从磁盘拷到内存,读写并复制到用户进程,用户进程读写并复制到socket缓存区,socket复制到网卡。其中读写共4次,复制4次,且期间CPU等待。

(2).DMA:取代CPU职能,上述操作CPU不用等待,但仍4读写4复制

(3).mmap+write:磁盘(DMA缓冲)复制到内存,内存用mmap映射到用户进程(读写未变),内存复制到socket缓存区,socket(DMA缓冲)复制到网卡。4读写3复制

(4).sendfile:磁盘(DMA缓冲)复制到内存,内存用sendfile读写同时操作到用户进程,用户进程也用sendfile读写到socket缓存区,内存复制到socket,socket(DMA缓冲)复制到网卡。2读写3复制,因为sendfile管道是读写都可进行的。

(5).SG-DMA:若网卡支持SG-DMA,上述中,内存可直接复制到网卡。2读写2复制。   

GB级大文件不适用零拷贝:缓存易占满,预读命中低, 应使用 直接IO+异步IO

Linux网络协议(简单提一下,后期说):

  1. 网络协议由TCP/IP模型实现-(4层) 应用层—传输层—网络层—网络接口层 

  1. 程序发送数据包—系统调用—切至内核且数据拷至socket缓冲区—4层协议—网卡。
  2. 发送需从上到下经历4层封数据,接收则从下到上剥离4层拿数据。
  3. 网络性能指标:带宽(链路最大传输速度b/s)、延时(发送请求到收到响应时间)、吞吐量(单位时间内成功传输的数据量b/s)、PPS(网络包的传输速率—包/s)、是否正常连通、TCP连接数、丢包率、重传率(重传网络包比例)。
  4. 网络配置查看 (ip,ifconfig)、socket信息查看 (netstat,ss)、网络吞吐率和PPS (sar)、连通性和延时性(ping)。

Linux系统:

  1. CPU负载-(可运行与不可中断-进程数和)、 CPU利用率-(正运行进程-实时占CPU百分比)。
  2. CPU负载高利用率低即等待任务多时,使用IO密集型任务:核心线程数=CPU数X2。
  3. CPU利用率高负载低即任务执行时间长时,使用CPU密集型:核心线程数=CPU数+1。
  4. 混合密集型:核心线程数=((线程等待时间+线程CPU时间)/线程CPU时间)* CPU数。
  5. CPU使用率100%—top找占CPU最高 -(top可找负载高、top可找利用率高)

CPU飙升

  1. 代码中读取数据量大,系统内存耗尽,导致Full GC次数过多,系统缓慢(系统不可用)
  2. 代码中有耗CPU操作,导致CPU过高,系统运行缓慢,系统不可用
  3. 代码有阻塞-耗时、代码线程进入wait等待、代码进入死锁

声明:因文章是个人笔记,顾偏向于知识总结,文中提到的相关知识还需细化学习,因篇幅有限无法逐一讲解,最后谢谢支持与指正。

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Spring中,循环依赖可能会导致bean无法正确地初始化,此时可以使用三级缓存解决该问题。具体步骤如下: 1. 创建一个ConcurrentHashMap类型的三级缓存,用于存储正在创建的bean。 2. 当Spring创建一个bean时,将该bean放入三级缓存的第一级中。如果发现该bean依赖于另一个正在创建的bean,则将该bean放入三级缓存的第二级中,表示该bean已经被创建但是未完成初始化。 3. 当另一个bean依赖于第二级缓存中的bean时,将该bean放入三级缓存的第三级中,表示该bean已经被创建和完成初始化。 4. 如果一个bean已经完成初始化,则将其从三级缓存中移除,同时将其添加到Spring容器中。 5. 如果在创建bean的过程中发生异常,则将该bean从三级缓存中移除,并抛出异常。 示例代码如下: ```java public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { // 三级缓存,用于存储正在创建的bean private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); @Override public Object getSingleton(String beanName) { // 从第一级缓存中获取bean Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 从第二级缓存中获取bean singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { // 从第三级缓存中获取bean ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // 将bean从第三级缓存移动到第二级缓存 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } return singletonObject; } protected boolean isSingletonCurrentlyInCreation(String beanName) { return this.singletonObjects.containsKey(beanName); } protected void addSingleton(String beanName, Object singletonObject) { // 将bean从第一级缓存移动到第二级缓存 this.singletonObjects.remove(beanName); this.earlySingletonObjects.put(beanName, singletonObject); } protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { // 将bean从第一级缓存移动到第三级缓存 this.singletonObjects.remove(beanName); this.singletonFactories.put(beanName, singletonFactory); } protected void removeSingleton(String beanName) { // 将bean从第二级缓存或第三级缓存移除 this.earlySingletonObjects.remove(beanName); this.singletonFactories.remove(beanName); this.singletonObjects.remove(beanName); } } ``` 在Spring Boot中,可以通过配置以下属性开启循环依赖的三级缓存: ``` spring.main.allow-circular-references=true ``` 注意:使用三级缓存解决循环依赖可能会导致内存泄漏和线程安全问题,需要谨慎使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值