Java
int范围,Integer缓存
范围:-232~231
缓存:Integer中有个静态内部类IntegerCache,里面有个cache[],也就是Integer常量池,常量池的大小为一个字节(-128~127)。自动装箱的过程(Integer a=10):引用了valueOf()的方法
补码问题
8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值.
HashMap
- HashMapkey和value可以为Null吗?key为Null的时候怎么存?
允许插入键为 null 的键值对。但是因为无法调用 null 的 hashCode() 方法,通过强制指定桶下标为0来存放
- HashMap的头插法出现环的问题
1、假设旧表的初始长度为2,此时已经在下标为0的位置存放了两个元素,再put第三个元素的时候考虑需要扩容(i=0:3->7->null)
2、此刻有两个线程A,B都进行put操作,线程A先扩容,执行到代码Entry<K,V> next = e.next;执行完这段代码,线程A挂起;此时A的e=3,next=7;
3、线程B正常执行,把原来的table变成一个table,此时(i=2:7->3->null)写回内存
4、线程A被唤醒,此时执行(处理3和7看到的是旧表,因为是上次保存的),处理元素3, 将 3 放入自己栈的新table(3->null)接着处理7,插入后为7->3->null,接着看新表(正常读内存中的新table),因为新表的7的next是3,接着循环,3指向了7形成环链表,3的next为null退出循环。
- hashmap初始化大小的计算
如果有6个元素,JDK处理后会设置成8,但是hashmap有负载因子loadFactor,当元素个数阈值大于等于loadFactor * capacity时,就会进行扩容。即到8*0.75=6时会多进行一次扩容。所以设置成(6/0.75 +1=9)即new HashMap(9),实际容量为16。提高效率
ConcurrentHashMap
- jdk1.7用分段锁和1.8用synchronized和cas有什么区别
1、整体结构:移除Segment,用 Synchronized + CAS 代替 Segment ,这样锁的粒度更小了,并且不是每次都要加锁了,CAS尝试失败了再加锁。
2、put():
JDK1.7:先定位Segment,再定位桶,put全程加锁,没有获取锁的线程提前找桶的位置,并最多自旋64次获取锁,超过则挂起。
JDK1.8:访问相应的bucket时,使用sychronizeded关键字,防止多个线程同时操作同一个bucket,遍历链表更新节点或插入新节点;如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过putTreeVal方法往红黑树中插入节点;更新了节点数量,还要考虑扩容和链表转红黑树
3、扩容:在1.7中,扩容只针对每个segment中的HashEntry数组进行扩容。在rehash的时候是有锁的,所以在rehash的过程中,其他线程无法对segment的hash表做操作,这就保证了线程安全。
1.8中, 遍历整个table,当前节点为空,则采用CAS的方式在当前位置放入fwd
当前节点已经为fwd(with hash field “MOVED”),则已经有有线程处理完了了,直接跳过 ,这里是控制并发扩容的核心
当前节点为链表节点或红黑树,重新计算链表节点的hash值,移动到nextTable相应的位置(构建了一个反序链表和顺序链表,分别放置在i和i+n的位置上)。移动完成后,用Unsafe.putObjectVolatile在tab的原位置赋为为fwd, 表示当前节点已经完成扩容。
ArrayList
- 一个线程读list、一个线程写list,这种操作是否允许?为什么?(fail-fast机制) 不能
fail-fast(快速失败)是在遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改,则会抛出 ConcurrentModificationException 异常。在多线程和单线程环境下都有可能出现快速失败。(单线程会是迭代器,多线程是一个写一个遍历)
原理:集合在被遍历的时候如果内容发生变化,就会改变 modCount 的值。当另一个线程(并发修改)或者同一个线程遍历过程中,在实际访问元素前,都会调用checkForComodification方法,检测 modCount 变量是否为 expectedModCount 值,如果是的话就返回遍历,否则抛出异常,终止遍历。
避免:
1、在单线程的遍历过程中,如果要进行remove操作,可以调用迭代器的remove方法而不是集合类的remove方法
2、使用并发包中的类: CopyOnWriterArrayList代替ArrayList
反射
- .getClass()和.class 区别
.getClass()是一个对象实例的方法,只有对象实例才有这个方法,具体的类是没有的**。**类的Class类实例是通过.class获得的,类没有.getClass()方法。
出现的时期不同:Class.forName()和getClass()是在运行时加载;Class.class是在编译器加载,即.class是静态加载,.getClass()是动态加载。
JVM
java内存溢出
内存溢出:JVM内存溢出分为两种情况,OutOfMemoryError和StackOverflowError。
- OutOfMemoryError是在程序无法申请到足够的内存的时候抛出的异常。
- StackOverflowError是线程申请的栈深度大于虚拟机所允许的深度所抛出的异常。
OOM原因:
- 内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
- 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
- 代码中存在死循环或循环产生过多重复的对象实体;
- 启动参数内存值设定的过小;
StackOverflowError原因:当程序中栈深度所需空间大小超过了虚拟机分配给线程的栈大小时就会出现此error。StackOverflowError发生于单个线程的栈大小无法满足程序所需的栈空间大小时。内存溢出的区域:
- 虚拟机栈:如果虚拟机在扩展栈时无法申请到足够的内存空间会抛出OOM;如果线程请求的栈深度大于虚拟机的最大深度,会抛StackOverflowError
- 本地方法栈:和虚拟机栈一样,不同的是处理的对象不一样,虚拟机栈处理java的字节码,而本地栈则是处理的Native方法。其他方面一致。
- 堆:会抛出OOM
- 方法区:用于存放已被虚拟机加载的类信息,常量,静态方法,即使编译后的代码。只能抛出OOM
OOM排查方案:- 检查代码中是否有死循环或递归调用。
- 检查是否有大循环重复产生新对象实体。
- 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。
- 检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
类初始化时机
(加载的时机由虚拟机决定)
- 遇到new时,必须先初始化
- 对类进行反射调用时
- 当初始花一个类的时候,如果发现父类还没初始化,必须先初始化其父类
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟器会先初始化这个主类。
垃圾回收器CMS及其优缺点,G1和CMS区别
CMS:老年代并行收集器,牺牲吞吐量为代价来获得最短回收停顿时间
- 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。
- 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
- 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。
- 并发清除:不需要停顿。
优缺点:它的主要优点是:并发收集、低停顿。缺点:CMS收集器对CPU资源非常敏感,占用了一部分线程使应用程序变慢,总吞吐量会降低。CMS是基于“标记–清除”算法实现的,所以在收集结束的时候会有大量的空间碎片产生
与G1对比:
- 与CMS的“标记–清理”算法不同**,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的**。
- G1的筛选回收(stop the world事件 根据用户期望的GC停顿时间回收),CMS 在这一步不需要stop the world。G1能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内。
java内存管理
- new出来的对象会存放在堆内存中,栈内存中会保存指向该对象的引用,即对象在堆内存中的地址。
- 栈中的数据和堆中的数据销毁并不是同步的。每个方法在执行时都会建立自己的栈区,方法一旦结束,栈中的局部变量立即销毁,但是堆中对象不一定销毁。因为可能有其他变量也指向了这个对象
- 类中定义的实例成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中)。
Java并发
synchronized锁优化
重量级锁:当使用Synchronized给对象上重量级锁时,该对象头的Mark Word中就被设置为指向Monitor对象的指针。一旦某个线程获得对象锁,其余线程需要阻塞
轻量级锁:轻量级锁的使用场景:如果一个对象虽然有多个线程需要加锁,当加锁时间是分开的,则可以用轻量级锁来优化。每次进行CAS操作,如果成功,说明该线程给对象加锁,如果失败,有两种情况,一是如果其他线程持有了该锁,表明有竞争,升级为重量级锁。二是如果是自己进行重入,需要进行+1计数。
轻量锁适合在竞争情况下使用,其自旋锁可以保证响应速度快,但自旋操作会占用CPU,所以一些计算时间长的操作不适合使用轻量级锁。
偏向锁:在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了减少同一线程获取锁(会涉及到一些CAS操作,耗时)的代价而引入偏向锁。偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作
锁和ThreadLocal区别以及应用场景
Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问,用于在多个线程间通信时能够获得数据共享。而ThreadLocal为每一个线程都提供了变量的副本, 使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享
ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
ThreadLocal应用场景:
当某些数据是以线程为作用域并且不同线程有不同数据副本时,考虑ThreadLocal。
无状态,副本变量独立后不影响业务逻辑的高并发场景。
如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决。还是选择共享数据而不是独立
countdownlaunth cyclicbarrier 信号量
CountDownLatch:用于某个线程A等待若干个其他线程执行完之后,它才执行。给定一个计数count。线程调用CountDownLatch 对象的awiat( )方法时,判断这个计数count是否为0,如果不为0,就进入等待状态。其他线程在完成一定任务时,调用countDown()方法,使计数count减一。直到count的值等于0或者少于0时,A线程运行。
CyclicBarrier:N个线程相互等待,任何一个线程完成某个事情之前,所有的线程都必须等待。
多线程如何保证一个变量的一致性
主要使用到的方法有synchronized
关键字、
volatile关键字、
ReentrantLock同步锁、
java.util.concurrent.atomic工具包
Atomic类的使用
原理:atomic类是通过自旋CAS操作volatile变量实现的。
类型:基本类型(AtomicInteger整形原子类),数组类型(AtomicIntegerArray),引用类型(AtomicReference 引用类型原子类)
Java框架
IOC容器的启动流程
1、创建Bean容器,加载并注册Bean。首先是加载配置文件,通过BeanDefinitionReader抽象接口处理配置信息,实现了配置和解析,将Bean注册到容器中
- 定义的各个 Bean 其实会转换成BeanDefinition 存在于 Spring 的 BeanFactory 中。(保存的核心是一个 beanName-> beanDefinition 的 map)obtainFreshBeanFactory 创建一个新的 BeanFactory、读取和解析 bean 定义。
2、初始化所有的非延迟加载的 singleton beans:实例化、属性注入、初始化、销毁。
AOP实现方式
- JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的实例, 生成目标类的代理对象。
- 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
数据库
数据库幻读问题
幻读:指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的数据行。产生幻读的原因是:行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的间隙
间隙锁:间隙锁(Gap Lock)是Innodb在可重复读提交下为了解决幻读问题时引入的锁机制。
InnoDB的行级锁
记录锁:对某行进行加锁,防止该行被其他操作修改或删除。一般要通过主键或唯一索引加锁,就可以较好的实现
间隙锁:间隙锁基于
非唯一索引
,它锁定一段范围内的索引记录
。间隙锁基于下面提到的Next-Key Locking
算法,请务必牢记:使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。
- 唯一索引/主键+范围查询:select * from test where id between 6 and 16 for update;
- 普通索引+绝对范围查询:select * from test where num>10 for update;锁定范围(10,正无穷)不包括10
- 普通索引+等值查询:select * from test where num = 7 for update;//锁定左右两侧的gap
临键锁:临键锁就是记录锁 + 间隙锁,理解为特殊的间隙锁,他的区间是前开后闭的。需要强调的一点是,InnoDB中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列包括主键列)上不存在临键锁。链接
mysql默认隔离级别
可重复读 (oracle,sql server —read commited)
SQL优化
- 最大化利用索引
- 避免全表扫描
- 减少无效数据的查询
避免索引失效场景
避免在开头模糊查询,会导致全表扫描,尽量在字段后使用模糊查询
避免使用in 和not in ,会导致全表扫描,连续的数据用between代替,子查询用exists代替
避免使用or,会导致全表扫描,使用union代替
避免进行null值的判断,加默认值0,判断0。
避免在where条件中等号的左侧进行表达式和函数操作。将其移动到右侧
– 全表扫描
SELECT * FROM T WHERE score/10 = 9
– 走索引
SELECT * FROM T WHERE score = 10*9查询条件不能使用<>或者 !=
where条件仅包含复合索引的非前置列,这样违反最左匹配,不会走索引
查询Select的优化
- 避免出现select *
- 多表关联查询时,小表在前,大表在后
- 使用表的别名
- 用where代替having
建表的优化
- 在表中建立索引,优先考虑where、order by
- 尽量使用数字型字段,字符型会降低查询和连接的性能,增加存储开销
- 查询数据量大的表 会造成查询缓慢。主要的原因是扫描行数过多。可以通过程序,分段分页进行查询。
- 用 varchar/nvarchar 代替 char/nchar。
聚簇索引和非聚簇索引区别,B+树属于哪种
聚簇索引不是一种索引类型,而是一种数据存储方式
聚簇索引:按照每张表的主键构造一颗B+树,同时叶子结点存放的是整张表的行记录数据,其叶子结点称为数据页,每张表只能有一个聚簇索引。InnoDB通过主键聚集数据,如果没有主键会选择非空的唯一索引,如果没有就隐式的定义一个主键作为聚簇索引(这个字段长度为6个字节,类型为长整形)
优势:数据访问快,因为聚簇索引的数据和索引保存在同一个B+树中。对于主键的排序查找和范围查找速度快。
缺点:1、插入速度依赖于插入排序,按照主键的顺序插入最快,否则会出现页分裂,因此InnoDB一般会定义一个自增的ID列作为主键。2、更新主键的代价高,一般会等定义主键不可更新。3、二级索引访问需要两次索引查找,第一次找到主键的值,第二次根据主键值找行数据(回表查询)
非聚簇索引:将数据和索引分开存储,索引结构的叶子节点指向了数据的对应行,myisam的非聚簇索引的两棵B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。
注意:innodb中,在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值
InnoDB索引实现:
InnoDB使用的是聚簇索引,将主键组织到一棵B+树中,而行数据就储存在叶子节点上,若使用"where id = 14"这样的条件查找主键,则按照B+树的检索算法即可查找到对应的叶节点,之后获得行数据。若对Name列进行条件搜索,则需要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主索引B+树再执行一次B+树检索操作,最终到达叶子节点即可获取整行数据。
主键索引是聚集索引还是非聚集索引?
在Innodb下主键索引是聚集索引,在Myisam下主键索引是非聚集索引
为什么主键通常建议使用自增id
聚簇索引的数据的物理存放顺序与索引顺序是一致的**,即:**只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的。如果主键不是自增id,会不断地调整数据的物理地址、分页。MyISAM的主索引并非聚簇索引,那么他的数据的物理地址必然是凌乱的,拿到这些物理地址,按照合适的算法进行I/O读取,于是开始不停的寻道不停的旋转。聚簇索引则只需一次I/O。
数据库表中重复字段怎么去除
1、group by:主要用于分组统计,一般都是使用在聚合函数中使用
2、distinct:一般用于比较小的表进行去重,会过滤掉多余的重复记录,返回不重复的记录或字段;
大表优化
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降
- 限定数据的范围,比如:当用户查询订单历史时,可以加以限定,控制在一个月的范围内。
- 读写分离,主库负责写,从库负责读。
- 垂直切分:将一张表按列切分成多个表,比如将电商数据库垂直切分成商品数据库、用户数据库。简化了表的结构,方便维护,但主键会出现冗余。
- 水平切分:将一张表中的数据拆分到多个结构相同的表中,比如将一个用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。
Redis为什么单线程的性能这么好
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作 (单进程多线程模型:MySQL)
3、使用多路I/O复用模型,非阻塞IO;Redis 虽然使用单线程模型处理用户的请求,但是它却使用 I/O 多路复用机制并发处理来自客户端的多个连接,同时等待多个连接发送的请求。
多路复用:在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。
Redis缓存问题
- 缓存穿透:指的是对某个一定不存在的数据进行请求,该请求将会穿透缓存到达数据库。查不到
解决方案:
- 布隆过滤器
- 缓存空对象
- 缓存击穿:指一个Key非常热点,不停的扛着大并发,大并发集中对一个点进行访问。
解决方案:
- 设置热点数据不过期
- 互斥加锁:分布式锁
- 缓存雪崩:指的是由于数据没有被加载到缓存中,或者缓存数据在同一时间大面积失效(过期),又或者缓存服务器宕机,导致大量的请求都到达数据库。
解决方案:
- 为了防止缓存在同一时间大面积过期导致的缓存雪崩,合理设置缓存过期时间来实现;
- 为了防止缓存服务器宕机出现的缓存雪崩,可以使用分布式缓存
- 缓存一致性:缓存一致性要求数据更新的同时缓存数据也能够实时更新。
解决方案:
- 在数据更新的同时立即去更新缓存;
- 在读缓存之前先判断缓存是否是最新的,如果不是最新的先进行更新。
要保证缓存一致性需要付出很大的代价,缓存数据最好是那些对一致性要求不高的数据。
覆盖索引
覆盖索引(covering index ,或称为索引覆盖)即从非主键索引中就能查到的记录,而不需要查询主键索引中的记录,避免了回表的产生减少了树的搜索次数,显著提升性能。
使用:
建立了表student,那么现在出现的业务需求中要求根据名称获取学生的年龄,并且该搜索场景非常频繁,那么先在我们删除掉之前以字段name建立的普通索引,以name和age两个字段建立联合索引,sql命令与建立后的索引树结构如下
ALTER TABLE student DROP INDEX I_name;//删掉旧索引
ALTER TABLE student ADD INDEX I_name_age(name, age);//新建联合索引
查询语句:SELECT age FROM student WHERE name = ‘小李’;的执行流程为:
在联合索引中找名为小李的结点;此索引中包含age,直接返回,无需回表查询
计算机网络
Http
版本之间的区别
Http1.0:工作方式是每次TCP连接只能发送一个请求,当服务器响应后就会关闭这次连接,下一个请求需要再次建立TCP连接,就是不支持keepalive。
Http1.1:引入了持久连接,即TCP连接默认不关闭,可以被多个请求服用,增加了keep-alive,一个TCP连接可以允许多个HTTP请求。允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的。服务端是按队列顺序处理请求的,服务器只有处理完一个回应,才会进行下一个回应。假如前面的请求处理时间很长,后面就会有许多请求排队等着,这样就造成了“队头阻塞”的问题;
Http2.0:增加了双工模式,不仅客户端能够同时发送多个请求,服务端也能同时处理多个请求,解决了队头堵塞的问题
IP寻址
同一网段IP寻址,判断两个主机是否是与同一网段,就是借助子网掩码来判断。于是在自己的ARP缓存中查找是否有主机B 的MAC地址,如果能找到就直接做数据链路层封装并且通过网卡将封装好的以太网帧发送有物理线路上去:如果ARP缓存中没有主机B的MAC地址,主机A将启动ARP协议通过在本地网络上的ARP广播来查询主机B的MAC地址,获得主机B的MAC地址后写入ARP缓存表,进行数据链路层的封装,发送数据。
若两个主机处于不同的两个网段,那么就需要借助路由器(网关),路由器充当中间节点,将其拆分为两个网段的ARP地址解析过程。路由器(网关)也有ARP缓存,把同一网段下的IP寻址中的主机理解为路由器,然后路由器再扮演一下主机的角色,就是两个不同网段的IP寻址了。
DNS寻址
检查浏览器缓存中是否缓存过该域名对应的IP地址
如果在浏览器缓存中没有找到IP,那么将继续查找本机系统是否缓存过IP
向本地域名解析服务系统发起域名解析的请求
如果本地DNS服务器也失效,采用转发模式(递归),DNS服务器就会把请求转发至上一级DNS服务器,如果上一级DNS服务器不能解析,则继续向上请求。最终将解析结果依次返回本地DNS服务器,本地DNS服务器再返回给客户机,查询完成。
- DNS在区域传输的时候使用TCP协议,域名解析使用UDP协议。
辅域名服务器会定时(一般3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。区域传送使用TCP而不是UDP,因为数据同步传送的数据量比一个请求应答的数据量要多得多。TCP是一种可靠连接,保证了数据的准确性。
客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过三次握手,这样DNS服务器负载更低,响应更快。
Cookie和Session
cookie和session都是用来跟踪浏览器用户身份的会话方式。
cookie工作原理
- 浏览器第一次发送请求到服务器,服务器创建Cookie,包含用户信息,发送到浏览器。当浏览器再次访问时会携带Cookie,服务器会根据Cookie区分不同用户
session工作原理- 浏览器端第一次发送请求到服务器端,服务器端创建一个Session,同时会创建一个Cookie(name为JSESSIONID的固定值,value为session对象的ID),然后将该Cookie发送至浏览器端
- 当浏览器再次访问时,会根据Cookie的value去查询session对象
区别:- cookie数据存放在客户的浏览器上,session数据放在服务器上
- cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,session安全,将登陆信息等重要信息存放为SESSION;其他信息如果需要保留,可以放在COOKIE中
- session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用COOKIE
- 单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能3K。
Http缓存实现方式
Http缓存主要分为两种:强缓存和协商缓存。两种缓存分别通过Http报文头部不同的字段进行控制
Cache-Control即是用来控制缓存的规则字段
强缓存:所请求的数据在缓存数据库中尚未过期时,不与服务器进行交互,直接使用缓存数据库中的数据。
协商缓存:当强缓存过期未命中或者响应报文Cache-Control
中有must-revalidate
标识必须每次请求验证资源的状态时,便使用协商缓存的方式去处理缓存文件。
协商缓存主要原理是从缓存数据库中取出缓存的标识,然后向浏览器发送请求,验证请求的数据是否已经更新,如果已更新则返回新的数据,若未更新则使用缓存数据库中的缓存数据。
为什么会有TIME_WAITE?什么时候会出现很多 TIME_WAITE???
产生原因:调用close()发起主动关闭的一方,在发送最后一个ACK之后会进入time_wait的状态
TIME_WAIT存在的两个理由:
1 可靠的实现TCP全双工连接的终止
2 允许老的重复的分节在网络上的消逝
状态很多的原因:当请求量比较大的时候,而且所有的请求都是短连接的时候。因为每一个连接在结束4次挥手的时候,都会有一个time-wait状态的socket出现。例如:如果许多连接正在被快速打开和关闭,可能会引起大量的 TIME_WAIT 连接。
解决:
1、扩大资源可用
2、减少短连接次数
TCP状态变化
- 建立连接:
- 开始都是CLOSED,服务器创建socket开始监听,变为LISTEN状态
- 客户端请求连接,发送SYN报文,客户端状态变为SYN_SENT,服务器收到后发送ACK和SYN,服务器状态变为SYN_RCVD,
- 客户端收到确认包后,向服务器发送确认包,建立连接,二者状态都变为ESTABLISHED。
- 断开连接:
- 客户端向服务器发送FIN,请求断开连接,状态变为FIN_WAIT1,服务器收到后向服务器发送ACK,状态变为FIN_WAIT。客户端收到ACK后,状态变为FIN_WAIT2。此时连接断开一半
- 如果服务器发完了,就发送FIN报文,此时服务器进入LAST_ACK状态。
- 客户端收到服务器的FIN后,马上发送ACK给服务器,此时客户端进入TIME_WAIT状态,再过了2MSL长的时间后进入CLOSED状态。服务器收到客户端的ACK就进入CLOSED状态。
BIO和NIO区别
BIO:同步并阻塞,服务器的实现模式是一个连接一个线程,这样的模式很明显的一个缺陷是:由于客户端连接数与服务器线程数成正比关系,可能造成不必要的线程开销
NIO:同步非阻塞的。而服务器的实现模式是多个请求一个线程,即请求会注册到多路复用器Selector上,多路复用器轮询到连接有IO请求时才启动一个线程处理
浏览器渲染的具体过程
浏览器将获取的HTML文档并解析成DOM树。
解析CSS
构建渲染树,浏览器先从DOM结点遍历,寻找适配的CSS样式规则并应用
渲染树布局
将渲染树的各个节点绘制到屏幕上,这一步被称为绘制painting.
分布式一致性session
问题:当有多台服务器时,每次登录后,过段时间总是要再次登录
解决:
- Session复制:将 Tomcat1 的 Session 复制到 不同的服务器上,缺点较大(n台服务器)
- Session 前端存储:cookie存储用户信息,但是不安全
- Session 粘滞:利用 Nginx 可以做四层 Hash 或七层 Hash 的特性,保证同一IP的请求都落在同一台机器上
- 后端集中存储:上面几种的方式我们都是把 Session 存储在应用内存上,应用机器只要重启,Session 就会丢失。利用 Redis 集中存储 Session,Web 应用重启或扩容,Session 也无需丢失。
状态码
3XX系列响应代码表明:客户端需要做些额外工作才能得到所需要的资源。重定向
303:请求已经被处理,但服务器不是直接返回一个响应文档,而是返回一个响应文档的URI。重定向到新的URI
304:说明客户端请求的资源在服务器上没有变过,服务器不重复发送返回304
4XX说明是客户端错误
400:此响应代码通常用于“服务器收到客户端通过PUT或者POST请求提交的表示,表示的格式正确,但服务器不懂它什么意思”的情况。
401:客户端试图对一个受保护的资源进行操作,却又没有提供正确的认证证书。客户端提供了错误的证书,或者根本没有提供证书。
403:该响应代码常用于一个资源只允许在特定时间段内访问,或者允许特定IP地址的用户访问的情况。
操作系统
排查CPU飙高怎么排查
先用top命令,找到cpu占用最高的进程 PID
再用ps -mp pid -o THREAD,tid,time 查询进程中,那个线程的cpu占用率高 记住TID
根据线程号查出对应的线程,进行处理。
容易出现cpu占用过高的几点:
1.代码中写死循环时,一直占用cpu
2.如上在循环中不停的创建对象,也会导致GC频繁
3.System.currentTimeMillis() 采用这种方式去做计时,大概占用了10-20%cpu,因为不停的调用
分页和分段的区别
1、页是信息的物理单位,用户透明,长度固定,把逻辑地址划分为页号和页内地址两部分。为了满足系统管理的需要。段是信息的逻辑单位,用户可见,长度可变。为了满足用户的需要。
2、分页的作业地址空间是一维的,即单一的线性地址空间,程序员只需利用一个记忆符,即可表示一个地址;分段的作业地址空间则是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。
缓冲区与缓存区别
缓存(cache)是在读取硬盘中的数据时,把最常用的数据保存在内存的缓存区中,再次读取该数据时,就不去硬盘中读取了,而在缓存中读取。
缓冲(buffer)是在向硬盘写入数据时,先把数据放入缓冲区,然后再一起向硬盘写入,把分散的写操作集中进行,减少磁盘碎片和硬盘的反复寻道,从而提高系统性能。
进程通信方式的优缺点
- 管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;具备同步机制。
- 共享内存:最快的一种通信方式,多个进程可同时访问同一片内存空间,相对其他方式来说具有更少的数据拷贝,效率较高。不具备同步机制。
- 消息队列:与共享内存和FIFO类似,使用一个路径名来实现各个无亲缘关系进程之间的通信。消息队列相比于其他方式有很多优点:它提供有格式的字节流,减少了开发人员的工作量;具备同步机制。
- 信号量:不具备同步机制。
页面置换算法
LRU:最近最久未使用
实现方式:
- 每次新插入数据的时候将新数据插到链表的头部;每次缓存命中(即数据被访问),则将数据移到链表头部;那么当链表满的时候,就将链表尾部的数据丢弃。在插入数据、删除数据以及访问数据时都是O(n)
- 利用链表和hashmap。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部,如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。
代码:LinkedHashMap底层就是用的HashMap加双链表实现的
Linux命令:
- 使用top命令查看负载,在top下按“1”查看CPU核心数量,shift+"c"按cpu使用率大小排序,shif+"p"按内存使用率高低排序;
- 使用iostat -x 命令来监控io的输入输出是否过大
虚拟内存
虚拟内存是:一个连续的地址空间(这也只是进程认为),而实际上,它通常是被分隔成多个物理内存碎片,还有一部分存储在外部磁盘存储器上,在需要时进行数据交换。所以可能会发生缺页进行页面置换算法