Skip to content

Instantly share code, notes, and snippets.

@zstraw
Last active July 19, 2017 05:39
Show Gist options
  • Save zstraw/9c5a3e93734e195a598b20ae4e0572e2 to your computer and use it in GitHub Desktop.
Save zstraw/9c5a3e93734e195a598b20ae4e0572e2 to your computer and use it in GitHub Desktop.

Http状态码

  • 200 成功
  • 204 No Content(没有内容)
  • 206 Partial Content(部分内容)
  • 301 Moved Permanently(永久重定向)
  • 302 临时重定向
  • 400 Bad Request(坏请求,语法格式错误)
  • 401 未授权
  • 403 Forbidden(禁止)
  • 404 页面不存在
  • 405 Method Not Allowed(不允许使用的方法)
  • 411 Length Required(要求长度指示)
  • 413 Request Entity Too Large(请求实体太大)
  • 414 Request URI Too Long(请求URI太长)
  • 500 Internal Server Error(内部服务器错误)
  • 501 Not Implemented(未实现)
  • 502 Bad Gateway(网关故障)
  • 503 服务器过载或维护
  • 504 Gateway Timeout (网关超时)
  • 505 HTTP Version Not Supported(不支持的HTTP版本)

Http1.0,1.1,2.0区别

  • http1.1:
    1. 支持tcp连接复用
    2. 允许client未收到回复就发出下一个请求,但服务端只能按顺序回复
    3. 默认双方都支持长连接,但是request或response中有一个出现connection为close时,在请求处理完会立刻关闭tcp连接
    4. 可以只发送头而无内容,也可以发送部分内容,实现断点续传
    5. 增加HOST请求头字段,可以实现通过一个ip和端口使用不同主机名创建多个虚拟web站点
  • http2.0:
    1. 采用二进制格式而非文本格式. 二进制协议解析起来更高效、“线上”更紧凑,更重要的是错误更少。
    2. 完全多路复用的.
    3. 使用报头压缩
    4. 服务器可以推送到客户端缓存中,可以一个请求多个响应

String的hashCode

val[0]*31^(n-1)+val[1]*31^(n-2)+...val[n-1] 选择31的原因:

  1. 31可以被编译器优化成左移5位-1,计算更快
  2. 数较大,有利于分布尽量均匀不重复
  3. 质数不容易产生重复

final

  • final类不能被继承,没有子类,final类中的方法默认是final的。
  • final方法不能被子类的方法覆盖,但可以被继承。
  • final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
  • final不能用于修饰构造方法。
  • 不能修饰抽象类
  • 高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。内联 父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。

内部类

  • 静态内部类:static修饰
    1. 只能访问外部静态成员和静态方法
    2. 不能与外部类重名
  • 成员内部类:只有外部实例化后,才可实例化
  • 局部内部类:在方法中.不能有访问修饰符及static;只能访问方法中final局部变量
  • 匿名内部类:无类名.必须继承类或实现接口;
    1. 局部内部类的所有限制
    2. 不能有static
    3. 必须跟在new后
    4. 只能创建该类的实例
    5. 不能定义构造函数

任何指针的.getClass都是取得当前类.要取父类必须.superClass().getClass()

跳出多层循环break out; out: for(...)自定义点 continue也同样用

可能内存泄露的情况

  • 静态容器
  • 没有显式释放连接资源
  • 监听器对象销毁时没有释放监听器
  • 不合适的作用域
  • 单例模式

[TOC] #2 简单动态字符串 SDS Redis中C字符串只作为不修改的字符串字面量,如日志.其它的用SDS

sds.h/sdshdr
struct sdshdr{
    int len;        //已使用长度(不包括'\0')
    int free;      //未使用长度
    char buf[];  //与c字符串一样
}

SDS与C串区别:

  1. 获得长度快O(1)
  2. 杜绝缓冲区溢出
  3. 减少修改字符串时的内存重分配(1空间预分配-未超过1M时,使用多少,也会分配出多少未使用的空间;超过1M时,分配1M的未使用空间2惰性空间释放-即默认不释放缩短后多余空间,得手动释放)
  4. 可以存图片这样的二进制文件信息,因为不会以\0作为结束标志
  5. 兼容部分C串函数

#3 链表

  • 链表节点 adlist.h/listNode
typedef struct listNode{
    struct listNode *prev;
    struct listNode *next;
    void *value;
}listNode;
  • 链表 adlist.h/list
typedef struct list{
    //表头节点
    listNode *head;
    //表尾节点
    listNode *tail;
    //链表包含的节点数
    unsigned long len;
    //节点值复制函数
    void *(*dup)(void *ptr);
    //节点值释放函数
    void (*free)(void *ptr);
    // 节点值对比函数
    int (*match)(void *ptr, void *key);
}list;

#4 字典 哈希表 dict.h/dictht

typedef struct dictht{
    //哈希表数组
    dictEntry **table;
    //表大小
    unsigned long size;
    //用于计算索引值,等于size-1
    unsigned long sizemask;
    //已有节点数
    unsigned long used;
}dictht;

哈希表节点 dict.h/dictEntry

typedef struct dictEntry{
    void *key;
    //value
    union{
        void *val;
        uint64_t u64;
        int64_t s64;
    }v;
    //下个哈希表节点,用来解决键哈希时冲突的情况
    struct dictEntry *next;
}dictEntry;

字典 dict.h/dict

typedef struct dict{
    //类型特定函数
    dictType *type;
    //私有数据
    void *privdata;
    //哈希表(一般只用ht[0]这张表,只有当rehash时,把新的表放在ht[1]中,结束后就释放0的空间,把1改成0,1重新开辟新的空间)
    dictht ht[2];
    //rehash索引, 当rehash不在进行时,值为-1
    int trehashidx;
}
typedef struct dictType{
    //计算哈希值函数
    unsigned int (*hashFunction)(const void *key);
    //复制键的函数
    void *(*keyDup)(void *privdata, const void *key);
    ....
}
  • 索引index=hash&dict->ht[x].sizemask,即保存在ht[x]哈希表的第index位置.当索引值一样时,发生键冲突,采用链表的方式解决冲突
  • rehash
    1. 扩展,ht[1]取第一个>=ht[0].used*2的2^n(条件:1.服务器没执行BGSAVE或者BGREWRITEAOF命令,且负载因子>=1;2.在执行...命令,且负载因子>=5)
    2. 收缩,取第一个>=ht[0].used的2^n(当负载因子<0.1时)

过程:

  1. 为ht[1]分配空间
  2. 把rehashidx设为0
  3. rehash期间,对字典CURD,会顺带把ht[0]表在rehashidx索引上的所有键值对rehash到ht[1].完成后,rehashidx++
  4. 随着字典操作的不断执行,0表会全rehash到1表上,rehashidx变为-1,完成rehash rehash完0表中删去,添加到1表中;新添加的对全放1表中;查询时0表找不到会从1表中找

5章 跳跃表

跳跃表节点 redis.h/zskiplistNode 创建表节点时,会随机生成1-32的值作为level数组大小,越大概率越低

typedef struct zskiplistNode{
    //层
    struct zskiplistLevel{
        //前进指针
        struct zskiplistNode *forward;
        unsigned int span;
    }level[];
    struct zskiplistNode *backward;
    //根据分值大小排序
    double score;
    //成员对象
    robj *obj;
}zskiplistNode

6章 整数集合

typedef struct intset{
    //编码方式
    uint32_t encoding;
    uint32_t length;
    int8_t contents[];
}
  • 有序,不重复
  • 当有无素过大时,进行升级,全部替换
  • 不支持降级

7章 压缩列表ziplist

|z||||

8章 对象

对象类型有:REDIS_STRING字符串, REDIS_LIST列表, REDIS_HASH哈希, REDIS_SET集合, REDIS_ZSET有序集合

  • 字符串对象 int编码;大于32字节时,编码为raw;小等于32字节时,编码为embstr(优化的用于保存短字符串的方式:redisObject,sdshdr两块连续内存区域一次分配,也只需要释放一次,能够更好利用缓存带来的优势)

9章 数据库

Redis中所有数据库都保存在redisServer.db数组中,数据库由dict和expires两个字典构成 数据库的键是一个字符串对象,值是任意一种Redis对象

  • 启动Redis时,若启动RDB,则载入RDB文件时:
    1. 以主服务器模式运行,过期键不会载入;
    2. 以从服务器模式,载入所有键,与主服务器同步时删除过期键
  • 以AOF持久化模式运行,当过期键被惰性或定期删除时,会向AOF文件append一条DEL命令,显式记录该键已被删除 执行AOF重写时,已过期的键不被重写
  • 复制模式
    1. 主删除过期键后,会向所有从发送DEL命令,告知从删除该键
    2. 从执行客户端的读命令时,对所有键一致对待
    3. 从只有收到主的DEL时,才会删过期键

数据库通知:SUBSCRIBE,订阅消息包括两种:对某个键执行什么命令,某个命令被哪些键执行了

10章 RDB持久化

SAVE:会阻塞进程,到RDB文件创建完毕,期间无法处理任何命令请求 BGSAVE:会开子进程负责创建,服务器(父进程)继续运行.执行期间,拒绝SAVE,BGSAVE命令,BGREWRITEAOF命令会在GBSAVE命令结束后执行.若BGREWRITEAOF在执行,则BGSAVE会被拒绝 实际执行由rdb.c/rdbSave完成 RDB载入在服务器启动时自动检测载入,载入期间处于阻塞态 AOF更新频率比RDB高,若开启AOF,则优先AOF还原,只有关闭后才会使用RDB还原

###自动间隔性保存 默认每隔100ms执行一次serverCron,满足任一条件就执行保存 服务器默认save条件设置(BGSAVE):900s内至少1次修改,300s内10次修改.... save 900 1 save 300 10 save 60 10000 一次修改三个键算三次修改,dirty+3

struct redisServer{
    //记录保存条件的数组
    struct saveparam *saveparams;
    //修改计数器(距离上次成功SAVE,BGSAVE后进行了多少次修改)
    long long dirty;
    //上一次执行保存时间(距离上次成功SAVE,BGSAVE的时间)
    time_t lastsave;
}
struct saveparam{
    time_t seconds;
    int changes;
}

###RDB文件结构

REDIS db_version databases EOF check_sum
REDIS db_version databases EOF check_sum

13章 客户端

普通客户端被关闭原因:

  1. client进程网络连接关闭
  2. client向server发送带有不符合协议格式的命令请求
  3. client成为CLIENT KILL命令的目标
  4. 设置了timeout选项.当client空转时间超过timeout时.例外情况client是主服务器(打开REDIS_MASTER标志),从服务器(打开REDIS_SLAVE标志),正在被BLPOP等命令阻塞(打开REDIS_BLOCKED标志),或正执行SUBSCRIBE,PSUBSCRIBE等订阅命令,超时也不会关闭
  5. client发送的命令请求大小超过输入缓冲区限制大小(默认1G)
  6. 要发送给client的命令回复超过了输出缓冲区的限制大小(hard limit:超过立即关闭;soft limit: 超过后开始监视,如一直超出且持续时间超过设定值,则关闭;如果不再超出,则不被关闭,且obuf_soft_limit_reached_time也会被清零)

AOF伪客户端在载入完成后会被关闭

14章 服务器

####服务器命令请求执行过程:

  1. client把命令请求转换成协议格式,通过socket发送到服务器
  2. server读取命令,存入clientStatus的输入缓冲区里
  3. 读取分析命令,提取命令及参数和参数个数放入argv和argc中
  4. 调用命令执行器执行

####serverCron函数

  1. 更新服务器时间缓存:redisServer中time_t unixtime; long long mstime;两个字段,每100ms更新一次,精度不高,只在对时间精度要求不高的场景使用
  2. 更新LRU时钟:redisServer中unsigned lruclock字段,用于计算键的空转时间,10s更新一次;每个redis对象都有的unsigned lru,保存对象最后一次被命令访问的时间.空转时间=lruclock-lru
  3. 更新服务器每秒执行命令次数,100ms一次,估算
  4. 更新内存峰值
  5. 处理SIGTERM信号:处理该信号时打开关闭标识shutdown_asap,serverCron每次都会检查该标识,为1时关闭服务器关闭前会先进行RDB持久化
  6. 执行clientsCron:客户端连接超时释放;输入缓冲区在上次执行命令请求后超过一定的长度,会被释放,并重新创建一个默认大小的缓冲区
  7. databaseCron:删除过期键,对字典进行收缩
  8. 执行被延迟的BGREWRITEAOF:执行BGSAVE期间,发来BGREWRITEAOF命令,会在save结束后执行
  9. 会检查BGSAVE,BGREWRITEAOF的子进程是否完成操作:若rdb_child_pid和aof_child_pid都为-1,会执行1)是否有BGREWRITEAOF被延迟 2)是否满足自动保存条件,如果有且没有其它持久化操作,则开始新的BGSAVE 3)检查AOF重写条件是否满足,满足且没其它持久化操作,开始新的BGREWRITEAOF
  10. 将AOF缓冲区中的内容写入AOF文件.若开启AOF,且AOF缓冲区里有待写入的数据
  11. 关闭异步客户端:输出缓冲区大小超出限制
  12. 增加cronloops:记录执行serverCron的次数

16章 sentinel

sentinel监视到主服务器下线时,会自动将从服务器上升为主服务器,若其上线,则会将它转成从服务器 redis.h/redisServer结构

struct sentinelState{
    //当前纪元,用于实现故障转移
    uint64_t current_epoch;
    //保存被监视的服务器,指向sentinelRedisInstance结构(可以是主,从服务器或其它sential)的指针
    dict *masters;
    //是否进入TILT模式
    int tilt;
    //目前正在执行的脚本数量
    int running_scripts;
    //进入TILT时间
    mstime_t tilt_start_time;
    //最后一次执行时间处理器的时间
    mstime_t previous_time;
    //包含了所有需要执行的用户脚本
    list *scripts_queue;
}sentinel;

####sentinel会

  • 与监视的主服务器间创建两个异步网络连接:命令连接(收发命令),订阅连接(订阅_sentinel_:hello频道)
  • 默认10s1次向被监视的主服务器发送INFO命令,会返回主服务器及从服务器的信息.会检查sentinelRedisInstance的slaves里是否有对应的从服务器,有则更新,没有新建
  • 出现新的从服务器时,会创建连接到从服务器的命令和订阅连接,默认10s一次发送INFO
  • 2s1次通过命令连接向所有主从服务器发送PUBLISH sentinel:hello "<s_ip>.....";通过订阅连接向该频道接收信息
  • 当sentinel向_sentinel_:hello频道发送消息时,所有订阅该频道的sentinel都会收到该消息,若是自己发出的则丢弃,否则对相应主服务器的实例进行更新
  • sentinelRedisInstance中的sentinels保存了除自己以外的所有监视该主服务器的sentinel
  • 当发现新的sentinel时则互相建立命令连接
  • 检测主观下线状态:1次/s向所有创建了命令连接的服务器(主从及其它sentinel)发送PING,回复有有效回复(+PONG,-LOADING,-MASTERDOWN),及其它无效回复.当超过down-after-milliseconds没有有效回复,则进入主观下线状态.这也成为判断该主服务器的所有从服务器及其它sentinel主观下线的标准
  • 检测客观下线:会向其它sentinel询问,若其它认为主观下线的数量超过一定数时,认为客观下线.并会进行选举,并由leader进行主服务器的故障转移,规则如下:
  1. 进行一次选举,epoch自增一次
  2. 一个纪元里,所有sentinel都有一次选举的机会,并且在该纪元内不能更改
  3. 每个发现主服务器客观下线的sentinel都会要求别人把自己设为局部leader
  4. 当一个sentinel(源)向另一个(目标)发送is-master-down-by-addr时,且runid不是*而是源sentinel的运行ID时,表示源要求目标把前者设为后者的局部leader
  5. 先到先得,最早发送上述命令的将成为目标的局部leader
  6. 目标收到is-master-down-by-addr,会回复源自己的局部leader的runid和configuration epoch.
  7. 源收到回复后,检查与自己的configuration epoch是否相同,相同且leader_runid与源的runid一致,则目标将把源sentinel设置成局部leader
  8. 如果有半数以上的sentinel把某个设为局部leader,则其成为leader
  9. 如果一定时间内未选出leader.则一段时间后继续选举直到选出Leader.

17章 集群

一共有16384个slot,节点会与分配的slot对应起来,放在clusterState.slots数组中,每个slot对应一个clusterNode.clusterNode.slots数组中记录了slot指派信息,用一个二进制位来表示,1为使用,0为不使用

clusterState中zskiplist *slots_to_keys,保存slot与数据库key的关系,key先通过CRC生成校验和,再&16384进行哈希,其中跳跃表的score存slot,object存key

ASK,MOVED错误:

MOVED错误会把所有请求转向该错误指向的节点 ASK只是在接下来的一次请求转至指示的节点,之后仍然会把请求转向当前节点

复制与故障转移

节点分为主从节点:主用于处理slot,从用于复制某个主节点,主下线时,从替代它. 节点成为从节点,并开始复制某主节点这一消息会通过消息发送给所有其它节点.

故障检测
  1. 每个节点都会向其它节点定期发送PING,如果没在规定时间内返回PONG,就标记疑似下线(probable fail, PFAIL)
  2. 当主节点A收到主B认为主C疑似下线消息时,A会在C的clusterNode中添加list *fail_reports
struct clusterNodeFailReport{
    //报告目标节点下线的节点,即谁发的此报告,这里为B
    struct clusterNode *node;
    //最后一次从node节点收到下线报告的时间
    //用来判断下线报告是否过期,与当前时间相差太久会被删除
    mstime_t time;
}
  1. 当半数以上负责处理slot的主节点都把某主节点x报告为疑似下线时,它将被标记为已下线FAIL,并通知所有节点,其它节点会立即标记
故障转移

当从节点发现在复制的主节点下线,会执行:

  1. 选举一个从节点作为主节点:有投票权的主给第一个发现的从投票,超过半数的成为新的主
  2. leader执行SLAVEOF no one,成为主
  3. 把slot指派改成指向自己
  4. 向集群广播PONG,通知已接管
  5. 开始接收新的命令请求
消息
  • MEET:向接收者发送,请求对方加入集群
  • PING:
  1. 默认每隔1s随机选出5个,找最长时间没发送过的发送,来检测对方是否在线
  2. A最后一次收到B的PONG消息时间距离现在,超过A的cluster-node-timeout的一半,A也会向B发.(防止长时间没随机选中B导致对B的信息更新滞后)
  • PONG:
  1. 收到PING和MEET时,返回.
  2. 向集群发送,让其它节点刷新对自己状态的改变.(如故障转移成功后)
  • FAIL:只存有下线节点的名字(集群中节点名字唯一)A判断B下线时,广播,收到的立即对B标记下线(因为Gossip协议要等待,传播速度会变慢)
  • PUBLISH:节点接收到执行完时会广播,所有接收的点都会执行相同的PUBLISH命令

Gossip协议由MEET,PING,PONG实现,发送时会往clusterMsgDataGossip gossip[1]中添加两个随机的节点信息,接收方收到时,进行更新这两个点的信息

客户端向集群中的点发送PUBLISH 时,接收的节点会向该channel发送message,并广播一条PUBLISH消息,所有其它点也会发送.

18章 发布与订阅

struct redisServer{
    //保存所有频道的订阅关系
    dict *pubsub_channels;
    //保存所有模式订阅关系
    //其中存放着pubsubPattern结构
    list *pubsub_patterns;
}
typedef struct pubsubPattern{
    //订阅模式的客户端
    redisClient *client;
    robj *pattern;
}pubsubPattern;

pubsub_channels的key为频道名,value为客户端链表

19章 事务

MULTI事务开始,输入非EXEC,DISCARD,WATCH,MULTI四个命令入队,EXEC执行事务,执行完把返回结果放到队列中,然后清除事务标记,再返回结果

WATCH是乐观锁,在事务开启后,执行前监视某些数据库键,一旦修改,拒绝执行事务

WATCH实现:
typedef struct redisDb{
    dict *watched_keys;
}
  1. 其中key为被监视的键,value为记录监视该键的客户端
  2. 执行修改命令如:SET,LPUSH,SADD,ZREM,DEL,FLUSHDB等后,会调用multi.c/touchWatchKey函数,如果该键被监视,则该客户端的REDIS_DIRTY_CAS被打开
  3. 服务器收到EXEC命令时,会根据是否有REDIS_DIRTY_CAS来决定执行事务:打开的话拒绝事务
  • 原子性:Redis事务不支持事务回滚,中间有命令错误,后续命令也会执行
  • 一致性:错误命令不入队;出错命令进行相应处理,不执行;服务器停机:若无持久化,则数据库空白,数据一致.若RDB或AOF,则会恢复还原到一个一致状态,找不到则为空白.

在AOF模式下,appendfsync设为always时,事务具有耐久性.

IoC容器

容器初始化

BeanFactory是基本的简单容器,ApplicationContext是对BeanFactory的扩展,封装了更多方法供用户使用.

ApplicationContext通过refresh方法初始化:

  1. 准备环境

  2. 初始化BeanFactory(obtainFreshBeanFactory),并读取xml文件(getBeanFactory时对beanFactoryMonitor加锁):

    • 在refreshBeanFactory中调用loadBeanDefinition,先创建BeanDefinitionReader,再对其进行相应配置,然后就与简单容器BeanFactory一样加载BeanDefinition.
    • 在BeanDefinitionReader.loadBeanDefinition,把Resource然后变成InputStream, InputSource, 最后在doLoadBeanDefinitions中把它变成变成Document对象
      1. 通过DocumentLoader,转换成Document;
      2. 解析并注册BeanDefinition(registerBeanDefinitions):用BeanDefinitionDelegate类解析成BeanDefinitionHolder,再把它里面的BeanDefinition放进beanDefinitionMap中
      3. prepareBeanFactory,对其进行功能填充 后面的是对beanFactory的功能扩展,如激活处理器,注册拦截器,国际化处理,注册监听器等

创建bean:getBean方法

  1. 从单例的缓存中取,如果是factoryBean,用工厂方法生成bean
  2. 找不到再从父容器中找
  3. 还找不到就取得合并的BeanDefinition
  4. 实例化依赖,放入缓存
  5. createBean:
    • 如果存在重写之类的配置,则生成代理bean,在此应用了实例化前后的后处理器applyBeanPostProcessorsBefore/AfterInitialization;
    • 核心doCreateBean:
      1. 如果是单例,先清缓存
      2. 实例化bean,将BeanDefinition转换成BeanWrapper(1.用工厂方法 2.根据参数选择构造函数 3.用默认构造方法);对beanDefinition.constructorArgumentLock加锁
      3. MergedBeanDefinitionPostProcessor应用.autowired注解实现
      4. 依赖处理,单例用ObjectFactory从缓存中取
      5. 属性填充populateBean
      6. 循环依赖检查,非单例的循环依赖抛异常
      7. 如果配置destroy-method,注册DisposableBean

AOP

  1. ProxyFactoryBean通过属性配置target和拦截器,或ProxyFactory用编程方式来配置
  2. 上述类默认使用DefaultAopProxyFactory生成AopProxy对象(用JDK的Proxy-InvocationHandler的invoke或CGLIB实现)
  3. 对应每个Advice通知,都有一个AdviceAdapter适配器.需要将适配器注册,适配成拦截器.
  4. 代理的接口被调用时,JDK的invoke方法和CGLIB的callback方法实现了拦截.遍历拦截器链,proceed方法,调用完才调用目标方法.

MVC

在DispatcherServlet的doDispatch中,

  1. getHandler:遍历handlerMappings取得本次请求对应的HandlerExecutionChain,其中包含一个handler和拦截器链.
  2. 取得HandlerAdapter,先处理所有拦截器,再调用它的handle方法,执行Controller的业务逻辑,返回ModelAndView
  3. 解析视图,渲染返回

#struts与spring mvc区别

  1. spring mvc核心控制器是servlet,struts是filter
  2. spring mvc是基于方法设计,struts是基于对象,每个请求都会实例化一个action对象
  3. spring mvc用方法参数实现参数传递,struts是用值栈实现
  4. spring mvc拦截器是方法级拦截,用AOP实现,struts是类级别的拦截
  5. spring mvc对于ajax请求,可以直接转换成json数据返回

分布式锁

实现方式有:

数据库实现

  • 方法一:数据库增加一条记录,方法名唯一.添加成功则加锁成功,删除记录解锁 缺点:
    1. 如果添加锁后宕机会导致释放锁失败
    2. 数据库崩溃造成session丢失
  • 方法二:数据库的行锁实现.insert into ...... for update.排他锁x

缓存实现

zookeeper实现

MySql提供的四个事务隔离级别

  • Serializable:全避免
  • repeatable read:避免脏读,不可重复读.mysql默认
  • read committed:避免脏读
  • read uncommitted:不能避免

三个范式

  • 1NF:属性不可再细分
  • 2NF:满足1,且非主键必须完全依赖于主键,而不能部分.如订单商品表中,[orderProduct]orderId,productId,price,productName.主键为两个id,price依赖于两个,而productName只依赖于productId.这样就要把productName与productId独立分出一张表
  • 3NF:满足2,且非主键与主键必须直接依赖,不可存在传递依赖,即非主键AB,主键C,不能A依赖B,B再依赖C.如【Order】(OrderID,OrderDate,CustomerID,CustomerName),CustomerName依赖CustomerID,再依赖主键OrderID.此时应该分出另外的表

sql语句执行顺序

from->on->join->where->group by->having->select->distinct->order by->top

sql语句优化

  1. where,order by加索引
  2. where中尽量不用is null.索引失效
  3. where尽量不用!= <>.索引失效
  4. where尽量不用or,用多次查询union all来优化,针对索引列
  5. where尽量不用in/not in,连续值可以用between,用exist替换 exist(select )
  6. where尽量不用模糊匹配'%...'.索引失效
  7. where尽量不用表达式,如num = 100*2.索引失效
  8. 不在where子句中用变量 num =@num.编译期无变量值,无法优化. 改成 from t with(index(索引名)) where num =@num.强制索引
  9. 不在where子句中对字段进行函数操作,如取子串substring.索引失效
  10. 用>=替换>
  11. 大写关键字

2章 java内存区域与内存溢出异常

##运行时数据区域

###程序计数器

  • 正在执行的(下条)字节码指令地址
  • 若执行的是Native方法,该值为空 ###虚拟机栈
  • 存储局部变量表,操作数栈,动态链接,方法出口等
  • 局部变量表存放:编译期可知的各种基本数据类型(int,boolean,byte,char,short等),对象引用,returnAddress类型
  • 栈深度超出报StackOverflowError
  • 无法申请到足够内存,抛OutOfMemoryError
  • 为字节码服务 ###本地方法栈
  • 为Native方法服务 ###java堆 存放对象实例
  • 新生代(Eden,From Survivor,to Survivor),老年代. ###方法区(永久代,非堆)
  • 存放类信息,常量,静态变量,JIT编译后的代码等 jdk1.7后把字符串常量池移到堆中 ####运行时常量池
  • 方法区的一部分
  1. 存放编译期生成的字面量和符号引用,翻译出的直接引用
  2. 运行期也可能把常量放进池中.如String的intern()方法 ###直接内存
  3. NIO引入的可以直接操作堆外内存,避免Java堆和Native堆中来回复制
  4. 适合频繁I/O操作,大数据缓存,长生命周期 ##JVM对象 ###对象创建
  5. 遇到new指令时,检查是否能在常量池定位一个类符号引用,并检查引用代表的类是否被加载,解析,初始化过.若没有,则执行类加载
  6. 分配内存.两种方法:1内存规整,指针碰撞(把已分配和未分配空间分界点指针往空闲方向移动对象大小的距离)2,不规整,空闲列表(分配完后更新列表上的记录)
  • 解决多线程问题:1CAS失败重试;2每个线程在java堆中预先分配内存,本地线程分配缓冲(TLAB),哪个线程要分配就在它的TLAB上分配,用完再分配新的TLAB时才同步锁定.
  1. 设置对象头.
  2. 执行init方法 ###对象内存布局
  3. 分为:对象头,实例数据,对齐填充(对象大小必须是8字节的整数倍)
  4. 对象头:
  • 存储自身的运行时数据MarkWord,如:hashCode,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等
  • 类型指针,指向类元数据指针.
  • 若为数组,则有用于记录数组长度的数据 ###对象访问定位 局部变量表中的符号引用指向句柄或直接指向对象(或指向到对象类型数据的指针)
  • 句柄:java堆中划分一块内存作为句柄池,句柄包含对象实例数据与类型数据各自的具体地址信息
  • 直接指针:类型数据置于方法区中,只能通过堆中的一个指针来指向.可以直接在栈中指向在堆中的对象

3章 垃圾收集器与内存分配策略

垃圾收集器

G1收集器

特点:

  1. 并行与并发
  2. 分代收集
  3. 空间整合:把堆分成很多个Region.新生代老年代物理不隔离,都放在各个Region中
  4. 可预测的停顿:跟踪每个Region的垃圾堆积的价值大小,维护一个优先列表.
  • 每个Region都有一个对应的Remembered Set来避免对全堆的扫描
  • 不算维护Remembered Set,运作分为:
  1. 初始标记
  2. 并发标记
  3. 最终标记(同CMS的重新标记)
  4. 筛选回收

GC日志

  • DefNew:default new generation,Serial收集器
  • ParNew:parallel new generation,ParNew收集器
  • PSYoungGen:Parallel Scavenge收集器

内存分配与回收策略

对象优先在Eden分配

空间不够时发起Minor GC -XX:+PrintGCDetails打印内存回收日志

大对象直接进入老年代

-XX:PretenureSizeThreshold设置大对象的标准,只有Serial和ParNew有效

长期存活的对象进老年代

-XX:MaxTenuringThreshold设置年龄

GCRoots

动态对象年龄判定

如果在Survivor中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄>=该年龄的对象直接进入老年代

空间分配担保

当老年代最大可用连续空间小于新生代总空间时,HandlePromotionFailure允许担保失败.取之前每一次回收晋升到老年代对象容量大小的平均大小作为经验值,再决定是否Full GC.

6章 类文件结构

Class类文件结构

  • 以8个字节为基础单位的二进制流
  • 只有两种数据类型:无符号数和表

magic魔数与Class文件的版本

  • 前4字节CAFEBABE
  • 2字节次版本号
  • 2字节主版本号:jdk1.7为51及以上

常量池

  • u2(2字节)的常量池容量计数值
  • 主要存放字面量和符号引用.字面量:字符串,final常量等;符号引用:类和接口的全限定名,字段的名称和描述符,方法的名称和描述符
  • 第一项常量均为一个表

7章 JVM类加载机制

类加载时机

生命周期包括:加载,验证,准备,解析,初始化,使用,卸载.其中斜体的统称为连接 除了解析,其它的按顺序开始,但会交叉的进行. 加载时机,有且只有:

  1. 遇到new,getstatic,putstatic,invokestatic字节码指令,如果类未初始化.最常见场景:new一个对象,读取或设置静态字段(除final修饰或编译期已把结果放入常量池的),调用静态方法.final常量在编译期放入常量池
  2. 对类进行反射调用
  3. 初始化一个类时,发现父类未初始化.不要求所有父接口初始化,只有被使用时才会初始化,如使用父接口中的常量
  4. 主类(包含main方法的类)
  5. java.lang.invoke.MethodHandle实例解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,且该方法对应的类未初始化

类加载过程

加载,验证,准备,解析,初始化

加载

  1. 通过类的全限定名获取二进制字节流
  2. 把其代表的静态存储结构转化为方法区的运行时数据结构
  3. 生成java.lang.Class对象,放方法区 数组类不通过类加载器创建,由JVM创建.但其中的元素类型靠类加载器创建

验证

  1. 文件格式验证 魔数,版本号,常量池中是否有不支持的常量(看tag),索引是否指向不存在的常量等....
  2. 元数据验证 语义分析(是否有父类,是否父类有final修饰等..)即关于类继承,重载,数据类型等语法是否合法
  3. 字节码验证 确定程序主义是否合法,即类的方法体 如:不能跳转到方法体以外的字节码指令上,保证类型转换有效,操作栈一个int数据不能按long加载入本地变量表等..
  4. 符号引用验证 把符号引用转成直接引用,即解析时发生. 如:符号引用中通过全限定名能否找到类,符号引用中的类,字段,方法的访问性等

准备

为类变量分配内存(被static修饰的变量),并设置默认初始值.特殊情况,类字段的字段属性表中存在ConstantValue属性,会被设成ConstantValue所指定的值,即static final变量

解析

把常量池中的符号引用替换为直接引用 主要针对类或接口,字段,类方法,接口方法,方法类型,方法句柄,调用点限定符

初始化

执行类构造器方法

  • 该方法由类变量赋值及静态语句块组成,按顺序生成.
  • 静态语句块能对后面定义的类变量进行赋值,但不能访问
  • 该方法非必须,如果没有类变量赋值及静态语句块,则没有.
  • 接口不能有静态语句块,其父接口方法不会先被执行
  • 多线程中会被加锁

类加载器

通过类的全限定名获取其二进制字节流

双亲委派模型

  • 启动类加载器(Bootstrap ClassLoader),JVM自身的一部分.<JAVA_HOME>/lib
  • 其他类加载器,独立于JVM.继承自java.lang.ClassLoader
  1. 扩展类加载器(Extension ClassLoader).<JAVA_HOME>/lib/ext
  2. 应用程序类加载器(Application ClassLoader),又叫系统类加载器.一般默认为此加载器.
  • 所有类加载请求都先交给顶级加载器处理,处理不了再交给子加载器处理

JVM字节码执行引擎

运行时栈帧结构

JVM栈存放局部变量表,操作数栈,动态连接,返回地址和一些附加信息

局部变量表

存放方法参数和方法内部定义的局部变量

  • 以Slot为最小单位
  • reference:1.可以直接或间接找到对象在堆中的起始地址索引;2.直接或间接找到对象所属类在方法区中的类信息
  • 非static方法第0位Slot默认为this引用
  • slot会被复用,出了作用域后,如果未被复用,会仍然与GCRoots保留着关联,不会被回收
  • 局部变量无默认初始值

动态连接

每个栈帧都有一个指向运行时常量池中该栈帧所属方法的引用

方法调用

编译完所有方法只是符号引用,类加载或运行时才转成直接引用

解析

5条方法调用字节码指令:

  1. invokestatic:调用静态方法
  2. invokespecial:调用实例构造器方法,私有方法和父类方法
  3. invokevirtual:调用虚方法
  4. invokeinterface
  5. invokedynamic 其中1.2以及final修饰的invokevirtual方法可以称为非虚方法,类加载时解析

分派

重载,重写实现

静态分派:重载

//实际类型改变
Human man = new Man();
man = new Woman();
//静态类型改变
sayHello((Man)man);
sayHello((Woman)man);

依赖静态类型的分派,典型应用重载,发生在编译阶段

  • 重载匹配方法优先级:'a'->char->int->long->float->double->Character->Serializable->Object->可变长参数char...等

动态分派:重写,多态

invokevirtual指令会查看栈顶第一个元素对象的实际类型,根据方法从下往上遍历查找与常量中描述符和简单名称一致的方法,找到就执行,并返回

  • 实现:在方法区建立一个虚方法表,其中存放各个方法的实际入口地址,若没有被重写则都指向父类的实现入口.一般在类加载的连接阶段初始化

动态类型支持

MethodHandle模拟字节码指令

//第一个参数为方法返回类型,第二个及以后为方法的具体参数
MethodType mt = MethodType.methodType(void.class,String.class);
MethodHandle handle = lookup().findVirtual(obj.getClass(), "println",mt);
handle.invokeExact("helloWorld");

10章 早期(编译期)优化

编译

  1. 解析与填充符号表过程:词法,语法分析;填充符号表
  2. 插入式注解处理器的注解处理过程:
  3. 分析与字节码生成过程:标注检查;数据及控制流分析(final在编译期检查,是否final编译后的代码都一样);解语法糖;字节码生成(添加init/clinit方法,替换+为StringBuffer/StringBuilder)

java语法糖

泛型,自动拆装箱,遍历循环,变长参数,条件编译,内部类,枚举类,断言语句,枚举和字符串的switch,try中的定义和关闭资源

泛型

  • 方法重载要求具备不同特征签名,返回值不在其中
  • Class文件格式中,描述符不同可以共存
  • 两个方法有相同名称和特征签名,但返回值不同,可以共存于一个Class文件中

11章 晚期(运行期)优化

  • 热点代码会编译成本地平台相关的机器码,并进行优化.JIT
  • 解释器和编译器混合

热点代码探测:

  1. 基于采样:某方法经常出现在栈顶
  2. 基于计数器:统计方法执行次数.方法调用计数器,回边计数器

经典的优化技术

  1. 公共子表达式消除
  2. 数组范围检查消除
  3. 方法内联
  4. 逃逸分析

3.Climbing Stairs

斐波纳契函数 f(n)到n阶的方法数=f(n-1)+f(n-2) 通项公式为

$$(1/sqrt(5))*{[(1+√5)/2]^n - [(1-√5)/2]^n}$$

$$ 1/sqrt{5} $$

原则

  1. 单一职责原则:高内聚、低耦合
  2. 开闭原则:对扩展开放,对修改关闭
  3. 里氏代换原则:任何基类可以出现的地方,子类也可以出现
  4. 依赖倒置原则:针对抽象编程,不针对具体
  5. 迪米特原则:系统中的类,尽量不要与其他类互相作用,减少类之间的耦合度
  6. 合成复用原则:多用组合,少用继承
  7. 接口隔离原则:用多个专门的接口,而不使用单一的总接口
  1. 团队协作.使用git工具来管理项目,各个模块间,前后端的沟通配合,以及跟市场部的需求探讨.
  2. 代码规范.从变量名,类名,文件名的命名规范,到对空指针等常见异常的避免,再到设计类,方法时的一些原则,如单一职责原则,依赖倒置原则,多组合少继承
  3. 技术的学习.包括java的基础,虚拟机知识的加深巩固,框架的学习等
  4. 学习应用新知识的能力.例如使用websocket.
  5. 解决问题的能力.学习未掌握的技术,或用已掌握的技术来实现需求.另外遇到问题,通过自己查找资料,分析定位问题原因,然后解决.比如一次遇到内存泄露问题,通过-XX:+HeapDumpOnOutOfMemoryError把内存dump出来,用mat查看找问题的原因是静态容器中的元素没有清除
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment