Java后端开发面试宝典

Java后端开发面试宝典,第1张

欢迎大家点赞+收藏哦
  • JavaSE篇
    • 1. Java面向对象的思想
    • 2. 面向对象与面向过程
    • 3. 面向对象的三大特性
    • 4. 类与对象
    • 5. 抽象类和接口的区别
    • 6.Java中的异常体系
    • 7. Java的数据类型
    • 8. String,StringBuffer,StringBuilder
    • 9. ==和equals()的区别
    • 10. final关键字
    • 11. static关键字
    • 12. 重载和重写
    • 13. 构造方法不能重写
    • 13. 自动拆箱,自动装箱
    • 14.jdk和jre的区别
  • 数据结构篇
    • 1. Collection和Collections的区别
    • 2. List,Set,Map的区别
    • 3. Array和ArrayList的区别
    • 4. ArrayList和LinkedList区别
    • 5. HashMap和Hashtable的区别?底层实现?
    • 6. 一些线程安全的集合类
    • 7. ConcurrentHashMap
    • 8. HashMap扩容流程
    • 9. 常见的排序算法
  • MySQL篇
    • 1.索引
      • a.索引的基本原理
      • b.索引的四种类型
      • c.索引的数据结构
      • d.索引的实现原则
    • 2. 事务
      • a.事务的四大特性(ACID)
      • b.并发事务问题
      • c.事务隔离级别?Mysql的默认隔离级别是什么?
      • d.ACID靠什么保证的?
    • 3.锁
      • a.对MySQL的锁了解吗?
      • b.隔离级别与锁的关系
      • c.什么是死锁?怎么解决?
      • d.MySQL的锁有哪些,如何理解?
      • e.数据库的乐观锁和悲观锁是什么?怎么实现的?
    • 4.MySQl慢查询该如何优化?
    • 5.sql的优化做过吗?有哪些策略?
    • 6.MyISAM与InnoDB的区别
    • 7.为什么建议InnoDB表必须建主键?并且推荐使用整形的自增主键?
    • 8.为什么非主键索引结构叶子节点存储的是主键值?
    • 9.最左前缀原则
    • 10. 为什么不建议用 select*from... 来查找数据?
  • 四:线程篇
    • 1.并发,并行,串行区别
    • 2.并发的三大特性
    • 3.线程的生命周期,以及线程有哪些状态?
    • 4.sleep,wait,yield,join
    • 5.对线程安全的理解
    • 6.说说你对守护线程的理解
    • 7.为什么使用线程池?解释一下线程池的参数?
    • 8.线程池四种创建方式
    • 9.如何理解volatile关键字?
    • 10.synchronized和lock都能进行加锁,那么区别是什么呢?
    • 11.什么是线程死锁?
    • 12.形成死锁的四个状态?
    • 13.如何避免死锁?
    • 14.线程与进程的区别?
    • 15.什么是单例模式?单例模式中饿汉模式与懒汉模式的区别
  • 五:网络
    • 1. TCP与UDP的区别和使用场景
    • 2. TCP是如何保证可靠性传输的
    • 3.HTTP和HTTPS的区别
    • 4.输入一个URL的全过程
    • 5.ARP协议
    • 6.UDP协议
    • 7.状态码
    • 8.三次握手,四次挥手
      • 三次握手
      • 四次挥手
    • 9.GET和POST的区别
  • 六:JVM篇
    • 1. 垃圾回收机制
      • 1.1 判断是否是垃圾对象的算法
        • 1.1.1引用计数法:
        • 1.1.2 可达性分析法:
      • 1.2 垃圾回收算法
        • 1.2.1 标记清除法
        • 1.2.2 标记整理算法
        • 1.2.3 复制算法
        • 1.2.4 分代收集算法
    • 2.类加载机制
    • 3.JVM内存管理
      • 3.1 方法区
      • 3.2 程序计数器
      • 3.3 虚拟机栈(线程栈)
      • 3.4 本地方法栈
      • 3.5 堆

JavaSE篇 1. Java面向对象的思想

面向对象是一种更加优秀的程序设计方法,它的基本思想是使用类,对象,继承,封装,多态,等基本概念进行程序设计。他是从现实中客观存在的事物出发来构造软件系统,并在系统构造中尽可能运用人类的自然思维方式,强调直接以现实世界中的事物为中心进行思考,认识问题,并且根据这些事物的本质特点,把它们抽象的表示为系统中的类,作为系统的基本构成单元,然后映像到客观世界,并保持客观世界中事物及其相互关系的本来面貌。
将所有预处理的问题抽象为对象,同时了解这些对象具有哪种属性以及展示这些属性的行为,以解决这些对象面临的一切实际问题。

2. 面向对象与面向过程

面向对象是把构成问题的事务分解成各个对象,建立对象不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为,以功能划分问题。易维护,易复用,易扩展,低耦合,但是性能比面向过程低。
面向过程就是分析出解决问题所需的步骤,然后把这些函数一步步实现,使用的时候一个个调用就可以了。性能比较高,但是比较消耗资源,不易维护。
举一个洗衣服的例子来进行理解:
面向过程:打开洗衣机盖子,放入衣服,放入洗衣液,启动洗衣机,洗衣,甩干(注重过程)
面向对象:分为人和洗衣机两个对象,人去打开盖子,放入衣服,放入洗衣液,启动洗衣机;洗衣机洗衣服,甩干.(注重各项功能)

3. 面向对象的三大特性

封装: 将对象的属性和行为封装起来,通常对客户隐藏其实现的细节。保证了类内部的数据结构的完整性,应用该类的用户不能轻易的直接 *** 作此数据结构,只能执行运行公开的数据,提高了程序的可维护性。
继承: 实现代码复用的重要手段,允许程序在保持原有类特性的基础上进行扩展,增加新的功能。
多态: 子类对象可以直接赋给父类变量,但运行时依然表现出子类的行为特征,这意味着同一个类型的对象在执行同一个方法时,可能表现出多种行为特征。提高程序的可扩展性,让代码更加的简洁和优美。(对与同一行为,不同的子类对象有不同的表现,例如动物吃食物,猫吃鱼,狗吃骨头)
如果硬要说是四个特性的话,就再加一个抽象:是指把一些不同类型的物体的共有属性或者行为抽取出来,然后构成一个公共的父类。然后当使用这些公共的属性或者行为的使用,就可以通过父类去调用,这样的话,就不用去考虑具体应该由谁去调用这个行为,屏蔽了类型的细节,用起来更方便。

4. 类与对象

类: 对同一种事物的总称统称。如动物,人类,树等等。
类是一个模型一样的东西,对一个实体对象进行描述的,主要描述该实体对象 具有哪些属性,哪些功能.
对象: 是事物存在的实体,现实中,随处可见的就是对象,比如人啊,猫啊,柳树啊等等,是通过类实例化出来的,一个类可以实例化多个对象

5. 抽象类和接口的区别
  • 抽象类可以存在普通成员函数,而接口中只能存在public abstract方法
  • 抽象类中的成员变量可以是各种类型的,而接口中只能是public static final类型的。
  • 抽象类只能继承一个,接口可以实现多个
  • 抽象类中可以有构造方法,而接口中不能有
6.Java中的异常体系
  • Java中的所有异常都来自顶级父类Throwable.Throwable下有两个子类Exception和Error
  • Error是程序无法处理的错误,一旦发现这个错误,程序就会被迫停止允许。
    Exception不会导致程序停止,又分为两个部分RunTimeException运行时异常和CheckedException检查异常
  • RunTimeException发生在程序运行过程中,会导致程序当前线程执行失败。
    CheckedException常常发生在程序编译过程中,会导致程序编译不通过。
7. Java的数据类型
  • 整数类型:byte,short,int,long -----------对应的字节:1, 2, 4, 8
  • 小数类型:float,double------------------对应的字节:4,8
  • 字符类型:char------------------------------对应的字节:2
  • 布尔类型:boolean ------------------------对应的字节:没有明确的规范
8. String,StringBuffer,StringBuilder
  • String是底层结构是final修饰的,不可变,每次 *** 作都会产生新的对象
  • StringBuffer和StringBuilder都是在原对象上进行 *** 作的
  • StringBuffer是线程安全的,StringBuilder是线程不安全的
  • StringBuffer的方法通过synchronized修饰,所以安全
  • 性能:StringBuilder>StringBuffer>String
  • 场景:经常需要改变字符串内容时优先使用后面两个
9. ==和equals()的区别

== 运算符: 作用于基本类型值时,是比较两个数值是否相等。
作用于引用数据类型时,是比较两个对象的内存地址是否相同,即判断他们是否为同一个对象
equals(): 没有重写时,默认使用==实现的,
进行重写后,按照对象的内容来进行比较。内容相同返回true,不同就false。

10. final关键字

修饰类:表示类不可以被继承
修饰方法:表示方法不可以被子类覆盖
修饰变量:表示变量一旦被赋值就不可以更改它的值
(1).修饰成员变量:
修饰静态变量:只能在静态初始化块指定初始值或者声明该类变量时指定初始值
修饰成员变量:可以在非静态初始化块,声明该变量或者构造器中执行初始值
(2).修饰局部变量
系统不会为局部变量进行初始化,局部变量必须由程序员显示初始化。可以在定义时指定默认值,也可以不指定默认值,但是在后面给变量赋值时只能赋值一次
(3).修饰基本数据类型和引用数据类型
基本数据类型:数值一旦初始化后就不能修改了
引用数据类型:初始化之后就不能再让指向另一个对象,但是引用的值是可以变的

11. static关键字

可以修饰成员变量,成员方法。
a. 被static修饰的成员称为静态成员,不属于某个具体的对象,是所有对象所共享的,
不存储在某个对象的空间中,既可以通过对象访问,也可以通过类名访问,生命周期:随类的加载而创建,类的卸载而销毁。比如定义一个学生类,一个班里的学生其他属性不一定相同,但是教室是共用的。
b. 被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的,特性:不属于某个具体的对象,是类方法,没有隐藏的this引用参数,因此不能在静态方法中访问任何非静态变量,静态方法无法调用任何的非静态方法,因为非静态方法有this引用参数
c. static存在的意义:创建独立与具体对象的变量或方法,即使没有创建对象,也能使用属性和方法。可以形成静态代码块以优化程序的性能,类加载的时候,静态代码块只能执行一次,因此,很多的时候都会将一些只需要进行一次初始化的 *** 作都放在static代码块中进行。

12. 重载和重写

重载

  • 在同一个类中,同名的方法有着不同的参数列表(参数类型不同,参数个数不同,参数顺序不同)则称为重载。与返回值类型无关。
  • 重载(Overload)是一个类中多态性的一种表现

重写

  • 发生在父类和子类之间
  • 方法名,参数列表,返回值类型必须相同
  • 子类方法访问修饰符的权限一定不能低于父类方法的访问修饰符
    权限比较:(public>protected>default>private)

重载和重写的区别

区别重载重写
概念方法名相同,参数列表不同,与返回值类型无关方法名,参数列表,返回值类型全相同
范围同一个类中继承关系中
限制无限制子类的方法的修饰符权限不低于父类
多态编译时的多态运行时的多态
13. 构造方法不能重写

构造方法需要和类名保持同名,而重写是要保证子类方法和父类方法同名,如果允许的话,子类中会存在和类名不同的构造方法,这个要求是矛盾的。

13. 自动拆箱,自动装箱

装箱:把一个基本类型的数据直接付给对应的包装类型
拆箱:把一个包装类型的对象直接付给对应的基本类型
可以大大简化基本类型与包装类对象之间的简化过程,比如:某个方法的参数类型为包装类型,调用时我们所持有的数据却是基本类型的值,则可以不做任何特殊的处理,直接将这个基本类型的值传给方法即可。

14.jdk和jre的区别

JDK是Java的开发工具,它不仅提供了Java程序运行所需的JRE,还提供了一系列的编译,运行等工具,如javac,java,javaw等。JDK包含了JRE和JVM
JRE只是Java程序的运行环境,它最核心的内容就是JVM(Java虚拟机)及核心类库

数据结构篇 1. Collection和Collections的区别
  • Collection是一个集合接口(集合类的一个顶级接口),它提供了对集合对象进行基本 *** 作的通用接口方法。
    Collections是一个包装类,它包含有各种有关集合 *** 作的静态多态方法。
  • Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一 *** 作方式其直接继承接口有List与Set。
    Collections,此类不能实例化,就像一个工具类,用于对集合中元素进行排序、搜索以及线程安全等各种 *** 作,服务于Java的Collection框架。
2. List,Set,Map的区别
比较内容ListSetMap
继承CollectionCollection
常见实现类ArrayList,linkedList,Vector等HashSet,TreeSet等HashMap,HashTable,TreeMap,LinkedHashMap
元素可重复不可重复不可重复
顺序有序无序HashMap无序,LinkedHashMap有序(按照插入顺序),TreeMap按照key排序
线程安全Vector线程安全Hashtable线程安全
3. Array和ArrayList的区别
  • 存储的内容:Array 数组可以包含基本类型和对象类型,ArrayList却只能包含对象类型。Array 数组在存放的时候一定是同种类型的元素。ArrayList 就不一定了 。
  • 空间大小:Array 数组的空间大小是固定的,所以需要事前确定合适的空间大小。ArrayList的空间是动态增长的,而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。
  • 方法:ArrayList方法上比 Array更多样化,比如添加全部 addAll()、删除全部 removeAll()、返回迭代器 iterator() 等。
4. ArrayList和LinkedList区别
  • ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  • 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  • 对于新增和删除 *** 作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
    ArrayList内部是使用可増长数组实现的,所以是用get和set方法是花费常数时间的,但是如果插入元素和删除元素,除非插入和删除的位置都在表末尾,否则代码开销会很大,因为里面需要数组的移动。增加或删除后需要改变后面所有数组的位置。LinkedList是使用双链表实现的,所以get会非常消耗资源,除非位置离头部很近。但是插入和删除元素花费常数时间。增加或删除改变指针指向。

适用场景

如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组里, 但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等 *** 作,只是方便我们进行查找的话,那么,我们就选择 ArrayList。
如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用 ArrayList 就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择 LinkedList。

5. HashMap和Hashtable的区别?底层实现?
  • HashTable中的方法大多数都加了一个synchronized,所以是线程安全的,不允许key和value的值为null,效率比较低一点。
  • HashMap是线程不安全的,但是允许key和value值为null,而HashTable不允许。
  • HashMap是没有contains方法的,而包括containsValue和containsKey方法;hashtable则保留了contains方法,效果同containsValue,还包括containsValue和containsKey方法。
6. 一些线程安全的集合类
  • Vector,Hashtable,ConcurrentHashMap,Stack,enumeration(枚举,相当于迭代器)
7. ConcurrentHashMap

JDK 1.7:

  1. 数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构
  2. 元素查询:二次hash,第一次定位到Segment,第二次Hash定位到元素所在的链表的头部
  3. 锁:Segment分段锁,Segment继承了ReentrantLack,锁定 *** 作的Segment,其他的Segment不受影响,并发度为Segment的个数,可以通过构造函数指定,数组扩容不会影响其他的Segment
  4. get方法不需要加锁,volatile保证可见性和有序性

JDK 1.8:

  1. 数据结构:synchronized+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性,查找,替换,赋值 *** 作都是使用CAS

  2. 锁:锁链表的head节点,不影响其他元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写 *** 作,并发扩容

  3. 读 *** 作无锁:
    Node的val和next都是用volatile修饰,读写线程对该变量互相可见

    数组用volatile修饰,保证扩容时被读线程感知

8. HashMap扩容流程

扩容机制

​ 默认情况下hashmap的容量为16,如果是用户通过构造方法指定了一个数字作为容量,那么hash会选择 大于该数字的第一个2的幂作为容量(3->4,9->16),通过指定初始化容量可以有效提高性能

​ 没有初始化设置的时候,就要进行自动扩容,扩大到原来的2倍

​ 扩容条件:当HashMap中的元素个数size超过临界值时,就会自动进行扩容。

​ 临界值=负载因子(0.75)*当前容量(默认是16),举个例子,当元素个数>12时,就要进行自动扩容

​ 所以如果我们没有设置初始容量的大小,随着元素的不断增加,HashMap会进行多次扩容,都需要去建 hash表,是非常影响性能能

9. 常见的排序算法

稳定性算法:直接插入排序,冒泡排序,归并排序,计数排序。
不稳定算法:希尔排序,直接选择排序,堆排序,快速排序。

直接插入排序:扑克牌的例子,时间复杂度N^2,空间复杂度O(1).
应用场景:数据接近有序或者数据量比较小的情况。稳定

希尔排序:直接插入排序的优化,利用分组的方式将数据量降下来,然后对每一组数据进行插入排序。
时间复杂度:不确定,因为gap的值有很多种取法,导致很难去计算
空间复杂度:O(1) 不稳定

直接选择排序:找到最小的元素与第一个元素进行交换,或者找到最大的元素与最后一个元素进行交换(不稳定) 优化:双指针
时间复杂度:O(n^2)
空间复杂度:O(1)

堆排序:升序建大堆,降序建小堆(不稳定)
时间复杂度:O(N*logN)
空间复杂度:O(1)

冒泡排序:比较两个相邻的元素,根据结果将两个元素位置互换(稳定的)
时间复杂度:O(N^2)
空间复杂度:O(1)

快速排序:找一个基准值然后划分区间,左边比基准值小,右边比基准值大
递归进左侧去分割,递归去右侧进行分割,等剩一个元素再回退(不稳定)
时间复杂度:O(N*logN)
空间复杂度:O(logN)

归并排序:先使每个自序列有序,再使子序列段之间有序,递归(稳定的)
时间复杂度:O(N*logN)
空间复杂度:O(N)

计数排序:先统计每个元素出现的次数,然后根据计数的结果对数据进行回收
稳定的
时间复杂度:O(m+k)
空间复杂度:O(k)

想要更加的了解排序算法,可以看我的另外一篇博客
传送门:排序算法

MySQL篇 1.索引
  • 索引是一种特殊的文件,它们包含着对数据表里所有记录的引用指针。更通俗一点讲,索引就是一个目录,用来定位我们所需要的内容所处的位置。
  • 索引是一种数据结构,底层实现通常采用的是B树及其变种的B+树。数据库索引是数据库管理系统中一个排序的数据结构,以协助快速查询,更新数据库表中数据。
  • 索引是帮助mysql高效获取数据的排好序的数据结构
  • 优点:大大加快数据的检索速度。在查询过程中使用优化隐藏器,提高系统的性能。
  • 缺点:创建和维护比较浪费时间(时间方面),需要占据物理空间(空间方面)
a.索引的基本原理

用来快速寻找那些具有特定值的记录

把无序的数据变成了有序的查询

  • 把创建出来的索引的内容进行排序
  • 对排序结果生成倒排表
  • 在倒排表的内容上加上数据地址链
  • 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据
b.索引的四种类型
-类型-特点
主键索引列不能重复,不能为空,每个表只能有一个主键
唯一索引索引列的所有值都只能出现一次,可以为空
普通索引值可以为空,没有唯一性的约束,即数可以重复
全文索引用来对大表的文本域进行索引
c.索引的数据结构

B+树,Hash

大多数需求为单条记录查询时,使用hash索引查询性能最快;其余大部分场景建议使用B+树索引

为什么用B+树呢?

  • 相对于二叉树,层级更少,搜索效率更高
  • 对于B-树,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保持大量数据,只能增加树的高度,导致性能降低
  • 相对于Hash索引,B+树支持范围匹配及排序 *** 作

用B+树而不用B树的理由:

  • B树只适合随机检索,而B+树同时支持随机检索和顺序检索
  • B+树空间利用率更高,可减少I/O *** 作次数,磁盘读写代价更低
  • B+树的查询效率更加的稳定一些
  • B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。B+树的叶子节点使用指针顺序来接在一起,只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的范围查询 *** 作
  • 增加文件(节点)时,效率更高。因为B+树的叶子节点包含所有的关键字,并以有序的链表结构存储,可以提高增删效率。

Hash的特点:

  • 只能用于对等比较,不能进行范围查询
  • 无法利用索引完成排序 *** 作,是无序的
  • 查询效率高,在没有hash冲突的情况下,只需要一次查询

B+树的特点:

  • 非叶子节点存储的是指针,不会保存数据
  • 所有的关键字都出现在叶子节点的链表中,并且是有序的
d.索引的实现原则
  1. 针对数据量较大,且查询比较频繁的表建立索引

  2. 针对常作为查询条件(where),排序(group by) *** 作的字段建立索引

  3. 尽量选择区分度高的列作为索引,区分度越高,使用索引的效率越高

  4. 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引

  5. 尽量使用联合索引,减少单列索引

  6. 控制索引的数量,并不是越多的越好,多的话代价就会越大,会影响增删改的效率

2. 事务

一个最小的不可再分的工作单元,通常一个事务对应一个完整的业务。

一个完整的事务需要批量的DML语句共同联合完成

事务只和DML语句有关,只有DML语句才有事务。

a.事务的四大特性(ACID)
-特性-特点
原子性(Atomicity)事务是不可分割的最小 *** 作单元,要么全部成功,要么全部失败
一致性(Consistency)事务完成时,必须所有的数据都保持一致状态
隔离性(Isolation)数据库提供的隔离机制,保证事务在不受外部并发 *** 作影响的独立环境下运行
持久性(Durability)事务一旦提交或回滚,他对数据库中的改变就是永久的
b.并发事务问题
-问题-描述
脏读一个事务读到另外一个事务还没有提交的数据
不可重复读一个事务先后读取同一条数据,但是两次读取的结果不同
幻读一个事务按照条件查询数据时,没有对应的数据行,
但是插入数据时,又发现这行数据又存在了
c.事务隔离级别?Mysql的默认隔离级别是什么?

为了达到事务的四大特性,数据库定义了四种不同的事务隔离级别;
由低到高依次为:

  • Read-uncommitted(读取未提交):允许读取尚未提交的数据变更,可能会导致脏读,幻读或者不可重复读
  • Read-committed(读取已提交):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读和不可重复读可能还会发生
  • Repeatable-Read(可重复读):对同一字段的多次读取结果都是一致的,除非数据是本身事务自己所修改,可以阻止脏读和不可重复读,但是幻读可能还会又可能发生
  • Serializable(可串行化):最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样的事务之间就完全不可能产生干扰,该级别可以防止脏读,不可重复读,幻读

Mysql的默认隔离级别是Repeatable-Read(可重复读)

Oracle的默认隔离级别是Read-committed(读取已提交)

d.ACID靠什么保证的?
  • 原子性(A):由undo log日志保证的,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql语句
  • 一致性 (C):由其他三大特性进行保证,程序代码要保证业务上的一致性
  • 隔离性(I):由MVCC来保证
  • 持久性(D):由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次 *** 作,宕机(死机)的时候可以从redo log中恢复
3.锁 a.对MySQL的锁了解吗?

当数据库有并发事务的时候,可能产生的数据不一致,这时候需要一些机制来保证访问的次序,锁就是这样的机制
就像酒店的房子一样,如果可以随意进出,就可能出现好几个人抢夺一个房子的情况。而给房间加个锁,申请到房间的人才可以入住并且进去后将房子锁起来,等使用完后再供给别人使用。

b.隔离级别与锁的关系
  • 在Read-uncommitted级别下,读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突
  • 在Read-committed级别下,读 *** 作需要加共享锁,但是在语句执行完以后释放共享锁
  • 在Repeatable-Read级别下,读 *** 作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才能释放共享锁
  • Serializable是限制性最高的隔离级别,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成
c.什么是死锁?怎么解决?

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶行循环的现象

常见的解决死锁的方法:

a.如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低产生死锁的概率
b.在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率
c.对于非常容易产生死锁的业务部分,可以尝试使用锁定颗粒度,通过表级锁定来减少死锁产生的概率

d.MySQL的锁有哪些,如何理解?

按锁粒度分类:

行锁:锁某行数据,锁粒度最小,并发度最高
表锁:锁整张表,锁粒度最大,并发度低
间隙锁:锁的是一个区间

属性分类:

共享锁:也就是读锁,一个事务给某行数据加了读锁,其他事务可以读,但是不能写
排它锁:也就是写锁,一个事务给某行数据加了读锁,其他事务不能读,也不能写

其他:

乐观锁
悲观锁

e.数据库的乐观锁和悲观锁是什么?怎么实现的?

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的 *** 作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库的锁机制

乐观锁:假定不会发生并发冲突,只在提交 *** 作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:使用版本号机制或CAS算法实现

4.MySQl慢查询该如何优化?
  • 检查是否走了索引,如果没有,则优化sql利用索引
  • 检查所利用的索引是否是最优索引
  • 检查所查字段是否都是必须的,是否查询了过多的字段,查出了多余数据
  • 检查表中的数据是否过多,是否应该进行分库分表
  • 检查数据库实例所在机器的性能设置是否太低,是否可以增加适当资源
5.sql的优化做过吗?有哪些策略?
  • 创建表的时候。应尽量建立主键,根据主键查询数据;
  • 大数据表删除,用truncate table代替delete。
  • 合理使用索引,组合索引的列顺序尽量与查询条件列顺序保持一致;对于数据 *** 作频繁的表,索引需要定期重建,以减少失效的索引和碎片。查询尽量用确定的列名,少用*号。
  • 尽量少嵌套子查询,这种查询会消耗大量的CPU资源;对于有比较多or运算的查询,建议分成多个查询,用union all联结起来;多表查询的查询语句中,选择最有效率的表名顺序(基于规则的优化器中有效)
6.MyISAM与InnoDB的区别

MyISAM:

  • 不支持事务,但是每次查询都是原子的
  • 支持表级锁,即每次 *** 作都是对整个表加锁;
  • 存储表的总行数
  • 采用非聚簇索引,索引文件的数据域存储指向数据文件的指针,索引和数据是分离开的。辅索引与主索引基本一致,但是辅索引不用保证唯一性

InnoDB:

  • 支持ACID事务,支持事务的四种隔离级别
  • 支持行级锁和外键约束:因此可以支持写并发
  • 不存储总行数
  • 采用的是聚簇索引,数据文件和索引是绑定在一起的,必须要有主键
7.为什么建议InnoDB表必须建主键?并且推荐使用整形的自增主键?

推荐使用整形主键:整形的比较效率高,查找起来比较快一点,占用的空间比较小一点,节约空间。
自增ID可以保证每次插入时B+索引是从右边扩展的,可以避免B+树和频繁合并和分裂(对比使用UUID)。如果使用字符串主键和随机主键,会使得数据随机插入,效率比较差。

8.为什么非主键索引结构叶子节点存储的是主键值?

减少了出现行移动或者数据页分裂时二级索引的维护工作(当数据需要更新的时候,二级索引不需要修改,只需要修改聚簇索引,一个表只能有一个聚簇索引,其他的都是二级索引,这样只需要修改聚簇索引就可以了,不需要重新构建二级索引)

9.最左前缀原则

当一个SQL想要使用索引时,就一定要提供该索引所对应的字段中的最左边的字段,也就是排在最前面的字段,比如a,b,c三个字段建立了一个联合索引,那么在写一个sql时就一定要提供a字段的条件,这样才能用到联合索引。这是由于在建立a,b,c三个字段的联合索引时,底层的B+树是按照a,b,c三个字段从左到右去比较大小进行排序的。

10. 为什么不建议用 select*from… 来查找数据?

浪费资源,降低效率
select* 用来获取表中所有的数据字段,但是我们实际上是不需要查询这么多数据的,这样做会浪费系统资源,降低查询效率

可读性
我们需要的是某几个数据字段,但是select*会查找出所有的数据,导致可读性降低

程序变更问题
加入我们有两个功能A,B同时使用这张表table,但是某一天要修改一下B的功能,但是要修改就必须要给table加某些字段,此时对A来说又多了几项不需要的字段,其实并没有什么用,白白浪费资源和时间。

四:线程篇 1.并发,并行,串行区别
  • 并发指两个或多个事件在同一时间间隔内发生,即交替做不同事的能力,多线程是并发的一种形式。
  • 并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时进行,即同时做不同事的能力
  • 串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务只能干等着。
2.并发的三大特性
  • 原子性:在一次或者多次 *** 作时,要么所有 *** 作都被执行,要么所有 *** 作都不执行
  • 有序性:程序执行的顺序按照代码的先后顺序执行
  • 可见性:一个线程对共享变量值的修改,能够及时的被其他线程看到
3.线程的生命周期,以及线程有哪些状态?

线程中通常有五种状态,创建,就绪,运行,阻塞,死亡

创建状态: 新创建了一个线程对象

就绪状态: 线程对象创建以后,其他线程执行该对象的start方法时,该线程就会处于一个就绪的状态,变得可运行,等待着cpu的使用权

运行状态: 处于就绪态的线程获得了cpu的使用权,处于运行态

阻塞态: 线程因为某种原因放弃了cpu的使用权,暂时停止运行。直到线程进入到就绪态才能够再次运行

阻塞又分为3种:

  • 等待阻塞: 运行的线程执行wait方法,该线程会释放占用的所有资源,JVM把线程放入到等待池里。进入这个状态后,线程是不能够自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒。
  • 同步阻塞: 运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入到锁池中。
  • 其他阻塞: 线程执行sleep方法或join方法,或者发出了IO请求,JVM将该线程置为阻塞状态。

死亡态: 线程执行完或者遇到了某种异常推出了run方法,该线程结束生命周期。

4.sleep,wait,yield,join
  • sleep:Thread的静态方法,将cpu的执行资格和执行权让出去,不再运行此线程,当定时时间过了后,参与cpu的调度,获取到cpu的使用权后就可以继续运行了。sleep不依赖同步器synchronized。

  • wait:Object类的方法,当线程调用wait方法后,会将线程放入到等待池中,不会自动唤醒,得依靠notify,notifyAll方法。依赖同步器synchronized。

  • yield:执行后线程会进入就绪状态,并且,马上释放cpu的使用权,但是保留了cpu的执行资格。

  • join:执行后线程进入阻塞状态,在B中调用A的join,B线程会进入阻塞态,一直到A结束或者中断线程

5.对线程安全的理解

当多个线程访问同一个对象时,如果不用额外的同步控制或其他的协调 *** 作,调用这个对象的行为都可以获得正确的结果,我们就说这个线程是安全的。
(多个线程访问同一个对象跟单个线程执行的结果一样就是安全的)
主要的线程安全问题都是在堆中发生的,因为堆是共享内存,所有的线程都可以访问到该区域

6.说说你对守护线程的理解

守护线程:为所有非守护线程(用户线程)提供服务的线程;任何一个守护线程都是整个jvm中所有非守护线程的保姆
守护线程类似于整个进程的一个默默无闻的小喽啰;它的生死无关重要,却依赖整个进程而运行;哪天其他线程结束了,没有要执行的了,程序就结束了,理都不理守护线程,直接把它中断。
注意: 由于非守护线程的终止是自身无法控制的。

守护线程的应用场景:

  • 来为其他服务提供服务支持的情况
  • 或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程使用
7.为什么使用线程池?解释一下线程池的参数?

使用原因:

  1. 降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗
  2. 提高响应速度;任务来了,直接由线程可用可执行,而不是先创建线程再执行
  3. 提高线程的课管理性;线程是稀缺资源,使用线程池可以统一的分配调优监控

参数解释:

  • corePoolSize :代表线程核心数,也是正常情况下创建工作的线程数,这些线程创建后而不会消除

  • maxinumPoolSize:代表的是最大线程数,与核心线程数相对应,表示最大允许被创建的线程数

  • keepAliveTime:空闲时间,超出核心线程之外的线程空闲存活时间,也就是核心线程不会消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除

  • unit:上面这个空闲时间的单位

  • workQueue:任务队列,存放待执行的任务,假设核心线程已经全部被使用了,还有任务进来就放放入队列,等到队列放满时还有任务进来,此时就会开始创建新的线程

  • ThreadFactory:实际上是一个线程工厂,用来生产线程执行任务

  • Handler:任务拒绝策略,有两种情况,

    1.当我们调用shutdown等方法关闭线程池后,这时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程池提交任务就会遭到拒绝。

    2.当达到最大线程数,线程池已经没有能力继续处理新提交的任务时,这时也会拒绝。

8.线程池四种创建方式

Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:

  • newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool :创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor :创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
9.如何理解volatile关键字?

volatile关键字用来修饰对象的属性,在并发环境下可以保证这个属性的可见性,对于加了volatile关键字的属性,在对这个属性进行修改时,会直接将cpu高级缓存中的数据协会到主内存中,对这个变量的读取也会直接从主内存中读取,从而保证了可见性。
底层是通过 *** 作系统的内存屏障来实现的,由于使用了内存屏障,所以会禁止指令重排,也就保证了有序性。

10.synchronized和lock都能进行加锁,那么区别是什么呢?
  1. 从语法上看:synchronized是自动的加锁释放锁,而lock是我们进行显式(手动)来加 锁和释放锁(执行完,不管是否出现异常,都需要释放锁)。Lock相对就更加的灵活。

  2. lock提供了更多的获取锁的方式:

    lock():和synchronized申请锁类似,申请失败就干等(无条件等)

    lockInterruptibly():可被中断的申请锁(申请失败等待时,可以被其它线程中断)

    tryLock():尝试获取锁(不阻塞),如果申请成功就加锁返回true,反之马上就返回false。

    tryLock(long timeout,TimeUnit unit):尝试获取锁,如果申请失败,是超时等待(等待一段时间),这段是时间过去后还没获取到锁,就返回false。

  3. 从效率上看,当线程冲突比较严重的时候,lock的性能要高很多。

    synchronized在申请锁成功的线程释放锁以后,所有之前因为申请锁失败而阻塞的线程,都会再次竞争。

    lock是基于aqs来实现:aqs是一个双端队列,专门用来进行线程状态的管理;相当于:竞争锁失败的线程就放到队列中(入队),并设置状态(未获取到锁),释放锁以后,把队列中的线程引用拿出来,设置状态(获取到锁)

    aqs提供了很多种方法,用来方便的实现独占锁/共享锁,公平锁/非公平锁lock就是基于aqs独占锁的方式来实现,提供了公平和非公平锁的设置。公平就相当于队列的先进先出,非公平锁就是我们的随机出队。

11.什么是线程死锁?

死锁是指两个或者两个以上的任务在执行过程中,由于资源竞争或由于彼此通信造成一种堵塞的现象,若无外力的作用下,都无法推进,此时的系统处于死锁状态

12.形成死锁的四个状态?
  1. 互斥条件:一个资源只能被一个进程或线程占用,直到该资源被释放,才能被别的进程或线程使用
  2. 请求与保持条件:一个进程或线程因请求被占有资源而发生堵塞时,对已获取的资源保持不放
  3. 不剥夺条件:线程已经获取的资源在未使用完之前不能被其他线程强行剥夺,只有等自己使用完才释放资源
  4. 循环等待条件:当发生死锁时,所等待的线程必定形成一个环路,死循环造成永久堵塞
13.如何避免死锁?

只需要破坏上面11中的四个条件(除了互斥条件)中的一个就行

  1. 破坏互斥条件:无法破坏,我们的锁本身就是多个线程来产生互斥
  2. 破坏请求与剥夺条件:一次性申请所有的资源
  3. 破坏不剥夺条件:占有部分资源的线程尝试申请其他资源,如果申请不到,可以主动释放它占有的资源
  4. 破坏循环等待条件:按照顺序来申请资源
14.线程与进程的区别?

根本区别:进程是 *** 作系统资源分配的基本单位,而线程是处理器任务调度的基本单位

资源开销:线程可以看是一种轻量级进程,同一类线程共享代码和数据空间,每个线程都有自己的独立运行栈和程序计数器,所以说线程之间切换的开销较小。

包含关系:如果一个进程内有多个线程,那么执行过程是由这些线程共同完成的;线程是进程的一部分,所以线程也被称为轻量级进程

内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃后,会对其他线程产生影响,从而导致进程崩溃掉

15.什么是单例模式?单例模式中饿汉模式与懒汉模式的区别

单例模式的概念:

内存中只会创建并且只创建一次对象的设计模式。
程序中多次使用同一个对象时,为了防止频繁的创建对象使得内存飙升,我们只创建一次这个对象,从而让所有需要调用的地方都共享这个对象。

单例模式的特点:

  • 只能有一个实例
  • 单例类必须自己创建自己的唯一实例
  • 单例类必须给所有其他对象提供这一实例

饿汉模式:

在类加载的同时,就创建线程实例,后面直接使用即可

懒汉模式:

类加载的时候不会创建,第一次使用的时候才会创建实例

饿汉模式是线程安全的
而懒汉模式是线程不安全的,解决方法: 把创建实例的代码放在一个方法中,然后给这个方法加上一个synchronized锁,以保证线程安全。
懒汉模式的优化: 在加锁的基础上,使用双重if判定,降低锁的竞争频率;给instance加上volatile关键字,保证其可见性

五:网络 1. TCP与UDP的区别和使用场景

区别:

  • TCP是面对连接的可靠的服务,UDP是无连接的不可靠的服务
  • TCP是面向字节流的,UDP是面对数据报文的
  • TCP只支持点对点通信,UDP支持一对一,一对多,多对多
  • TCP有拥塞控制机制,UDP没有

使用场景:

对某些实时性比较高的情况,选择UDP,比如游戏,媒体通信,直播等等,即使出现了传输错误也是可以容忍的;其他大部分情况下,HTTP都是使用的TCP,因为要求传输的内容要可靠,不允许出现丢失

2. TCP是如何保证可靠性传输的
  • 校验和
    在传输过程中,将发送的数据段都当作是一个16位的整数。将这些整数加起来,并且保证前面的进位不能丢弃,补在最后面,然后取反,得到校验和。
  • 确认到达和序列号
    TCP给每个字节的数据都进行了编号(序列号),传输过程中,每次接收方收到数据以后,都会对传输方进行确认应答,回复一个确认序列号,告诉发送方,接收到了那些数据,下次的数据从哪发。
  • 超时重传
    发送方发送完数据后,等待一段时间,在这个时间内,如果发送方没有接收到响应,就会对刚才的数据进行重新的发送
  • 连接管理
    三次握手四次挥手(在第8个知识点了有专门的讲解)
  • 流量控制
    接收端在接收到数据后,对其进行处理。如果发送端的发送速度太快,导致接收端的接受缓冲区很快的填充满了。此时如果发送端仍旧发送数据,那么接下来发送的数据都会丢包,继而导致丢包的一系列连锁反应,超时重传呀什么的。而TCP根据接收端对数据的处理能力,决定发送端的发送速度,这个机制就是流量控制。
  • 拥塞控制
    TCP传输的过程中,发送端开始发送数据的时候,如果刚开始就发送大量的数据,那么就可能造成一些问题。所以TCP引入了慢启动的机制,在开始发送数据时,先发送少量的数据探路。探清当前的网络状态如何,再决定多大的速度进行传输。这时候就引入一个叫做拥塞窗口的概念。
3.HTTP和HTTPS的区别
  • HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。
  • HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者默认是80,后者默认是443。
  • HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)
4.输入一个URL的全过程
  • 1.首先,你要在浏览器中输入网址
  • 2.浏览器查找域名的IP地址浏览器会把输入的域名解析成对应的IP,DNS过程如下:
    查找浏览器缓存,查找系统缓存,查找路由器缓存,递归查询,迭代查询
  • 3.浏览器与目标服务器建立TCP连接(三次握手)
  • 4.浏览器给web服务器发送一个HTTP请求浏览器向主机发起一个HTTP-GET方法的请求。
    请求的方法包含访问的URL,也就是http://www.csdn.com/ ,KeepAlive,长连接,还有User-Agent用户浏览器 *** 作系统信息,编码等。值得一提的是Accep-Econding的Cookies项。Accept-Ecnoding一般采用gzip,压缩之后传输html文件。Cookies如果是首次访问,会提示服务器建立用户缓存信息,如果不是,可以利用Cookies对应键值,找到相应缓存,缓存里面存放着用户名,密码和一些用户设置项
  • 5.某些服务器会做永久重定向响应
    对于大型网站存在多个主机站点,负载均衡或者导入流量,提高SEO排名。往往不会直接返回请求页面,而是重定向。返回的状态码不是200ok,而是301,302以3开头的重定向码,浏览器在获取了重定向响应后,在响应报文中Location项找到重定向地址,浏览器重新第一步访问即可。
    重定向的作用:重定向是为了负载均衡或者导入流量,提高SEO排名。利用一个前端服务器接受请求,然后负载到不同的主机上,可以大大提高站点的业务并发处理能力,重定向也可将多个域名的访问,集中到一个站点;由于baidu.com,www.baidu.com会被搜索引擎认为是两个网站,造成每个的连接数都活减少从而降低排名,永久重定向会将两个地址关联起来,搜索引擎会认为是同一个网站,从而提高排名
  • 6.浏览器跟踪重定向地址
    当浏览器知道了重定向后最终的访问地址之后,重新发送一个http请求,发送内容同上。
  • 7.服务器处理请求
    服务器接收到获取请求,然后处理并返回一个响应
  • 8.服务器发出一个HTML响应
    返回状态码200 ok,表示服务器可以响应请求,返回报文,由于在报头中Content-type为“text/html”,浏览器以HTML形式呈现,而不是下载文件。
  • 9.释放TCP连接(四次挥手)
  • 10.浏览器显示页面
    在浏览器没有完整接收全部HTML文档时,它就已经开始显示这个页面了,浏览器收到返回的数据包,根据浏览器的渲染机制对响应的数据进行渲染。渲染后的数据,进行相应的页面呈现和脚步的交互
  • 11.浏览器发送获取嵌入在HTML中的其他内容
    比如一些样式文件,图片url,js文件url等,浏览器会通过这些url重新发送请求,请求过程依然是HTML读取类似的过程,查询域名,发送请求,重定向等。不过这些静态文件是可以缓存到浏览器中的,有时访问这些文件不需要通过服务器,直接从缓存中取。某些网站也会使用第三方CDN进行托管这些静态文件。
5.ARP协议

ARP协议是地址解析协议,是通过解析IP地址得到MAC地址的,mac地址是唯一的。是一个在网络协议包中极其重要的网络传输协议,它与网卡有着极其密切的关系,在TCP/IP分层结构中,把ARP划分到网络层

在以太网环境中,数据的传输所依懒的是MAC地址而非IP地址,而将已知IP地址转换为MAC地址的工作是由ARP协议来完成的。
以目标IP地址为线索,用来定位下一个应该接收数据分包的网络设备所对应的MAC地址(即下一跳的MAC地址)。如果目标主机不再同一链路上时,可以通过ARP查找下一跳路由器的MAC地址。

arp缓存表: 保存了网络层IP地址和数据链路层MAC地址的动态映射的一张表

解析过程:

同一网段:

  • A先通过子网掩码计算是不是和C在同一个网段(在)
  • 此时A想要发送数据给C,A主机先去查找自己的arp缓存表,如果表有C主机的mac地址,就会直接发送数据,如果没有,A会先通过广播发送一个arp包,这个arp包中包含了A主机ip,A主机mac地址和C主机的IP地址,不包含C主机的Mac地址,因为这是我们要查找的。
  • 当C读取到这个arp包后,发现就是寻找它的,先把A的ip地址和mac地址写入到自己的arp缓存表中(有的话就覆盖掉),然后去把自己的MAC地址封装到这个包中,返回响应给A主机,然后A将C的Mac地址保存在自己的arp缓存表中,就可以发送数据了。

不同网段:

  • A先通过子网掩码计算是不是和B在同一个网段(不在)
  • A先去发送一个ARP包来获取网关的MAC地址,获取到以后开始封装报文
  • 目标地址写上网关的mac地址,网关收到报文后,开始解封装,发现是给另外一个网段的主机发的
  • 如果这个网关是路由器,就直接把数据报文发送给下一跳路由器
  • 如果网关是普通的PC,就发送给路由器,由路由器发送给下一跳路由器,目标MAC写下一跳路由器的MAC地址,直到找到这个B主机。
6.UDP协议

用户数据包协议,提高不可靠无连接的服务,面向数据报的,整个过程就像是一个寄信的过程,每次接收和发送数据均是整条进行发送。

  • 无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接.
  • 不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息.
  • 面向数据报: 不能够灵活的控制读写数据的次数和数量.
7.状态码

200:表示页面访问成功

204:表示客户端发送给客户端的请求得到了成功处理,但在返回的响应报文中不含实体的主体部分(没有资源可以返回);

302:临时重定向,登录页面经常见到,实现登陆后自动跳转到主页

301:永久重定向,浏览器收到这种请求,后续的请求都会被改为新地址。

404:没有找到资源,访问对方服务器的资源,如果资源不存在,就会出现404

403:访问被拒绝,没有权限去访问,如果用户没有登录就访问,很容易出现

405:服务器不支持该方法

500:服务器内部出现错误,一般是代码执行过程中出现了一些特殊情况导致服务器崩溃

504:服务器负载比较大,消耗时间长,容易出现超时情况

8.三次握手,四次挥手 三次握手
  • 客户端发送一个SYN数据报到服务端,申请建立客户端到服务端的连接
  • 服务端返回一个SYN+ACK数据报给客户端,这里的ACK是第一次SYN的应答,
    确认第一次的连接,即申请建立服务端到客户端的连接。
  • 客户端接收到服务端返回的数据报,状态设置为established(建立客户端到服务端连接),
    发送一个ACK数据报回服务端(第二次的SYN的应答)
四次挥手
  • 客户端发送FIN数据报到服务端:申请关闭客户端到服务端的连接,状态设置为FIN_WAIT_1.
  • 服务端接收到FIN,状态设置为close_wait;返回一个ACK应答(这个动作是系统在实现tcp协议栈的时候默认执行的,不需要程序调用代码),客户端收到ACK后,进入FIN_WAIT_2状态。
  • 服务端发送一个FIN数据报到客户端:申请关闭服务端到客户端的连接(程序收到调用socket.close来发送的)
    程序可以执行一些关闭连接前的工作,如消耗资源,释放一些相应的对象等等,此时服务端的状态设置为LAST_ACK状态
  • 客户端返回ACK回服务端(来自服务端FIN的应答),且客户端状态设置为time_wait状态,需要等待一定时间(默认时间为2MSL)后关闭,这是因为这个返回的ACK可能会丢包,如果丢包,服务端要重传这个FIN报,此时我们客户端需要对这个FIN进行应答,并且重置这个time_wait等待时间。没有丢包的话就等待一段时间后进行关闭。
    服务端接收到ACK这个数据包后,马上关闭连接
9.GET和POST的区别
  • 语义不同:GET一般用于获取数据,POST一般用于提交数据
  • GET的body一般为空,需要传递的数据通过queryString传递,POST的queryString一般为空,需要传递的数据通过body传递
  • GET请求一般是幂等的,POST的请求一般是不幂等的。
    幂等:如果多次请求的结果一样,就视为请求是幂等的。
  • GET可以被缓存,POST不能被缓存
六:JVM篇 1. 垃圾回收机制

几乎所有的对象都是在堆中存放的,所以回收主要针对的就是堆区来进行的,而这些被称为垃圾的就是堆中已经死亡的对象,可以理解为“不可能再被任何途径使用的对象”,所以我们就要对这个对象进行判断,看它是否已经死亡。

用到的方法有引用计数法和可达性分析法(判断一个对象是否是垃圾)。

1.1 判断是否是垃圾对象的算法 1.1.1引用计数法:

假设堆中每个对象都有一个引用计数器,当一个对象被创建并且初始化赋值后,该对象的计数器就设置为1,每当有一个地方引用它的时候,计数器++,反之当引用失效时,例如一个对象的某个引用超过了生命周期(出了作用域)或者被设置为一个新值时,该对象的计数器就–,当某个对象的计数器计数为0时,就可以回收了。
当一个对象被当作垃圾回收时,它引用的任何对象的计数器都要-1。
优点:实现简单,对程序不被长时间打断的实时环境比较有利
缺点:需要额外的空间来保存计数器,难以检测出对象之间的循环引用。

1.1.2 可达性分析法:

将一系列的根对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,如果一个对象到根对象没有任何的引用链相连,那这个对象就不是可达的,也称之为不可达对象,可以解决循环引用的问题,不需要占用额外的空间。

1.2 垃圾回收算法 1.2.1 标记清除法

标记处需要回收的对象,再标记完成后统一回收掉被标记的对象,
优点:不需要进行对象的移动,并且仅对不存活的对象进行处理。
缺点:效率不高,标记清除后可能会产生大量的不连续的内存碎片。

1.2.2 标记整理算法

标记处需要回收的对象,再标记完成后统一回收掉被标记的对象,然后把有用的对象全部压缩到前面去
优点:比较简单,空闲区域的位置是可知的,也不会有碎片的问题了
缺点:GC暂停的时间会增长。因为你将所有的对象都移动到了一个新的地方,还得为他们更新引用地址。

1.2.3 复制算法

将内存按照容量分为大小相等的两块,每次只使用一块,当这一块的内存用户完之后,将活着的对象复制到另外一块,再把已经使用过的部分全部清理一遍。
优点: 标记阶段和复制阶段同时进行,每次只对一块内存进行回收,运行高效,实现简单,不会考虑内存碎片的出现
缺点:需要一块能容纳下所有存活对象的额外的内存空间

1.2.4 分代收集算法

将内存划分为新生代,老年代,永久代。新生代又被分为Eden和Survivor区,其中survivor由from区和to区组成,不同的对象生命周期是不一样的,因此,可以将不同的生命周期的对象进行分代,不同的代采取不同的回收算法进行垃圾回收,以便提高回收效率。

分代回收算法详细解释

所有新生成的对象都是放在新生代里面的,当Eden中的内存放满时,会触发一次Minor GC,将存活的对象复制到survivor0中,然后清空Eden区;当Eden区和survivor0区都存满的时候,将存活的对象都放到survivor1区中,然后清空Eden区和survivor0区,此时的话survivor0区就变成了空的,然后将0区和1区进行交换,保证1区为空。当1区不足以存放0区和E区的对象时,就将对象直接移动到老年代当中。当对象再survivor区中交换一次时,年龄就会+1,等到对象的年龄达到15时还没有被处理掉,就直接移动到老年代。如果老年代都满了的话,就会触发Full GC,将新生代,老年代中的垃圾对象都进行回收。

2.类加载机制

Java编译器将.java文件编译成为.class的文件,.class文件中保存着java转换后,虚拟机将要执行的指令,当需要某个类的时候,Java虚拟机就会加载这个.class文件到虚拟机的内存中,并且创建对应的class对象,这个过程被称为类加载。

加载,连接(验证,准备,解析),初始化使用,卸载

类加载只包括加载,连接,初始化阶段,加载只是类加载的第一个环节.

加载: 是一个读取class文件,将其转化为某种静态数据结构存储在方法区内,并在堆中生成一个便于用户调用的Class类型的对象的过程.

验证: 保证代码不会对虚拟机造成伤害。

准备: 为类变量分配内存并且设置该变量的初始值。

解析: 将符号引用替换成直接引用。

初始化: 类加载的最后阶段,如果该类有父类就对父类进行初始化,执行其静态代码块和静态初始化成员变量。

3.JVM内存管理

根据jvm的规范,JVM将内存分为了5个区域:

3.1 方法区

存储被虚拟机加载的类信息,静态变量,final定义的常量,即时编译器编译后的代码等数据。

3.2 程序计数器

是一块较小的内存空间,当前线程所执行的字节码的行号指示器,JVM通过改变程序计数器的值来选取下一条执行的字节码的指令。每一个线程都有一个独立的程序计数器。比如,线程1执行到某一行时,有优先级更高的线程2抢占式运行,当线程2运行完后,需要执行线程1,但是我们不可能从头开始重新执行,所以这块就用到了程序计数器,它标记了线程1运行的行数,所以直接从当前位置开始执行就行。

3.3 虚拟机栈(线程栈)

有线程执行的时候,就会从这个区拿出一部分空间,而且每个线程都对应着一个虚拟机栈,当这个线程内的方法执行时,都会创建一个栈帧,用于存储局部变量表, *** 作数栈,动态链接,方法出口等信息。当方法被调用时,栈帧入栈,方法执行结束时,线程出栈。

3.4 本地方法栈

与虚拟机栈的作用相似,他们之间的区别在于虚拟机栈为虚拟机执行Java方法(字节码文件),而本地方法栈用于执行native方法的执行,存储了每个native方法调 用的状态。

3.5 堆

是理解Java GC机制的重要区域。在JVM中,堆区是最大的一块,由线程所共享,在虚拟机启动时创建。用来存储对象示例及数组值,几乎所有通过new创建的对象都在此区域分配。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/736204.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-28
下一篇 2022-04-28

发表评论

登录后才能评论

评论列表(0条)

保存