个人记录和整理

okhttp

程序里直观请求是由request和okhttpclient一起组成,request包含请求的参数,url,header等等服务端需要的一些信息,okhttp声明的时候实例化dispatcher,调用newcall发起请求实则是由realcall开始,realcall里的同步和异步请求call对象会存到dispatcher里,它里面包含了三个请求队列:1,异步等待call队列,2,异步运行call队列,3,同步运行call队列,加入1和2的是asynccall,3是reallcall,1和2的评判是根据如果最大请求数目64或者同一host下请求数超过5个则放入1。不管同步还是异步都会调用核心方法:getResponseWithInterceptorChain(),里面声明一个集合按顺序依次放入各种interceptor,用户自己定义的放第一,其次是:失败和重定向(RetryAndFollowUpInterceptor),客户端与服务端桥接(BridgeInterceptor),读取缓存和更新缓存(CacheInterceptor),与服务器建立连接(ConnectInterceptor),从服务器读取响应数据(CallServerInterceptor)等迭代器;接着声明RealInterceptorChain,调用它的第一次proceed(request)方法,该方法通过迭代器集合取出各个interceptor并调用intercept()方法传入再次声明的RealInterceptorChain对象,因为每个迭代器里的具体方法都调用了RealInterceptorChain的proceed(request)方法,这样通过递归的方式(next索引每次加1)依次执行完所有的Interceptor来共同驱动okhttp工作。

HashMap

hashmap 数组加链表的结构,超过 16(默认数量)*0.75(负载因子)12个需要扩容,扩容时(双倍)会重新hash并定位,耗性能,所以最好能提前传入大小,并且扩容时的resize方法,并不是线程安全,并发操作容易形成环形链表,获取一个不存在的key时计算出的index正好是环形链表的下标就会出现死循环;使用entrySet迭代器while同时遍历出key和value,而keySet则需要先查出key在查value效率低,1.8修改为红黑树查询后效率提高但是并发问题还是存在。

1.7put:判断Entry table是否需要初始化,key为null则put一个null进去,根据key的hashcode方法计算出hash值,在利用hash值算出在table中的索引,将索引处Entry的key和hash与传入key(内存和值随便一个相等)和key的hash比较是否相同,如果相同则替换value,并返回旧的value,不相同说明是新数据则调用addEntry,此时需要判断扩容。

1.7get:key为null返回null,不为null和put一样根据key计算出hash,接着算出在table中的索引,将索引处的entry的hash,key与传入的key,计算出的hash比较,相同则取出并返回value,没有找到相对应的key的值也返回null。

concurrenthashmap

同样是数组加链表的结构,采用分段锁,其中 Segment 继承于 ReentrantLock。不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,解决了HashMap的并发问题。


          java8中 是用尾插法扩容 java7以前都是头插法 所以java8不会有 死循环问题。

Lrucache 近期最少使用算法

核心思想优先淘汰那些近期最少使用的缓存对象,内部使用的是LinkedHashMap双向链表结构,LHM的构造传入accessOrder为true时这个集合的顺序就会是访问顺序,也就是将访问后的数据放到最后面,put方法每次都会调用sizeof方法累加大小,需要用户自己计算并返回,默认为1,并且会根据key查找之前关联过的value,找到就返回并且相应的size减掉(重复算过了)最后调用trimTosize(maxsize)判断是否超过maxsize,超过就将LHM里存放时间最久的取出并移除,size减掉;get方法最重要的是LHMget方法里afterNodeAccess方法,这个方法的作用就是将刚访问过的元素放到集合的最后一位,所以没有用到的会被移到LHM的头部,trimToSize方法将其移除。


创建对象时构造器的调用顺序是:先初始化静态成员(父→子),然后初始化非静态成员(父→子),最后调用构造器(父→子)。

垃圾收集算法

标记-清除算法:分为标记和清除两部阶段,首先标记所需要回收的对象,在标记完成后统一回收所有被标记的对象,1:效率低,标记和清除过程都不高,2:空间问题,标记清除后会产生大量不连续的内存碎片,空间碎片太多导致在需要分配大对象时无法找到足够的连续内存而不得不在提前触发一次垃圾回收动作。

复制算法:按内存容量划分为大小相等的两块,每次只需要其中的一块,当一块内存用完了将存活的对象复制到另一块上面,然后再把刚刚用完的内存空间一次清理掉,解决了内存碎片问题,但是可用内存就缩小为一半。

标记整理算法:标记过程同标记-清除算法一样,但是在后续步骤不是直接对对象进行清理,而是让所有存活的对象都向一侧移动,然后直接清理掉端边界以外的内存。

分代收集算法:当前商业虚拟机的GC都是采用分代收集算法,根据对象存活周期的不同将堆分为:新生代和老年代,方法区为永久代(新版本已经将永久代废弃,引入元空间概念,永久代使用JVM内存而原空间直接使用物理内存),根据各个年代的特点采用不同的收集算法,新生代中的对象产生与死亡比较频繁,使用复制算法。新生代又分为Eden区和Survivor区(Survivor from Survivor to)比例8:1:1。新产生的对象优先进入Eden去,当Eden区满了之后在使用Survivor from区,当Survivor from区也满了之后就进行Minor GC(新生代GC):将Eden和Survivor from中存活的对象copy进入Survivor to,然后清空Eden和survivor from,这个时候的Survivor from就成了新的Survivor to(满状态未使用),原来的Survivor to就成了Survivor from(有存活对象,等待装满去Survivor to),Survivor  from和Survivor  to不断的交换自身角色进行对象的存储和清除。复制的时候如果Survivor to无法容纳全部存活对象根据老年代的分配担保将对象copy进去老年代,如果还是无法容纳则进行Full GC(老年代GC)。大对象直接进入老年代,JVM中的参数:-XX:PretenureSizeThreshold,大于这个值进入老年代,目的是为了避免Eden和Survivor区之间进行大量的内存复制。长期存活的对象进入老年代:JVM给每个对象一个年龄计数器,如果对象在Eden出生并经过第一次新生代GC后,并且能在Survivor区中容纳,那么该对象年龄设定为1,每熬过一次新生代GC,年龄就加1,直到一定程度(默认为15,可以通过XX:MaxTenuringThreshold来设定),就会移入老年代。如果Survivor区中相同年龄(如年龄为x)所有对象大小总和大于Survivor的一半,年龄大于等于x的所有对象直接进入老年代,无需等到最大年龄要求。

 

 

猜你喜欢

转载自blog.csdn.net/ak47985/article/details/81460685