蚂蚁一面整理

java的同步容器,并发容器?
同步:即每次只有一个线程访问容器状态。
并发:即每次可有多个线程访问容器状态。
同步容器:Vector,HashTable
并发容器:ConCurrentHashMap,CopeyOnwrite
–当并发读远多于修改的场景下需要使用List和Set时,可以考虑使用CopyOnWriteArrayList和CopyOnWriteArraySet;
–当需要并发使用<Key, Value>键值对存取数据时,可以使用ConcurrentHashMap;
–当要保证并发<Key, Value>键值对有序时可以使用ConcurrentSkipListMap。

ArrayList和LinkedList的插入和访问的复杂度
即数组与链表的访问插入复杂度
1>.数组在访问时可通过下标直接查询,复杂度O(1),链表复杂度O(n)
2>.数组插入时数组下标需要移动O(n),链表直接操作指针O(1)
反射原理—>

反射可动态加载外部配置对象,通过class.forName加载类信息,而forName方法就是通过反射类调用的类信息,
jvm加载获取到里面的classLoader,通过native方法获取类信息,最终调用invoke0()方法,反射是线程安全的,
因为loadClass方法是synchronized修饰的,找到需要的方法,都会copy一份出来,而不是使用原来的实例,从而保证数据隔离;
注解原理—>

注解等同于加了标记,注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池
注解参数成员必须是public的,没有成员方法也行,但是就没得意义了
新生代分为几个区?使用什么算法进行垃圾回收?为什么使用这个算法?
新生代分为Eden区,幸存from区,幸存to区,大小比例8:1:1
新生代使用复制算法,高效,省去了标记整理的过程,新生代需要清理的对象数量巨大,复制算法浪费空间,但效率高,
–>引申
MinorGC的过程(复制->清空->互换)
1:Eden,SurvivorFrom复制到SurvivorTo,年龄+1

首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到SurvivorFrom区,当Eden区再次出发GC的时候会扫描Eden区和form区,对这个区域进行垃圾回收,经过这次回收还活着的,复制到To区,对象年龄+1

2:清空Eden区、SurvivorFrom

然后清空Eden区和SurvivorFrom区的对象,谁空谁是to。

3:SurvivorTo和SurvivorFrom互换

互换之后SurvivorTo成为下一次GC的From区,当对象年龄达到15,最终如果存活,存入老年代。
—>jvm分区

jvm分区
jvm分区
堆栈方法区
堆管存储,类实例和数组对象存储
栈管运行,存储基础数据类型和引用,栈帧
1.8之前是方法区,1.8之后改为元空间,存储静态变量 + 常量 + 类信息(构造方法/接口定义) + 运行时常量池存在方法区中
—>垃圾回收算法
复制算法,标记清除,标记压缩,引用计数算法,可达性分析算法
复制算法

把空间分成两块,每次只对其中一块进行 GC。当这块内存使用完时,就将还存活的对象复制到另一块上面。

引用计数法:循环引用不可回收,不推荐

GCRoot:可达性分析算法

从根集对象向下搜索,如果一个对象没有任何链相连时,则说明对象不可用。

哪些可以作为GC root的对象
1.虚拟机栈中的引用对象
2.方法区中的类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中引用的对象
如何确定垃圾?
已经不再被内存使用到的空间
JVM虚拟机 YGC和FGC发生的具体场景

YGC和FGC是什么
YGC :对新生代堆进行gc。频率比较高,因为大部分对象的存活寿命较短,在新生代里被回收。性能耗费较小。 (复制算法 —> 一般适用对象存活率低的场景)

FGC :全堆范围的gc。默认堆空间使用到达80%(可调整)的时候会触发fgc。 (标记整理或者标记清除算法 —> 一般适用于对象存活率高的场景)

2、什么时候执行YGC和FGC

1、eden空间不足,执行 young gc

2、old空间不足,perm空间不足,调用方法System.gc() ,ygc时的悲观策略,
dump live的内存信息时(jmap –dump:live),都会执行full gc

3.JVM老年代和新生代的比例
在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。
这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。
默认的,Eden : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 )

频繁 fullgc 的排查
full gc产生场景如上叙述一下
其次猜测一下大概产生原因
尤其是大对象,80%以上的情况就是他。 那么大对象从哪里来的:
【1】数据库(包括 Mysql和 Mongodb等 NOSql数据库),结果集太大;
【2】第三方接口传输的大对象;
【3】消息队列,消息太大;

排查步骤—->
(1):打印GCdetail
-XX:+PrintGCDtails
(2):生成dump文件 注意:dump操作的时候是会发生stop the word事件的,也就是说此时所有的用户线程都会暂停运行
开启XX:+HeapDumpBeforeFullGC
使用jvisualvm查看
<关于top k 问题已经在实际解决Linux 阿里云服务器问题应用>
—>JMM
java memory model
—>垃圾收集器
并行 串行 并发标记 CMS G1 ZGC
G1不产生内存碎片 可精准控制停顿

–>CMS垃圾回收过程
1.总体介绍:
CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动JVM参数加上-XX:+UseConcMarkSweepGC ,这个参数表示对于老年代的回收采用CMS。CMS采用的基础算法是:标记—清除。

CMS
CMS
2.CMS过程:
初始标记(STW initial mark)
并发标记(Concurrent marking)
并发预清理(Concurrent precleaning)
重新标记(STW remark)
并发清理(Concurrent sweeping)
并发重置(Concurrent reset)
5.如何处理接口的重复请求?不得不说他不好好问,目的是问如何保证接口的幂等性?
分布式系统中,服务部署在不同的服务器上,但是数据要保证打到一个redis上
对于每个请求必须有一个唯一的标识。举个例子:订单支付请求,肯定得包含订单 id,一个订单 id 最多支付一次。
每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在数据库中记录个状态,比如支付之前记录一条这个订单的支付流水。
每次接收请求需要进行判断,判断之前是否处理过。比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId 已经存在了,唯一键约束生效,报错插入不进去的。然后系统就不用再扣款了。
在网络延迟传输中,会造成消息队列重试,在重试过程中,消息会存在重复

解决方案:

1.如果是数据库的插入操作,给消息做一个主键,避免出现脏数据。
2.使用第三方做消费记录,例如Redis,全局id为K,消息为V,写入到Redis,消费之前先去查Redis是否存在
–>引申分布式系统中如何生成高效的分布式唯一ID 雪花算法
可用分布式锁,redis 递增,机器的唯一码 拿出几位存为机器id,这样一来每次查询操作相对更快

G1回收器有个非常好的特性就是会不断的帮助JVM调整策略, 会根据实际的GC情况调整年轻代和老年代的比例大小,默认情况下,年轻代最多可以占用60%的堆内存。这其实就是GC的灵活性。

G1的另一个显著特点他能够让用户设置应用的暂停时间,通过参数:-XX:MaxGCPauseMillis来指定,为什么G1能做到这一点呢?也许你已经注意到了,G1回收的第4步,它是“选择一些内存块”,而不是整代内存来回收,这是G1跟其它GC非常不同的一点,其它GC每次回收都会回收整个Generation的内存(Eden, Old), 而回收内存所需的时间就取决于内存的大小,以及实际垃圾的多少,所以垃圾回收时间是不可控的;而G1每次并不会回收整代内存,到底回收多少内存就看用户配置的暂停时间,配置的时间短就少回收点,配置的时间长就多回收点,伸缩自如

Http与Https的区别?
博客:https://blog.csdn.net/guolin_blog/article/details/104546558
总结:首先回答对称加密以及非对称加密的区别。
对称加密比较简单,就是客户端和服务器共用同一个密钥,该密钥可以用于加密一段内容,同时也可以用于解密这段内容。对称加密的优点是加解密效率高,但是在安全性方面可能存在一些问题,因为密钥存放在客户端有被窃取的风险。对称加密的代表算法有:AES、DES等。

而非对称加密则要复杂一点,它将密钥分成了两种:公钥和私钥。公钥通常存放在客户端,私钥通常存放在服务器。使用公钥加密的数据只有用私钥才能解密,反过来使用私钥加密的数据也只有用公钥才能解密。非对称加密的优点是安全性更高,因为客户端发送给服务器的加密信息只有用服务器的私钥才能解密,因此不用担心被别人破解,但缺点是加解密的效率相比于对称加密要差很多。非对称加密的代表算法有:RSA、ElGamal等。
关键词:CA机构
个人理解:https使用的是对称加密与非对称加密相结合的方式。首先双端通信使用非对称加密,客户端加密传输时先请求第三方CA机构,CA加密处理完返回给服务器端,证书中加入了网站的域名,

redis掀桌连问

redis
redis
1.redis的hash怎么实现的?(实现原理)rehash过程
redis初始创建hash表,有序集合,链表时, 存储结构采用一种ziplist的存储结构, 这种结构内存排列更紧密, 能提高访存性能.
hash_max_ziplist_entries和hash_max_ziplist_value值作为阀值,hash_max_ziplist_entries表示一旦ziplist中元素数量超过该值,则需要转换为dict结构;hash_max_ziplist_value表示一旦ziplist中数据长度大于该值,则需要转换为dict结构。
哈希等价于Java语言的HashMap或者是Python语言的字典(Dict)
redis hash 的内部结构.第一维是数组,第二维是链表.组成一个 hashtable.
在 Java 中 HashMap 扩容是个很耗时的操作,需要去申请新的数组,为了追求高性能,Redis 采用了渐进式 rehash 策略.这也是 hash 中最重要的部分.
在扩容的时候 rehash 策略会保留新旧两个 hashtable 结构,查询时也会同时查询两个 hashtable.Redis会将旧 hashtable 中的内容一点一点的迁移到新的 hashtable 中,当迁移完成时,就会用新的 hashtable 取代之前的.当 hashtable 移除了最后一个元素之后,这个数据结构将会被删除.
https://juejin.im/post/5cfe6383e51d45599e019d8f
与java的hashmap的rehash区别
个人理解:hashmap的rehash是一次性拷贝的,不同的是,Redis的字典只能是字符串,另外他们rehash的方式不一样,因为Java的HashMap的字典很大时,rehash是个耗时的操作,需要一次全部rehash。Redis为了追求高性能,不能堵塞服务,所以采用了渐进式rehash策略。
rehash的详细步骤
https://www.cnblogs.com/meituantech/p/9376472.html
与ConcurrentHashMap扩容的策略比较?
ConcurrentHashMap采用的扩容策略为: “多线程协同式rehash“。
1.扩容所花费的时间对比: 一个单线程渐进扩容,一个多线程协同扩容。在平均的情况下,是ConcurrentHashMap 快。这也意味着,扩容时所需要 花费的空间能够更快的进行释放。
2.读操作,两者性能相差不多。
3.写操作,Redis的字典返回更快些,因为它不像ConcurrentHashMap那样去帮着扩容(当要写的桶位已经搬到了newTable时),等扩容完才能进行操作。
4.删除操作,与写一样。
http://xytschool.com/resource/236.html
redis如何保证高可用
保证redis高可用机制需要redis主从复制、redis持久化机制、哨兵机制、keepalived等的支持。
主从复制的作用:数据备份、读写分离、分布式集群、实现高可用、宕机容错机制等。

redis主从复制原理
首先主从复制需要分为两个角色:master(主) 和 slave(从) ,注意:redis里面只支持一个主,不像Mysql、Nginx主从复制可以多主多从。

(1)redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。

(2)通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。

https://blog.csdn.net/itcats_cn/article/details/82428716

说说redis的持久化机制,为啥不能用redis做专门的持久化数据库存储?***

个人理解:强一致性的数据是不适合放在缓存中的。另外MySQL对事务的支持也是redis本身不能达到的,需要单独实现
一般不是说redis or MySQL,而是redis+MySQL
https://blog.csdn.net/u011784767/article/details/76824822
为什么Redis进行RDB持久化数据时,新起一个进程而不是在原进程中起一个线程来持久化数据
(1)Redis RDB持久化机制会阻塞主进程,这样主进程就无法响应客户端请求。
(2)我们知道Redis对客户端响应请求的工作模型是单进程和单线程的,如果在主进程内启动一个线程,这样会造成对数据的竞争条件,为了避免使用锁降低性能。基于以上两点这就是为什么Redis通过启动一个进程来执行RDB了
—单线程的redis为什么这么快
(1)纯内存操作
(2)单线程操作,避免了频繁的上下文切换
(3)采用了非阻塞I/O多路复用机制

1
—Redis的数据类型以及使用场景
(1)String
这个其实没啥好说的,最常规的set/get操作,value可以是String也可以是数字。
一般做一些复杂的计数功能的缓存。

(2)hash
这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,
就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。

(3)list
使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,
做基于redis的分页功能,性能极佳,用户体验好。

(4)set
因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?
因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再启一个公共服务,太麻烦了。

另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。

(5)sorted set
sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。sorted set可以用来做延时任务。最后一个应用就是可以做范围查找

1

redis的过期策略以及内存淘汰机制

redis采用的是定期删除+惰性删除+内存淘汰策略。
[2020年6月29日17:25:36在平时的项目中测试,不定期会产生无用token的key数据,平时可以进行模糊删除]

```
缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。

解决方案:
(一)利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
(二)采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,
异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
(三)提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。
迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。

缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。

解决方案:
(一)给缓存的失效时间,加上一个随机值,避免集体失效。
(二)使用互斥锁,但是该方案吞吐量明显下降了。
(三)双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。
自己做缓存预热操作。然后细分以下几个小点
1 从缓存A读数据库,有则直接返回
2 A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程。
3 更新线程同时更新缓存A和缓存B。

8、如何解决redis的并发竞争key问题

分析:这个问题大致就是,同时有多个子系统去set一个key。这个时候要注意什么呢?大家思考过么。
需要说明一下,博主提前百度了一下,发现答案基本都是推荐用redis事务机制。博主不推荐使用redis的事务机制。
因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。你一个事务中有涉及到多个key操作的时候,
这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。

回答:如下所示
(1)如果对这个key操作,不要求顺序
这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。
(2)如果对这个key操作,要求顺序
假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.
期望按照key1的value值按照 valueA–>valueB–>valueC的顺序变化。这种时候我们在数据写入数据库的时候,
需要保存一个时间戳。假设时间戳如下
系统A key 1 {valueA 3:00}
系统B key 1 {valueB 3:05}
系统C key 1 {valueC 3:10}
那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。
redis分页
HSCAN testHash “0” count 10

注:测试field数量在22条时(没有测试Redis中Hash使分页生效时的field数量的下限),分页未生效。
#mysql 执行一个 sql 的过程
执行完毕之后有一个缓存的过程

mysql
mysql
https://www.cnblogs.com/luoying/p/12073812.html

MySQL分页limit速度太慢的优化方法
1.子查询优化法
先找出第一条数据,然后大于等于这条数据的id就是要获取的数据
缺点:数据必须是连续的,可以说不能有where条件,where条件会筛选数据,导致数据失去连续性
2.limit限制优化法
把limit偏移量限制低于某个数
3.where条件先过滤后分页
wait notify 为什么要搭配使用?
单独调用会报异常
只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的wait(),notify()和notifyAll()方法。因为程序验证通常是在对象的同步方法或同步代码块中调用它们的。如果尝试在未获取对象锁时调用这三个方法,
“java.lang.IllegalMonitorStateException:current thread not owner”。
底层把对象作为一个监视器

{% if post.top %} 置顶 | {% endif %}