1:关于序列化

  基本上能够序列化的对象都集成了Serializable接口,如果其中的某一个字段不需要被序列化,那么就用transient  修饰一下。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

2:关于数据结构

  a:数据和链表的区别

  基于数组的数据结构特点是查找速度很快,时间复杂度为 O(1),但是删除的速度比较慢,因为每次删除元素的时候需要把后面的所有的元素都要相应的往前移动一位,最坏的情况删除第一个元素,时间复杂度为O(n)。

  基于链表实现的数据结构的特点是删除的速度比较快,但是查找的速度比较慢,每次查找数据的时候都需要从链表头部开始往下遍历,链表查找最坏时间是O(n)。

  b:HashMap

  HashMap 就整合和了数组和链表的有点而设计出来的,它的查找速度为 O(1) + O(a),a为链表长度,事实上hashMap的hash算法能够很好的避免了在插入数据的碰撞问题,所以链表的长度基本不会很长,所以hashMap的查找速度还是很快的。一般地,我们平衡一种结构的性能是看平均时间复杂度的,在 jdk1.8以前hashMap在最糟糕的情况下查找的时间复杂度为 O(1) +O(n) ,n 为数据的大小。在jdk1.8时sun公司对hashMap进行了优化,hashMap的存储结构由原来的数组+链接的结构改成 数组+链表+红黑树的形式。时间复杂度由O(1) + O(n) 降为 O(1) + O(logn);

  深度理解:https://www.noblogs.cn/rainple/blog/17853.html

3:CAS操作

  cas: cas的全名称是Compare And Swap 即比较交换。cas算法在不需要加锁的情况也可以保证多线程安全。核心思想是: cas中有三个变量,要更新的变量V,预期值E和新值N,首先先读取V的值,然后进行相关的操作,操作完成后再向主存中读取一次取值为E,当且仅当V == E时才将N赋值给V,否则再走一遍上诉的流程,直至更新成功为止。就拿上面的i++的操作来做说明,假设当前i=1,两个线程同时对i进行+1的操作,线程A中V = 1,E = 1,N = 2;线程B中 V = 1,E = 1,N = 2;假设线程A先执行完整个操作,此时线程A发现 V = E = 1,所以线程A将N的值赋值给V,那么此时i的值就变成了 2 ;线程B随后也完成了操作,向主存中读取i的值,此时E = 2,V = 1,V != E,发现两个并不相等,说明i已经被其他线程修改了,因此不执行更新操作,而是从新读取V的值V = 2 ,执行+1后N = 3,完成后再读取主存中i的值,因为此时没有其他线程修改i的值了,所以E = 2,V = E = 2,两个值相等,因此执行赋值操作,将N的值赋值给i,最终得到的结果为3。在整过过程中始终没有使用到锁,却实现的线程的安全性。

4:Eureka相关

  缓存机制;

  • 注册:
    • Eureka Client 发起 Register 请求。
    • Eureka Server 将 Eureka Client 信息添加到注册表,同时使 ReadWriteCacheMap 缓存失效。
    • 一段时间后(默认30s),Eureka Server 的后台线程发现 ReadWriteCacheMap 被清空了,就会清空 ReadOnlyCacheMap 缓存。
  • 获取注册表:
    • Eureka Client 发起Get请求。
    • 首先,从 ReadOnlyCacheMap 缓存查询,有就返回。
    • 如果没有,从 ReadWriteCacheMap 缓存查询,有就返回。
    • 如果还没有,就查询内存中的注册表,同时将结果填充到 ReadWriteCacheMap 缓存和 ReadOnlyCacheMap 缓存。

  这就是 Eureka Server 端的缓存机制,通过 ReadWriteCacheMap 缓存和 ReadOnlyCacheMap 缓存减少了注册表的读写冲突,起到了类似于读写分离的效果,进一步保证了 Eureka 的性能。

  同时 Eureka Client 端也缓存了一份注册表信息,周期性的从 Eureka Server 拉取最新的数据。

Java程序是如何运行的:

  我们运行一个main方法,这个时候编译器就会将硬盘中的源程序读取到内存并编译成字节码文件(注意这个字节码文件不一定非要是本计算机编译的,只要是符合字节码文件格式的字节码文件都行,别人电脑传给你的肯定可以;这符合java一处编译到处运行的原则)

  2.我们运行一个main方法的同时,对操作系统来说就是新创建了一个进程,就要在操作系统的堆中申请这个进程的内存空间,我们把这块内存空间称为JVM(注意,假如运行两个java程序那么这里就会创建两个jvm的内存空间)

  3.jvm实例创建成功,就会在这个实例之中的内存空间进行分配,java栈,java堆,方法区等

  4.由于类装载子系统,会把类加载器先加载到java堆中,然后类加载器根据我们的java源程序类名去指定路径中去加载字节码文件(双亲委托机制),放入方法区中

  5.由执行引擎去执行这个字节码文件,伴随着验证、准备、解析和初始化,最终在java堆中生成了Class对象和实例化对象。

  6.假如调用本地方法(就是Native修饰的方法)就会涉及到本地方法栈(和java栈作用差不多,压栈和弹栈),在操作系统栈中压入栈帧,假如在本地方法中还调用java中的方法,这个时候在操作系统的栈中压入一个栈帧,然后下一个栈帧却到了jvm的栈中,很有趣的一个东西。

  7.我们方法调用完毕,栈中栈帧弹出,栈清理完毕;然后gc会对java堆中对象进行回收释放内存空间,然后gc还会对方法区进行清理,自此jvm中的内存空间清理完毕;

  8 操作系统堆jvm进行清理,jvm进程结束。

 

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄