1)jvm垃圾回收机制
这个问题真的是老生常谈了;我是这么去聊的;java主要分为堆栈,开发人员一般会关注堆区域,那么堆区域分为3个大区域;新生代,老年代,永久代。而新生代再分为Eden,S0,S1区,老年代old,永久代p区域。
新生代为什么这么去设计主要是最大努力避免对象进入老年代,而导致老年代对象满造成Full gc,而引发top the world的问题。
一个对象刚开始进入的是新生代eden,s0区域,当对象满的时候出发ygc这个ygc速度很快,基本没有什么影响,然后把ygc存活的对象复制到s1区域,然后会把Eden,s0区域全部清理。这里采用复制清理算法,复制清理主要是解决垃圾碎片问题。
对象放入S1区域有两种情况:
1)每次清理的时候,都会把存活的对象做一个计数器,当对象超过15次的时候,会把对象放到老年代(old),
2)如果这个对象很大;大到S1区域存放不了,也会把对象直接放到(Old)区域。
当老年代满了的情况下就会触发Full gc ,在cms垃圾回收器的情况;触发full gc会导致所有工作线程全部暂停,等full gc完成 其他的线程才会继续使用。
所以我们在设置垃圾回收内存的时候;是需要根据实际业务场景来考虑,毕竟Old内存设置太大,频率会降低,但是每次gc时间也会很长,如果设置太小;虽然gc耗时缩短了,但是频率也会增加。
cms垃圾回收器主要问题在于,每次的回收时间不可控,也就是我们没办法知道每次回收的耗时需要多久。
所以在jdk8以后增加了g1的垃圾回收器,这个垃圾回收器每次触发full gc的耗时是可控的,我们只要通过参数设置即可。
那它是怎么实现的呢?这里引入了元空间的一个概念。 g1的逻辑分代区域都没有变;主要是老年代发生变化。它把内存分为多个region块,大概是[1m~32m]之间,这么设计主要就是能够通过,用户设置的full gc耗时来控制回收多少个内存块。这样设计控制粒度更精细了。 比如1毫秒,那么就大概可以预估我用回收多少个region块了。
这么设计就解决了cms触发耗时不可控的问题!
2)在实际工作中有没有解决gc的问题。
业务背景是这样,某银行系统每天都会假死一次,影响千万用户转账。从物理内存,jvm内存,cpu,日志层面排查都很正常,加机器,重启系统只能缓解问题。
测试环境通过压测;通过jstat 查看内存情况,发现oid出现大量的full gc情况。 再通过jstack 排查堆栈日志,定位到full gc的日志块,再通过日志块定位到代码行数,发现代码层面上;在用户登陆退出的时候调用system.gc来清理内存。 由于大量的gc调用堆积导致系统假死。
cms在调用gc的时候,实际上只是用很少的线程去回收,工作线程都会处于等待。所以在排查其他的指标上看不出异样。
2)hashmap 和Hashtable的区别
这道题也是老生常谈的,我并没有去很细的去了解,就结合我自己经验去讲了。
我是这么来回答:
A) hashmap首先是我们常用的数据结构,但是它也有很多缺点。
1)首先它是线程不安全的,所以我们在并发的场景有update并不推线使用它。我们可以通过ConcurrentHashMap保证线程安全。
2)还有对于大量数据存储使用的时候也不推荐,因为它的底层是用链表来实现随着它的量越大它的遍历性能会越来越低。复杂度是属于O(n)级别。
3)如果需要本地缓存也不推荐使用,因为它没有很好的内存管理,如果我们需要一条数据在本地缓存使用1天清理一遍,这个就控制不了。这种场景可以通过Guava Cache封装的map实现更合适。
B) 而对于Hashtable这个是线程安全的,性能不没有hashmap好;工作中不常用没有做太多了解。
3)redis常用数据结构
string/list/hash/set/zset
4)redis为什么性能会比较高
redis为什么性能会高,首先它是个可以作为缓存数据库,数据可不用落磁盘。
当然它的底层处理链接的技术也有很大关系,主要通过reactor模式处理链接数,而reactor模式底层使用的是io多路复用技术,这个技术是操作系统级别的,目前操作系统io多路复用技术;主要还是使用epoll来实现,epoll能够同时处理多少连接数,受限系统的句柄数,而系统句柄数受限于物理内存。
也就是说如果我们内存够用,系统句柄数就可以设置非常大,而redis通过io多路复用技术就能够把处理并发连接数给提上来。这也就是官方redis说单机能实现10qps的勇气。
目前io多路复用很多技术产品都有在使用,比如nginx ,netty,tomcat,kafka等。
没有展开来讲select,poll,epoll 以及相关数据结构。
5)redis缓存穿透,缓存雪崩问题,如何优化
对于redis缓存穿透;我们是通过空key来解决,也就是说当一个缓存过期的时候,我们避免它直接去查询mysql,而是设置一个永久 null key返回,这样保证了mysql数据库的稳定性提供有损服务。
也可以通过分布式锁来优化,比如查询redis没有的情况下,再去获锁;获得锁的线程去查数据库,查到数据再更新到redis,其他没有获得锁的直接返回一个打底信息。
雪崩的的问题;就是大量数据同时过期,我们是可以通过把key耗时设置不同时段来避免,也可以通过锁,null key来缓解。
当然我们对redis key的设计要严格管理。经常去做cr,很多团队对redis key并没有做太多的管理,而更容易引发问题。
6)你有没有使用过redis锁
redis 分布式锁有很多种方案;常见的主要是通过nx px ,或者Redisson来实现。
nx px主要的问题是设置锁的时间是固定的,如果任务没有处理完,但是锁过期了没办法做时间续租,而Redisson解决了续租的这个问题,但是redis是属于异步复制的架构,ap模型;就是在极端的情况下可能会数据丢失,比如redis集群;我们创建一个锁在a节点,如果我们用的是rdb同步,设置5s进行rdb同步,在5s的间隔期间,a节点挂了,那么可能导致其他的用户去获得锁。电商的情况下可能导致超卖的情况 。
所以如果使用分布式锁,我们也要结合场景去选型,如果在并发并不高的情况;对一致性要求比较高,我们可以使用zookeeper来实现分布式锁,zk是属于ap模型,对一致性要求非常高,在节点挂的情况下对外是不可用的,当对外可用的情况下一定是数据已经同步好的。
当然还有其他的分布式锁框架,比如etcd等。
7)kafka为什么性能比较高
kafka的话我可以从4个点来讲,通过集群提高它的高可用。当然我们开发人员主要还是比较关注topic,那么topic下有个分区的概念,这个分区特点就是提高性能,可扩展性等。我们可以结合业务去合理的提高它的分区,当然消费者数量也要跟的上,最好是1:1 。
至于怎么配置分区,消费者需要结合实际场景去考虑,分区数太大,会占用很多内存,而且分区的数据同步也是个问题。
分区下面有个副本的概念,这个主要提供容灾能力,一个分区会有多个副本;每个副本数据都是一样的,存储在不同的节点中,如果某个分区挂了的情况下,重新选举最大能力可以保证数据不丢失,当然数据丢失的粒度在于我们是追求性能,还是追求数据可靠性,可以结合acks来调整平衡。
8)kafka 零拷贝技术
讲这个我们首先要了解两个状态,为了考虑安全问题,操作系统会分为两个状态就是内核态,用户态。所有用户的操作都是在【用户态】所有系统级别的调用都在【内核态】。
比如我们要去读磁盘数据;传统的数据传递一般先是通过【内核态】去从磁盘去读数据,切到【用户态】去传递到用户进程,用户态可能会做一些处理然后再通过内核态传递到系统socket再发送到网卡等。
【内核拷贝到用户态】
【用户态拷贝到内核态】
这里就是两次拷贝了。那么在kafka中呢,它都是一些消息数据期间并不需要对消息做一些处理,所以它在内核态的情况下读取磁盘数据后然后直接发送到网卡,省去了两次拷贝。
9)mysql 乐观锁 悲观锁 ,mysql事物级别
这里我结合cap理论来讲,性能,和数据一致性一定是互斥的。就看我们怎么从业务层面怎么去追求平衡。
mysql它提供了4种事物隔离级别,不同级别有不同的方案,读未提交,读以提交,可重复读,串行化
如果我们需要数据完全可靠的话就用【串行化】级别就好了。 如果我们追求绝对性能的话我们可以用【读未提交】,当然这也完全是裸奔。mysql默认的隔离级别是【可重复读】。