Java后端面试八股文汇总
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
一、Java基础
1.Java语言具有那些特点
- Java为纯面向对象的语言。它能够直接反映现实生活中的对象
- 具有平台无关性。java利用Java虚拟机运行字节码无论是在Windows、linux还是MacOS等其他平台对Java程序进行编译编译后的程序可以在其他平台运行
- Java为解释性语言编码器把Java代码编译成平台无关的中间代码然后在JVM上解释运行具有很好的可移植性
- Java提供了很多内置类库。如对多线程支持对网络通信支持最重要的一点是提供了垃圾回收器
- Java具有较好的安全性和健壮性。Java提供了异常处理和垃圾回收机制去除了C++中难以理解的指针特性
- Java语言提供了对Web应用开发的支持
2.面向对象的三大特性
- 继承一个新类可以从现有的类中派生派生类可以从它的基类那里继承方法和实例变量且派生类可以修改或新增新的方法使之更适合特殊的需求。
- 封装将客观事务抽象成类每个类可以把自身的数据和方法只让可信的类或对象操作对不可信的进行信息隐藏
- 多态指在父类中定义的属性和方法被子类继承之后可以具有不同的数据类型或表现出不同的行为。且上转型后子类方法覆盖父类方法呈现多态。
3.字节序定义以及Java属于哪种字节序
字节序是指多字节数据在计算机内存中存储或网络传输时各字节的存储顺序。通常由小端和大端两种方式。
- 小端低位字节存放在内存的低地址端高位字节存放在内存的高地址端
- 大端高位字节存放在内存的低地址端低位字节存放在内存的高地址端
Java语言的字节序是大端
4.JDK与JRE有什么区别
JDKJava开发工具包Java Development Kit提供了Java的开发环境和运行环境
JREJava运行环境Java Runtime Environment提供了Java运行所需的环境
JDK包含了JRE。如果只运行Java程序安装JRE即可。要编写Java程序需安装JDK
5.简述Java访问修饰符
default默认访问修饰符在同一包内可见
private在同一类内可见不能修饰类
protected对同一包内的类和所有子类可见不能修饰类
public对所有类可见
6.构造方法、成员变量初始化以及静态成员变量三者的初始化顺序
先后顺序静态成员变量、成员变量、构造方法
详细的先后顺序父类静态变量、父类静态代码块、子类静态变量、子类静态代码块、父类非静态变量、父类非静态代码块、父类构造函数、子类非静态变量、子类非静态代码块、子类构造函数
7.接口和抽象类的相同点和区别
相同点
1.都不能被实例化
2.接口的实现类或抽象类的子类需实现接口或抽象类中相应的方法才能被实例化
不同点
1.接口只能有方法定义不能有方法的实现而抽象类可以有方法的定义与实现
2.实现接口的关键字为implements继承抽象类的关键字为extends。一个类可以实现多个接口但只能继承一个抽象类
3.当子类和父类之间存在逻辑上的层次结构推荐使用抽象类有利于功能的累积。当功能不需要希望支持差别较大的两个或更多对象间的特定交互行为推荐使用接口。使用接口能降低软件系统的耦合度便于日后维护或添加删除方法。
8.为什么Java语言不支持多重继承
- 为了程序的结构能够更加清晰从而便于维护。假设Java语言支持多重继承类C继承自类A和类B如果类A和B都有自定义的成员方法f(),那么当代码中调用类C的f()会产生二义性。Java语言通过实现多个接口间接支持多重继承接口由于只包含方法定义不能有方法的实现类C继承接口A与接口B时即使他们都有方法f()也不能直接调用方法需实现具体的f()方法才能调用不会产生二义性。
- 多重继承会使类型转换、构造方法的调用顺序变得复杂会影响到性能。
9.Java提供的多态机制
Java提供了两种用于多态的机制分别是重载与覆盖
1.重载重载是指同一个类中有多个同名的方法但这些方法有着不同的参数因此在编译时就可以确定到底调用哪个方法它是一种编译时多态。重载可以被看作一个类中的方法多态性。
2.覆盖子类可以覆盖父类的方法因此同样的方法会在父类与子类中有着不同的表现形式。在 Java 语言中基类的引用变量不仅可以指向基类的实例对象也可以指向其子类的实例对象。同样接口的引用变量也可以指向其实现类的实例对象而程序调用的方法在运行期才动态绑定(绑定指的是将一个方法调用和一个方法主体连接到一起)就是引用变量所指向的具体实例对象的方法也就是内存里正在运行的那个对象的方法而不是引用变量的类型中定义的方法。通过这种动态绑定的方法实现了多态。由于只有在运行时才能确定调用哪个方法因此通过方法覆盖实现的多态也可以被称为运行时多态。
10.重载与覆盖的区别
- 覆盖是父类与子类之间的关系是垂直关系重载是同一类中方法之间的关系是水平关系
- 覆盖只能由一个方法或一对方法产生关系重载是多个方法之间的关系
- 覆盖要求参数列表相同重载要求参数列表不同
- 覆盖中调用方法体是根据对象的类型来决定的而重载是根据调用时实参表与形参表来对应选择方法体
- 重载方法可以改变返回值的类型覆盖方法不能改变返回值的类型
11.final、finally和finalize的区别是什么
- final用于声明属性、方法和类分别表示属性不可变、方法不可覆盖、类不可继承。
- finally作为异常处理的一部分只能在try/catch语句中使用finally附带一个语句块用来表示这个语句最终一定被执行经常被用在需要释放资源的情况下。
- finalize是Object类的一个方法在垃圾收集器执行的时候会调用被回收对象的finalize()方法。当垃圾回收器准备好释放对象占用空间时首先会调用finalize()方法并在下一次垃圾回收动作发生时真正回收对象占用的内存。
12.出现在Java程序中的finally代码块是否一定会执行
当遇到下面情况不会执行。
1.当程序在进入try语句块之前就出现异常时会直接结束。
2.当程序在try块中强制退出时如使用System.exit(0)也不会执行finally块中的代码。
其它情况下在try/catch/finally语句执行的时候try块先执行当有异常发生catch和finally进行处理后程序就结束了当没有异常发生在执行完finally中的代码后后面代码会继续执行。值得注意的是当try/catch语句块中有return时finally语句块中的代码会在return之前执行。如果try/catch/finally块中都有return语句finally块中的return语句会覆盖try/catch模块中的return语句。
13.Java语言中关键字static的作用是什么
static的主要作用有两个
1.为某种特定数据类型或对象分配与创建对象个数无关的单一的存储空间。
2.使得某个方法或属性与类关联在一起而不是对象在不创建对象的情况下可通过类直接调用方法或使用类的属性。
具体而言static又可分为4种使用方式
1.修饰成员变量。用static关键字修饰的静态变量在内存中只有一个副本。只要静态变量所在的类被加载这个静态变量就会被分配空间可以使用’‘类.静态变量’‘和’‘对象.静态变量’'的方法使用。
2.修饰成员方法。static修饰的方法无需创建对象就可以被调用。static方法中不能使用this和super关键字不能调用非static方法只能访问所属类的静态成员变量和静态成员方法。
3.修饰代码块。JVM在加载类的时候会执行static代码块。static代码块常用于初始化静态变量。static代码块只会被执行一次。
4.修饰内部类。static内部类可以不依赖外部类实例对象而被实例化。静态内部类不能与外部类有相同的名字不能访问普通成员变量只能访问外部类中的静态成员和静态成员方法。
14.Java代码块执行顺序
- 父类静态代码块只执行一次
- 子类静态代码块只执行一次
- 父类构造代码块
- 父类构造函数
- 子类构造代码块
- 子类构造函数
- 普通代码块
15.Java中一维数组和二维数组的声明方式
一维数组的声明方式
1.type arrayName[]
2.type[] arrayName
二维数组的声明方式
1.type arrayName[][]
2.type[][] arrayName
3.type[] arrayName[]
其中type为基本数据类型或类arrayName为数组名字
16.String和StringBuffer有什么区别
String用于字符串操作属于不可变类。String对象一旦被创建其值将不能被改变。而StringBuffer是可变类当对象创建后仍然可以对其值进行修改。
17.判等运算符==与equals的区别
== 比较的是引用equals比较的是内容。
- 如果变量是基础数据类型== 用于比较其对应值是否相等。如果变量指向的是对象,== 用于比较两个对象是否指向同一块存储空间。
- equals是Object类提供的方法之一每个Java类都继承自Object类所以每个对象都具有equals这个方法。Object类中定义的equals方法内部是直接调用 == 比较对象的。但通过覆盖的方法可以让它不是比较引用而是比较数据内容。需保证equals方法相同对应的对象hashCode也相同。
18.为什么要把String设计为不变量
- 节省空间字符串常量存储在JVM的字符串池中可以被用户共享。
- 提高效率:String会被不同线程共享是线程安全的。在涉及多线程操作中不需要同步操作。
- 安全String常被用于用户名、密码、文件名等使用由于其不可变可避免黑客行为对其恶意修改。
19.序列化是什么
序列化是一种将对象转换成字节序列的过程用于解决在对对象流进行读写操作时所引发的问题。序列化可以将对象的状态写在流里进行网络传输或者保存到文件、数据库等系统里并在需要的时候把该流读取出来重新构造成一个相同的对象。
20.简述Java中Class对象
java中对象可以分为实例对象和Class对象每一个类都有一个Class对象其包含了与该类有关的信息。
获取Class对象的方法
- Class.forName(“类的全限定名”)
- 实例对象.getClass()
- 类名.class
21.Java反射机制是什么
Java反射机制是指在程序的运行过程中可以构造任意一个类的对象、获取任意一个类的成员变量和成员方法、获取任意一个对象所属的类信息、调用任意一个对象的属性和方法。反射机制使得Java具有动态获取程序信息和动态调用对象方法的能力。可以通过以下类调用反射API。
- Class类可获得类属性方法
- Field类获得类的成员变量
- Method类获取类的方法信息
- Construct类获取类的构造方法等信息
22.简述注解
Java 注解用于为 Java 代码提供元数据。作为元数据注解不直接影响你的代码执行但也有一些类型的注解实际上可以用于这一目的。
其可以用于提供信息给编译器在编译阶段时给软件提供信息进行相关的处理在运行时处理相应代码做对应操作。
23.简述元注解
元注解可以理解为注解的注解即在注解中使用实现想要的功能。其具体分为
- @Retention: 表示注解存在阶段是保留在源码还是在字节码类加载或者运行期JVM中运行。
- @Target表示注解作用的范围。
- @Documented将注解中的元素包含到 Javadoc 中去。
- @Inherited一个被@Inherited注解了的注解修饰了一个父类如果他的子类没有被其他注解修饰则它的子类也继承了父类的注解。
- @Repeatable被这个元注解修饰的注解可以同时作用一个对象多次但是每次作用注解又可以代表不同的含义。
24.简述Java异常的分类
Java异常分为Error程序无法处理的错误和Exception程序本身可以处理的异常。这两个类均继承Throwable。
Error常见的有StackOverFlowError,OutOfMemoryError等等。
Exception可分为运行时异常和非运行时异常。对于运行时异常可以利用try catch的方式进行处理也可以不处理。对于非运行时异常必须处理不处理的话程序无法通过编译。
25.简述throw与throws的区别
throw一般是用在方法体的内部由开发者定义当程序语句出现问题后主动抛出一个异常。
throws一般用于方法声明上代表该方法可能会抛出的异常列表。
26.简述泛型
泛型即“参数化类型”解决不确定对象具体类型的问题。在编译阶段有效。在泛型使用过程中操作的数据类型被指定为一个参数这种参数类型在类中称为泛型类、接口中称为泛型接口和方法中称为泛型方法。
27.简述泛型擦除
Java编译器生成的字节码是不包涵泛型信息的泛型类型信息将在编译处理时被擦除这个过程被称为泛型擦除。
28.简述Java基本数据类型
- byte: 占用1个字节取值范围-128 ~ 127
- short: 占用2个字节取值范围-215 ~ 215-1
- int占用4个字节取值范围-2^31 ~ 2^31-1
- long占用8个字节
- float占用4个字节
- double占用8个字节
- char: 占用2个字节
- boolean占用大小根据实现虚拟机不同有所差异
29.简述自动装箱拆箱
对于Java基本数据类型均对应一个包装类。
装箱就是自动将基本数据类型转换为包装器类型如int->Integer
拆箱就是自动将包装器类型转换为基本数据类型如Integer->int
30.简述重载与重写的区别
重写即子类重写父类的方法方法对应的形参和返回值类型都不能变。
重载即在一个类中方法名相同参数类型或数量不同。
31.简述java的多态
Java多态可以分为编译时多态和运行时多态。
编译时多态主要指方法的重载即通过参数列表的不同来区分不同的方法。
运行时多态主要指继承父类和实现接口时可使用父类引用指向子类对象。
运行时多态的实现主要依靠方法表方法表中最先存放的是Object类的方法接下来是该类的父类的方法最后是该类本身的方法。如果子类改写了父类的方法那么子类和父类的那些同名方法共享一个方法表项都被认作是父类的方法。因此可以实现运行时多态。
32.简述抽象类与接口的区别
抽象类体现的是is-a的关系如对于man is a person就可以将person定义为抽象类。
接口体现的是can的关系。是作为模板实现的。如设置接口flyplane类和bird类均可实现该接口。
一个类只能继承一个抽象类但可以实现多个接口。
33.简述Object类常用方法
- hashCode通过对象计算出的散列码。用于map型或equals方法。需要保证同一个对象多次调用该方法总返回相同的整型值。
- equals判断两个对象是否一致。需保证equals方法相同对应的对象hashCode也相同。
- toString: 用字符串表示该对象
- clone:深拷贝一个对象
34.简述内部类及其作用
- 成员内部类作为成员对象的内部类。可以访问private及以上外部类的属性和方法。外部类想要访问内部类属性或方法时必须要创建一个内部类对象然后通过该对象访问内部类的属性或方法。外部类也可访问private修饰的内部类属性。
- 局部内部类存在于方法中的内部类。访问权限类似局部变量只能访问外部类的final变量。
- 匿名内部类只能使用一次没有类名只能访问外部类的final变量。
- 静态内部类类似类的静态成员变量。
35.简述String/StringBuffer与StringBuilder
String类采用利用final修饰的字符数组进行字符串保存因此不可变。如果对String类型对象修改需要新建对象将老字符和新增加的字符一并存进去。
StringBuilder采用无final修饰的字符数组进行保存因此可变。但线程不安全。
StringBuffer采用无final修饰的字符数组进行保存可理解为实现线程安全的StringBuilder。
36.简述Java序列化与反序列化的实现
序列化将java对象转化为字节序列由此可以通过网络对象进行传输。
反序列化将字节序列转化为java对象。
具体实现实现Serializable接口或实现Externalizable接口中的writeExternal()与readExternal()方法。
37.简述JAVA的List
List是一个有序队列在JAVA中有两种实现方式:
ArrayList 使用数组实现是容量可变的非线程安全列表随机访问快集合扩容时会创建更大的数组把原有数组复制到新数组。
LinkedList 本质是双向链表与 ArrayList 相比插入和删除速度更快但随机访问元素很慢。
38.Java中线程安全的基本数据结构有哪些
HashTable: 哈希表的线程安全版效率低
ConcurrentHashMap哈希表的线程安全版效率高用于替代HashTable
Vector线程安全版Arraylist
Stack线程安全版栈
BlockingQueue及其子类线程安全版队列
39.简述JAVA的Set
Set 即集合该数据结构不允许元素重复且无序。JAVA对Set有三种实现方式
1.HashSet 通过 HashMap 实现HashMap 的 Key 即 HashSet 存储的元素Value系统自定义一个名为PRESENT 的 Object 类型常量。判断元素是否相同时先比较hashCode相同后再利用equals比较查询O(1)
2.LinkedHashSet 继承自 HashSet通过 LinkedHashMap 实现使用双向链表维护元素插入顺序。
3.TreeSet 通过 TreeMap 实现的底层数据结构是红黑树添加元素到集合时按照比较规则将其插入合适的位置保证插入后的集合仍然有序。查询O(logn)
40.简述JAVA的HashMap
JDK8 之前底层实现是数组 + 链表JDK8 改为数组 + 链表/红黑树。主要成员变量包括存储数据的table 数组、元素数量 size、加载因子 loadFactor。
HashMap 中数据以键值对的形式存在键对应的 hash 值用来计算数组下标如果两个元素 key 的hash 值一样就会发生哈希冲突被放到同一个链表上。
table 数组记录 HashMap 的数据每个下标对应一条链表所有哈希冲突的数据都会被存放到同一条链表Node/Entry 节点包含四个成员变量key、value、next 指针和 hash 值。在JDK8后链表超过8会转化为红黑树。
若当前数据/总数据容量>负载因子Hashmap将执行扩容操作。
默认初始化容量为 16扩容容量必须是 2 的幂次方、最大容量为 1<< 30 、默认加载因子为 0.75。
41.为何HashMap线程不安全
在JDK1.7中HashMap采用头插法插入元素因此并发情况下会导致环形链表产生死循环。
虽然JDK1.8采用了尾插法解决了这个问题但是并发下的put操作也会使前一个key被后一个key覆盖。由于HashMap有扩容机制存在也存在A线程进行扩容后B线程执行get方法出现失误的情况。
42.简述java的TreeMap
TreeMap是底层利用红黑树实现的Map结构底层实现是一棵平衡的排序二叉树由于红黑树的插入、删除、遍历时间复杂度都为O(logN)所以性能上低于哈希表。但是哈希表无法提供键值对的有序输出红黑树可以按照键的值的大小有序输出。
43.Collection和Collections有什么区别
- Collection是一个集合接口它提供了对集合对象进行基本操作的通用接口所有集合都是它的子类比如List、Set等。
- Collections是一个包装类包含了很多静态方法、不能被实例化而是作为工具类使用比如提供的排序方法
Collections.sort(list);提供的反转方法Collections.reverse(list)。
44.ArrayList、Vector和LinkedList有什么共同点与区别
- ArrayList、Vector和LinkedList都是可伸缩的数组即可以动态改变长度的数组。
- ArrayList和Vector都是基于存储元素的Object[] array来实现的它们会在内存中开辟一块连续的空间来存储支持下标、索引访问。但在涉及插入元素时可能需要移动容器中的元素插入效率较低。当存储元素超过容器的初始化容量大小ArrayList与Vector均会进行扩容。
- Vector是线程安全的其大部分方法是直接或间接同步的。ArrayList不是线程安全的其方法不具有同步性质。LinkedList也不是线程安全的。
- LinkedList采用双向链表实现对数据索引需要从头开始遍历因此随机访问效率较低但在插入元素的时候不需要对数据进行移动插入效率较高。
45.HashMap和Hashtable有什么区别
- HashMap是Hashtable的轻量级实现HashMap允许key和value为null但最多允许一条记录的key为null.而HashTable不允许。
- HashTable中的方法是线程安全的而HashMap不是。在多线程访问HashMap需要提供额外的同步机制。
- Hashtable使用Enumeration进行遍历HashMap使用Iterator进行遍历。
46.如何决定使用HashMap还是TreeMap?
如果对Map进行插入、删除或定位一个元素的操作更频繁HashMap是更好的选择。如果需要对key集合进行有序的遍历TreeMap是更好的选择。
47.fail-fast和fail-safe迭代器的区别是什么
- fail-fast直接在容器上进行在遍历过程中一旦发现容器中的数据被修改就会立刻抛出ConcurrentModificationException异常从而导致遍历失败。常见的使用fail-fast方式的容器有HashMap和ArrayList等。
- fail-safe这种遍历基于容器的一个克隆。因此对容器中的内容修改不影响遍历。常见的使用fail-safe方式遍历的容器有ConcurrentHashMap和CopyOnWriteArrayList。
48.HashSet中equals与hashCode之间的关系
equals和hashCode这两个方法都是从object类中继承过来的,equals主要用于判断对象的内存地址引用是否是同一个地址hashCode根据定义的哈希规则将对象的内存地址转换为一个哈希码。HashSet中存储的元素是不能重复的主要通过hashCode与equals两个方法来判断存储的对象是否相同
1.如果两个对象的hashCode值不同说明两个对象不相同。
2.如果两个对象的hashCode值相同接着会调用对象的equals方法如果equlas方法的返回结果为true那么说明两个对象相同否则不相同。
50.拆箱装箱原理
装箱过程是通过调用包装器的valueOf方法实现的将原值赋给对应类。
拆箱过程是通过调用包装器的 intValue/doubleValue等方法实现返回基本的数据类型。
51.java反射原理
Java会在编译期装载所有的类并将其元信息保存至Class类对象中。
因此可以设计x.class/x.getClass()/Class.forName()等方法获取Class对象。所以在反射调用Field/Method/Constructor对象时可根据Class类对象进行进一步操作。
52.Comparator和Comparable的区别
Comparable 是一个接口用于对象内部的比较方式该接口需要实现的方法是
public interface Comparable<T> {
public int compareTo(T o);
}
Comparator 也是一个接口该接口有个compare方法该接口需要实现的方法是
public interface Comparator<T> {
int compare(T o1, T o2);
}
除该方法外comparator还可以实现其他方法。
53.动态代理实现方式
- 利用JDK反射机制实现代理接口
- 利用CGLib对指定类生成子类进行代理。
54.简述OOMout of memory
当JVM分配内存不够会抛出out of memory异常
55.简述StackOverFlowError
调用栈深度超过限制产生的异常。
一般会在递归调用时出现。
二、Java多线程
1.简述java内存模型JMM
java内存模型定义了程序中各种变量的访问规则。其规定所有变量都存储在主内存线程均有自己的工作内存。
工作内存中保存被该线程使用的变量的主内存副本线程对变量的所有操作都必须在工作空间进行不能直接读写主内存数据。操作完成后线程的工作内存通过缓存一致性协议将操作完的数据刷回主存。
2.简述as-if-serial
编译器等会对原始的程序进行指令重排序和优化。但不管怎么重排序其结果和用户原始程序输出预定结果一致。
3.简述happens-before八大原则
- 程序顺序原则。即在一个线程内必须保证语义的串行性as-if-serial也就是说按照代码顺序执行
- 锁原则。解锁unlock操作必然发生在后续的同一个锁的加锁lock之前也就是说在一个锁被解锁后再加锁那么加锁的动作必须在解锁动作之后操作同一个锁
- volatile原则。volatile变量的写发生于读之前这样保证了volatile变量的可见性。简单理解就是volatile变量在每次被线程访问时都强迫于从主内存中读取该变量的值而当该变量发生变化时又会强迫将最新的值刷新到主内存任何时候不同的线程总能看到该变量的最新值
- 线程启动原则。线程的start()方法先于它的每一个动作即如果线程A在执行线程B的start方法之前修改了共享变量的值那么当线程B执行start方法时线程A对共享变量的修改对线程B可见
- 传递性原则。A先于BB先于C那么A必然先于C
- 线程终止原则。线程的所有操作先于线程的终结Thread.join()方法的作用是等待当前执行的线程终止。假设在线程B在终止之前修改了共享变量线程A从线程B的join方法成功返回后线程B对共享变量的修改将对线程A可见
- 线程中断原则。对线程interrupt()方法的调用先发生于被中断线程的代码检测到中断事件的发生可以通过Thread.interrupted()方法检测线程是否中断
- 对象的终结原则。对象的构造函数执行结束先于finalize方法。
4.as-if-serial 和 happens-before 的区别
as-if-serial 保证单线程程序的执行结果不变happens-before 保证正确同步的多线程程序的执行结果不变。
5.简述原子性操作
一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断要么就都不执行这就是原子性操作。
6.简述线程的可见性
可见性指当一个线程修改了共享变量时其他线程能够立即得知修改。volatile,synchronized,final都能保证可见性。
7.简述有序性
即虽然多线程存在并发和指令优化等操作在本线程内观察该线程的所有执行操作是有序的。
8.简述java中volatile关键字作用
-
保证变量对所有线程的可见性。
当一条线程修改了变量值新值对于其他线程来说是立即可以得知的。 -
禁止指令重排序优化。使用 volatile 变量进行写操作汇编指令带有 lock 前缀相当于一个内存屏障编译器不会将后面的指令重排到内存屏障之前。
9.java线程的实现方式
- 实现Runnable接口
- 继承Thread类。
- 实现Callable接口
10.简述java线程的状态
线程状态有New, RUNNABLE, BLOCK, WAITING, TIMED_WAITING, THERMINATED
NEW新建状态线程被创建且未启动此时还未调用 start 方法。
RUNNABLE: 运行状态。其表示线程正在JVM中执行但是这个执行不一定真的在跑也可能在排队等CPU。
BLOCKED阻塞状态。线程等待获取锁锁还没获得。
WAITING: 等待状态。线程内run方法运行完语句Object.wait()/Thread.join()进入该状态。
TIMED_WAITING限期等待。在一定时间之后跳出状态。调用Thread.sleep(long) Object.wait(long)
Thread.join(long)进入状态。其中这些参数代表等待的时间。
TERMINATED结束状态。线程调用完run方法进入该状态。
11.简述线程通信的方式
- volatile 关键词修饰变量保证所有线程对变量访问的可见性。
- synchronized关键词。确保多个线程在同一时刻只能有一个处于方法或同步块中。
- wait/notify方法
- IO通信
12.简述线程池
没有线程池的情况下多次创建销毁线程开销比较大。如果在开辟的线程执行完当前任务后执行接下来任务复用已创建的线程降低开销、控制最大并发数。
线程池创建线程时会将线程封装成工作线程 WorkerWorker 在执行完任务后还会循环获取工作队列中的任务来执行。
将任务派发给线程池时会出现以下几种情况
1.核心线程池未满创建一个新的线程执行任务。
2.如果核心线程池已满工作队列未满将线程存储在工作队列。
3.如果工作队列已满线程数小于最大线程数就创建一个新线程处理任务。
4.如果超过最大线程数按照拒绝策略来处理任务。
13.线程池参数
- corePoolSize常驻核心线程数。超过该值后如果线程空闲会被销毁。
- maximumPoolSize线程池能够容纳同时执行的线程最大数。
- keepAliveTime线程空闲时间线程空闲时间达到该值后会被销毁直到只剩下 corePoolSize 个线程为止避免浪费内存资源。
- workQueue工作队列。
- threadFactory线程工厂用来生产一组相同任务的线程。
- handler拒绝策略。有以下几种拒绝策略
- AbortPolicy丢弃任务并抛出异常
- CallerRunsPolicy 重新尝试提交该任务
- DiscardOldestPolicy抛弃队列里等待最久的任务并把当前任务加入队列
- DiscardPolicy表示直接抛弃当前任务但不抛出异常。
14.线程池创建方法
- newFixedThreadPool创建固定大小的线程池。
- newSingleThreadExecutor使用单线程线程池。
- newCachedThreadPoolmaximumPoolSize 设置为 Integer 最大值工作完成后会回收工作线程
- newScheduledThreadPool支持定期及周期性任务执行不回收工作线程。
- newWorkStealingPool一个拥有多个任务队列的线程池。
15.简述Executor框架
Executor框架目的是将任务提交和任务如何运行分离开来的机制。用户不再需要从代码层考虑设计任务的提交运行只需要调用Executor框架实现类的Execute方法就可以提交任务。产生线程池的函数ThreadPoolExecutor也是Executor的具体实现类。
16.简述Executor的继承关系
- Executor一个接口其定义了一个接收Runnable对象的方法executor该方法接收一个Runable实例执行这个任务。
- ExecutorServiceExecutor的子类接口其定义了一个接收Callable对象的方法返回 Future 对象同时提供execute方法。
- ScheduledExecutorServiceExecutorService的子类接口支持定期执行任务。
- AbstractExecutorService抽象类提供 ExecutorService 执行方法的默认实现。
- Executors实现ExecutorService接口的静态工厂类提供了一系列工厂方法用于创建线程池。
- ThreadPoolExecutor继承AbstractExecutorService用于创建线程池。
- ForkJoinPool: 继承AbstractExecutorServiceFork 将大任务分叉为多个小任务然后让小任务执行Join 是获得小任务的结果类似于map reduce。
- ScheduledThreadPoolExecutor继承ThreadPoolExecutor实现ScheduledExecutorService用于创建带定时任务的线程池。
17.简述线程池的状态
- Running能接受新提交的任务也可以处理阻塞队列的任务。
- Shutdown不再接受新提交的任务但可以处理存量任务线程池处于running时调用shutdown方法会进入该状态。
- Stop不接受新任务不处理存量任务调用shutdownnow进入该状态。
- Tidying所有任务已经终止了worker_count有效线程数为0。
- Terminated线程池彻底终止。在tidying模式下调用terminated方法会进入该状态。
18.简述阻塞队列
阻塞队列是生产者消费者的实现具体组件之一。当阻塞队列为空时从队列中获取元素的操作将会被阻塞当阻塞队列满了往队列添加元素的操作将会被阻塞。具体实现有
- ArrayBlockingQueue底层是由数组组成的有界阻塞队列。
- LinkedBlockingQueue底层是由链表组成的有界阻塞队列。
- PriorityBlockingQueue阻塞优先队列。
- DelayQueue创建元素时可以指定多久才能从队列中获取当前元素
- SynchronousQueue不存储元素的阻塞队列每一个存储必须等待一个取出操作
- LinkedTransferQueue与LinkedBlockingQueue相比多一个transfer方法即如果当前有消费者正等待接收元素可以把生产者传入的元素立刻传输给消费者。
- LinkedBlockingDeque双向阻塞队列。
19.谈一谈ThreadLocal
ThreadLocal 是线程共享变量。ThreadLoacl 有一个静态内部类 ThreadLocalMap其 Key 是ThreadLocal 对象值是 Entry 对象ThreadLocalMap是每个线程私有的。
- set 给ThreadLocalMap设置值。
- get 获取ThreadLocalMap。
- remove 删除ThreadLocalMap类型的对象。
存在的问题
- 对于线程池由于线程池会重用 Thread 对象因此与 Thread 绑定的 ThreadLocal 也会被重用造成一系列问题。
- 内存泄漏。由于 ThreadLocal 是弱引用但 Entry 的 value 是强引用因此当 ThreadLocal 被垃圾回收后value 依旧不会被释放产生内存泄漏。
20.聊聊你对java并发包下unsafe类的理解
对于 Java 语言没有直接的指针组件一般也不能使用偏移量对某块内存进行操作。这些操作相对来讲是安全safe的。
Java 有个类叫 Unsafe 类这个类使 Java 拥有了像 C 语言的指针一样操作内存空间的能力同时也带来了指针的问题。这个类可以说是 Java 并发开发的基础。
21.JAVA中的乐观锁与CAS算法
对于乐观锁开发者认为数据发送时发生并发冲突的概率不大所以读操作前不上锁。
到了写操作时才会进行判断数据在此期间是否被其他线程修改。如果发生修改那就返回写入失败如果没有被修改那就执行修改操作返回修改成功。
乐观锁一般都采用 Compare And SwapCAS算法进行实现。顾名思义该算法涉及到了两个操作比较Compare和交换Swap。
CAS 算法的思路如下
- 该算法认为不同线程对变量的操作时产生竞争的情况比较少。
- 该算法的核心是对当前读取变量值 E 和内存中的变量旧值 V 进行比较。
- 如果相等就代表其他线程没有对该变量进行修改就将变量值更新为新值 N。
- 如果不等就认为在读取值 E 到比较阶段有其他线程对变量进行过修改不进行任何操作。
22.ABA问题及解决方法简述
CAS 算法是基于值来做比较的如果当前有两个线程一个线程将变量值从 A 改为 B 再由 B 改回为A 当前线程开始执行 CAS 算法时就很容易认为值没有变化误认为读取数据到执行 CAS 算法的期间没有线程修改过数据。
juc 包提供了一个 AtomicStampedReference即在原始的版本下加入版本号戳解决 ABA 问题。
23.简述常见的Atomic类
在很多时候我们需要的仅仅是一个简单的、高效的、线程安全的++或者–方案使用synchronized关键字和lock固然可以实现但代价比较大此时用原子类更加方便。
基本数据类型的原子类有
- AtomicInteger 原子更新整形
- AtomicLong 原子更新长整型
- AtomicBoolean 原子更新布尔类型
Atomic数组类型有
- AtomicIntegerArray 原子更新整形数组里的元素
- AtomicLongArray 原子更新长整型数组里的元素
- AtomicReferenceArray 原子更新引用类型数组里的元素。
Atomic引用类型有:
- AtomicReference 原子更新引用类型
- AtomicMarkableReference 原子更新带有标记位的引用类型可以绑定一个 boolean 标记
- AtomicStampedReference 原子更新带有版本号的引用类型
FieldUpdater类型
- AtomicIntegerFieldUpdater 原子更新整形字段的更新器
- AtomicLongFieldUpdater 原子更新长整形字段的更新器
- AtomicReferenceFieldUpdater 原子更新引用类型字段的更新器
24.简述Atomic类基本实现原理
以AtomicIntger 为例
方法getAndIncrement以原子方式将当前的值加1具体实现为
- 在 for 死循环中取得 AtomicInteger 里存储的数值
- 对 AtomicInteger 当前的值加 1
- 调用 compareAndSet 方法进行原子更新
- 先检查当前数值是否等于 expect
- 如果等于则说明当前值没有被其他线程修改则将值更新为 next
- 如果不是会更新失败返回 false程序会进入 for 循环重新进行 compareAndSet 操作。
25.简述CountDownLatch
countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
是通过一个计数器来实现的计数器的初始值是线程的数量。每当一个线程执行完毕后调用countDown方法计数器的值就减1当计数器的值为0时表示所有线程都执行完毕然后在等待的线程就可以恢复工作了。
只能一次性使用不能reset。
26.简述CyclicBarrier
CyclicBarrier 主要功能和countDownLatch类似也是通过一个计数器使一个线程等待其他线程各自执行完毕后再执行。但是其可以重复使用reset。
27.简述Semaphore
Semaphore即信号量。
Semaphore 的构造方法参数接收一个 int 值设置一个计数器表示可用的许可数量即最大并发数。使用 acquire 方法获得一个许可证计数器减一使用 release 方法归还许可计数器加一。如果此时计数器值为0,线程进入休眠。
28.简述Exchanger
Exchanger类可用于两个线程之间交换信息。可简单地将Exchanger对象理解为一个包含两个格子的容器通过exchanger方法可以向两个格子中填充信息。线程通过exchange 方法交换数据第一个线程执行 exchanger 方法后会阻塞等待第二个线程执行该方法。当两个线程都到达同步点时这两个线程就可以交换数据当两个格子中的均被填充时该对象会自动将两个格子的信息交换然后返回给线程从而实现两个线程的信息交换。
29.简述ConcurrentHashMap
JDK7采用锁分段技术。首先将数据分成 Segment 数据段然后给每一个数据段配一把锁当一个线程占用锁访问其中一个段的数据时其他段的数据也能被其他线程访问。
get 除读到空值不需要加锁。该方法先经过一次再散列再用这个散列值通过散列运算定位到Segment最后通过散列算法定位到元素。
put 须加锁首先定位到 Segment然后进行插入操作第一步判断是否需要对 Segment 里的HashEntry 数组进行扩容第二步定位添加元素的位置然后将其放入数组。
JDK8的改进
- 取消分段锁机制采用CAS算法进行值的设置如果CAS失败再使用 synchronized 加锁添加元素
- 引入红黑树结构当某个槽内的元素个数超过8且 Node数组 容量大于 64 时链表转为红黑树。
- 使用了更加优化的方式统计集合内的元素数量。
30.Synchronized底层实现原理
Java 对象底层都关联一个的 monitor使用 synchronized 时 JVM 会根据使用环境找到对象的monitor根据 monitor 的状态进行加解锁的判断。如果成功加锁就成为该 monitor 的唯一持有者monitor 在被释放前不能再被其他线程获取。
synchronized在JVM编译后会产生monitorenter 和 monitorexit 这两个字节码指令获取和释放monitor。这两个字节码指令都需要一个引用类型的参数指明要锁定和解锁的对象对于同步普通方法锁是当前实例对象对于静态同步方法锁是当前类的 Class 对象对于同步方法块锁是synchronized 括号里的对象。
执行 monitorenter 指令时首先尝试获取对象锁。如果这个对象没有被锁定或当前线程已经持有锁就把锁的计数器加 1执行 monitorexit 指令时会将锁计数器减 1。一旦计数器为 0 锁随即就被释放。
31.Synchronized关键词使用方法
- 直接修饰某个实例方法
- 直接修饰某个静态方法
- 修饰代码块
32.简述java偏向锁
JDK 1.6 中提出了偏向锁的概念。该锁提出的原因是开发者发现多数情况下锁并不存在竞争一把锁往往是由同一个线程获得的。偏向锁并不会主动释放这样每次偏向锁进入的时候都会判断该资源是否是偏向自己的如果是偏向自己的则不需要进行额外的操作直接可以进入同步操作。
其申请流程为
- 首先需要判断对象的 Mark Word 是否属于偏向模式如果不属于那就进入轻量级锁判断逻辑。否则继续下一步判断
- 判断目前请求锁的线程 ID 是否和偏向锁本身记录的线程 ID 一致。如果一致继续下一步的判断如果不一致跳转到步骤4
- 判断是否需要重偏向。如果不用的话直接获得偏向锁
- 利用 CAS 算法将对象的 Mark Word 进行更改使线程 ID 部分换成本线程 ID。如果更换成功则重偏向完成获得偏向锁。如果失败则说明有多线程竞争升级为轻量级锁。
33.简述轻量级锁
轻量级锁是为了在没有竞争的前提下减少重量级锁出现并导致的性能消耗。
其申请流程为
- 如果同步对象没有被锁定虚拟机将在当前线程的栈帧中建立一个锁记录空间存储锁对象目前Mark Word 的拷贝。
- 虚拟机使用 CAS 尝试把对象的 Mark Word 更新为指向锁记录的指针
- 如果更新成功即代表该线程拥有了锁锁标志位将转变为 00表示处于轻量级锁定状态。
- 如果更新失败就意味着至少存在一条线程与当前线程竞争。虚拟机检查对象的 Mark Word 是否指向当前线程的栈帧
- 如果指向当前线程的栈帧说明当前线程已经拥有了锁直接进入同步块继续执行
- 如果不是则说明锁对象已经被其他线程抢占。
- 如果出现两条以上线程争用同一个锁轻量级锁就不再有效将膨胀为重量级锁锁标志状态变为10此时Mark Word 存储的就是指向重量级锁的指针后面等待锁的线程也必须阻塞。
34.简述锁优化策略
即自适应自旋、锁消除、锁粗化、锁升级等策略偏。
35.简述java的自旋锁
线程获取锁失败后可以采用这样的策略可以不放弃 CPU 不停的重试这种操作也称为自旋锁。
36.简述自适应自旋锁
自适应自旋锁自旋次数不再人为设定通常由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定。
37.简述锁粗化
锁粗化的思想就是扩大加锁范围避免反复的加锁和解锁。
38.简述锁消除
锁消除是一种更为彻底的优化在编译时java编译器对运行上下文进行扫描去除不可能存在共享资源竞争的锁。
39.简述Lock与ReentrantLock
Lock 是 java并发包的顶层接口。
可重入锁 ReentrantLock 是 Lock 最常见的实现与 synchronized 一样可重入。ReentrantLock 在默认情况下是非公平的可以通过构造方法指定公平锁。一旦使用了公平锁性能会下降。
40.简述AQS
AQSAbstractQueuedSynchronizer抽象的队列式同步器。
AQS是将每一条请求共享资源的线程封装成一个锁队列的一个结点Node来实现锁的分配。
AQS是用来构建锁或其他同步组件的基础框架它使用一个 volatile int state 变量作为共享资源如果线程获取资源失败则进入同步队列等待如果获取成功就执行临界区代码释放资源时会通知同步队列中的等待线程。
子类通过继承同步器并实现它的抽象方法getState、setState 和 compareAndSetState对同步状态进行更改。
41.AQS获取独占锁/释放独占锁原理
获取acquire
- 调用 tryAcquire 方法安全地获取线程同步状态获取失败的线程会被构造同步节点并通过addWaiter 方法加入到同步队列的尾部在队列中自旋。
- 调用 acquireQueued 方法使得该节点以死循环的方式获取同步状态如果获取不到则阻塞。
释放release
- 调用 tryRelease 方法释放同步状态
- 调用 unparkSuccessor 方法唤醒头节点的后继节点使后继节点重新尝试获取同步状态。
42.AQS获取共享锁/释放共享锁原理
获取锁acquireShared
- 调用 tryAcquireShared 方法尝试获取同步状态返回值不小于 0 表示能获取同步状态。
释放releaseShared
- 释放并唤醒后续处于等待状态的节点。
43.线程池类型
- newCachedThreadPool 可缓存线程池可设置最小线程数和最大线程数线程空闲1分钟后自动销毁。
- newFixedThreadPool 指定工作线程数量线程池。
- newSingleThreadExecutor 单线程Executor。
- newScheduleThreadPool 支持定时任务的指定工作线程数量线程池。
- newSingleThreadScheduledExecutor 支持定时任务的单线程Executor。
44.synchronized关键字作用
保证只有一个线程可以获取对象的锁,并执行代码块,其他线程不能在该线程执行代码块时执行。
45.简述三色标记法
三色标记法是垃圾收集器CMS和G1使用的标记方法该方法把对象分为三种颜色
- 白色该对象尚被未访问。
- 灰色该对象已被访问但该对象引用的其他对象并没有被访问。
- 黑色该对象和引用的其他对象均被访问。
因此对三色标记法来说所有对象都可以看作由白色集合黑色集合灰色集合组成。通过这种标记方法的访问过程如下
4. 初始所有对象均在白色集合
5. 将GC root直接引用的对象移动至灰色集合。
6. 从灰色集合中取出一个对象将该对象引用的白色集合对象移动至灰色集合
7. 移动完成后将该对象移动至黑色集合
8. 重复3-4操作。
三、Java虚拟机
1.简述JVM内存模型
线程私有的运行时数据区: 程序计数器、Java 虚拟机栈、本地方法栈。
线程共享的运行时数据区: Java 堆、方法区。
2.简述程序计数器
程序计数器表示当前线程所执行的字节码的行号指示器。
程序计数器不会产生StackOverflowError和OutOfMemoryError。
3.简述虚拟机栈
Java 虚拟机栈用来描述 Java 方法执行的内存模型。线程创建时就会分配一个栈空间线程结束后栈空间被回收。
栈中元素用于支持虚拟机进行方法调用每个方法在执行时都会创建一个栈帧存储方法的局部变量表、操作栈、动态链接和返回地址等信息。
虚拟机栈会产生两类异常
StackOverflowError线程请求的栈深度大于虚拟机允许的深度抛出。
OutOfMemoryError如果 JVM 栈容量可以动态扩展虚拟机栈占用内存超出抛出。
4.简述本地方法栈
本地方法栈与虚拟机栈作用相似不同的是虚拟机栈为虚拟机执行 Java 方法服务本地方法栈为本地方法服务。可以将虚拟机栈看作普通的java函数对应的内存模型本地方法栈看作由native关键词修饰的函数对应的内存模型。
本地方法栈会产生两类异常
StackOverflowError线程请求的栈深度大于虚拟机允许的深度抛出。
OutOfMemoryError如果 JVM 栈容量可以动态扩展虚拟机栈占用内存超出抛出。
5.简述JVM中的堆
堆主要作用是存放对象实例Java 里几乎所有对象实例都在堆分配内存堆也是内存管理中最大的一块。Java的垃圾回收主要就是针对堆这一区域进行。
可通过 -Xms 和 -Xmx 设置堆的最小和最大容量。
堆会抛出 OutOfMemoryError异常。
6.简述方法区
方法区用于存储被虚拟机加载的类信息、常量、静态变量等数据。
JDK6之前使用永久代实现方法区容易内存溢出。JDK7 把放在永久代的字符串常量池、静态变量等移出JDK8 中抛弃永久代改用在本地内存中实现的元空间来实现方法区把 JDK 7 中永久代内容移到元空间。
7.简述运行时常量池
运行时常量池存放常量池表用于存放编译器生成的各种字面量与符号引用。一般除了保存 Class 文件中描述的符号引用外还会把符号引用翻译的直接引用也存储在运行时常量池。除此之外也会存放字符串基本类型。
JDK8之前放在方法区大小受限于方法区。JDK8将运行时常量池存放堆中。
8.简述直接内存
直接内存也称为堆外内存就是把内存对象分配在JVM堆外的内存区域。这部分内存不是虚拟机管理而是由操作系统来管理。
Java通过DriectByteBuffer对其进行操作避免了在 Java 堆和 Native堆来回复制数据。
9.简述java创建对象的过程
- 检查该指令的参数能否在常量池中定位到一个类的符号引用并检查引用代表的类是否已被加载、解析和初始化如果没有就先执行类加载。
- 检查通过后虚拟机将为新生对象分配内存。
- 完成内存分配后虚拟机将成员变量设为零值
- 设置对象头包括哈希码、GC 信息、锁信息、对象所属类的类元信息等。
- 执行 init 方法初始化成员变量执行实例化代码块调用类的构造方法并把堆内对象的首地址赋值给引用变量。
10.简述JVM给对象分配内存的策略
- 指针碰撞 这种方式在内存中放一个指针作为分界指示器将使用过的内存放在一边空闲的放在另一边通过指针挪动完成分配。
- 空闲列表 对于 Java 堆内存不规整的情况虚拟机必须维护一个列表记录哪些内存可用在分配时从列表中找到一块足够大的空间划分给对象并更新列表记录。
11.java对象内存分配是如何保证线程安全的
- 对分配内存空间采用CAS机制配合失败重试的方式保证更新操作的原子性。该方式效率低。
- 每个线程在Java堆中预先分配一小块内存然后再给对象分配内存的时候直接在自己这块"私有"内存中分配。一般采用这种策略。
12.简述对象的内存布局
对象在堆内存的存储布局可分为对象头、实例数据和对齐填充。
对象头主要包含两部分数据 MarkWord、类型指针。MarkWord 用于存储哈希码HashCode、GC分代年龄、锁状态标志位、线程持有的锁、偏向线程ID等信息。
类型指针即对象指向他的类元数据指针如果对象是一个 Java 数组会有一块用于记录数组长度的数据。
实例数据存储代码中所定义的各种类型的字段信息。
对齐填充起占位作用。HotSpot 虚拟机要求对象的起始地址必须是8的整数倍因此需要对齐填充。
13.如何判断对象是否是垃圾
引用计数法设置引用计数器对象被引用计数器加 1引用失效时计数器减 1如果计数器为 0 则被标记为垃圾。会存在对象间循环引用的问题一般不使用这种方法。
可达性分析通过 GC Roots 的根对象作为起始节点从这些节点开始根据引用关系向下搜索如果某个对象没有被搜到则会被标记为垃圾。可作为 GC Roots 的对象包括虚拟机栈和本地方法栈中引用的对象、类静态属性引用的对象、常量引用的对象。
14.简述java的引用类型
强引用 被强引用关联的对象不会被回收。一般采用 new 方法创建强引用。
软引用被软引用关联的对象只有在内存不够的情况下才会被回收。一般采用 SoftReference 类来创建软引用。
弱引用垃圾收集器碰到即回收也就是说它只能存活到下一次垃圾回收发生之前。一般采用WeakReference 类来创建弱引用。
虚引用 无法通过该引用获取对象。唯一目的就是为了能在对象被回收时收到一个系统通知。虚引用必须与引用队列联合使用。
15.简述标记清除算法、标记整理算法和标记复制算法
标记清除算法先标记需清除的对象之后统一回收。这种方法效率不高会产生大量不连续的碎片。
标记整理算法先标记存活对象然后让所有存活对象向一端移动之后清理端边界以外的内存
标记复制算法将可用内存按容量划分为大小相等的两块每次只使用其中一块。当使用的这块空间用完了就将存活对象复制到另一块再把已使用过的内存空间一次清理掉。
16.简述分代收集算法
根据对象存活周期将内存划分为几块不同块采用适当的收集算法。
一般将堆分为新生代和老年代对这两块采用不同的算法。
新生代使用标记复制算法
老年代使用标记清除或者标记整理算法
17.简述Serial垃圾收集器
单线程串行收集器。垃圾回收的时候必须暂停其他所有线程。新生代使用标记复制算法老年代使用标记整理算法。简单高效。
18.简述ParNew垃圾收集器
可以看作Serial垃圾收集器的多线程版本新生代使用标记复制算法老年代使用标记整理算法。
19.简述Parallel Scavenge垃圾收集器
注重吞吐量即cpu运行代码时间/cpu耗时总时间cpu运行代码时间+ 垃圾回收时间。新生代使用标记复制算法老年代使用标记整理算法。
20.简述CMS垃圾收集器
注重最短时间停顿。CMS垃圾收集器为最早提出的并发收集器垃圾收集线程与用户线程同时工作。采用标记清除算法。该收集器分为初始标记、并发标记、并发预清理、并发清除、并发重置这么几个步骤。
初始标记暂停其他线程(stop the world)标记与GC roots直接关联的对象。
并发标记可达性分析过程(程序不会停顿)。
并发预清理查找执行并发标记阶段从年轻代晋升到老年代的对象重新标记暂停虚拟机stop theworld扫描CMS堆中剩余对象。
并发清除清理垃圾对象(程序不会停顿)。
并发重置重置CMS收集器的数据结构。
21.简述G1垃圾收集器
和之前收集器不同该垃圾收集器把堆划分成多个大小相等的独立区域Region新生代和老年代不再物理隔离。通过引入 Region 的概念从而将原来的一整块内存空间划分成多个的小空间使得每个小空间可以单独进行垃圾回收。
初始标记标记与GC roots直接关联的对象。
并发标记可达性分析。
最终标记对并发标记过程中用户线程修改的对象再次标记一下。
筛选回收对各个Region的回收价值和成本进行排序然后根据用户所期望的GC停顿时间制定回收计划并回收。
22.简述Minor GC
Minor GC指发生在新生代的垃圾收集因为 Java 对象大多存活时间短所以 Minor GC 非常频繁一般回收速度也比较快。
23.简述Full GC
Full GC 是清理整个堆空间—包括年轻代和永久代。调用System.gc(),老年代空间不足空间分配担保失败永生代空间不足会产生full gc。
24.常见内存分配策略
大多数情况下对象在新生代 Eden 区分配当 Eden 没有足够空间时将发起一次 Minor GC。
大对象需要大量连续内存空间直接进入老年代区分配。
如果经历过第一次 Minor GC 仍然存活且能被 Survivor 容纳该对象就会被移动到 Survivor 中并将年龄设置为 1并且每熬过一次 Minor GC 年龄就加 1 当增加到一定程度默认15就会被晋升到老年代。
如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 的一半年龄不小于该年龄的对象就可以直接进入老年代。
空间分配担保。MinorGC 前虚拟机必须检查老年代最大可用连续空间是否大于新生代对象总空间如果满足则说明这次 Minor GC 确定安全。如果不JVM会查看HandlePromotionFailure 参数是否允许担保失败如果允许会继续检查老年代最大可用连续空间是否大于历次晋升老年代对象的平均大小如果满足将Minor GC否则改成一次 FullGC。
25.简述JVM类加载过程
加载
- 通过全类名获取类的二进制字节流.
- 将类的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成类的Class对象作为方法区数据的入口。
验证对文件格式元数据字节码符号引用等验证正确性。
准备在方法区内为类变量分配内存并设置为0值。
解析将符号引用转化为直接引用。
初始化执行类构造器clinit方法真正初始化。
26.简述JVM中的类加载器
BootstrapClassLoader启动类加载器加载/lib下的jar包和类。C++编写。
ExtensionClassLoader扩展类加载器 /lib/ext目录下的jar包和类。java编写。
AppClassLoader应用类加载器加载当前classPath下的jar包和类。java编写。
27.简述双亲委派机制
一个类加载器收到类加载请求之后首先判断当前类是否被加载过。已经被加载的类会直接返回如果没有被加载首先将类加载请求转发给父类加载器一直转发到启动类加载器只有当父类加载器无法完成时才尝试自己加载。
加载类顺序BootstrapClassLoader->ExtensionClassLoader->AppClassLoader->CustomClassLoader
检查类是否加载顺序
CustomClassLoader->AppClassLoader->ExtensionClassLoader->BootstrapClassLoader
28.双亲委派机制的优点
- 避免类的重复加载。相同的类被不同的类加载器加载会产生不同的类双亲委派保证了java程序的稳定运行。
- 保证核心API不被修改。
29.如何破坏双亲委派机制
重载loadClass()方法即自定义类加载器。
30.如何构建自定义类加载器
- 新建自定义类继承自java.lang.ClassLoader
- 重写findClass、loadClass、defineClass方法
31.JVM常见调优参数
- -Xms 初始堆大小
- -Xmx 最大堆大小
- -XX:NewSize 年轻代大小
- -XX:MaxNewSize 年轻代最大值
- -XX:PermSize 永生代初始值
- -XX:MaxPermSize 永生代最大值
- -XX:NewRatio 新生代与老年代的比例
32.调用system.gc()一定会发生垃圾收集吗为什么
调用System.gc()的时候其实并不会马上进行垃圾回收,只会把这次gc请求记录下来。需配合System.runFinalization()才会进行真正回收
33.静态变量存储位置
在1.8以前静态成员变量存在方法区在1.8后由于JDK8取消永生代静态变量存储到了堆中
四、Redis
1.Redis单线程原理
首先必须明确Redis单线程指的是网络请求模块使用了一个线程其他模块仍用了多个线程。并不是一个线程完成了所有功能。
原理上其采用了epoll的多路复用特性因此可以采用单线程处理其网络请求。
2.Redis数据类型
String字符串类型最简单的类型
Hash类似于Map的一种结构。
List有序列表。
Set:无序集合。
ZSet带权值的无序集合即每个ZSet元素还另有一个数字代表权值集合通过权值进行排序。
3.什么情况下使用redis
- 针对热点数据进行缓存
- 对于特定限时数据的存放
- 针对带热点权值数据的排序list
- 分布式锁
4.redis与memcache的区别
- redis处理网络请求采用单线程模型而memcache采用多线程异步IO的方式
- redis支持数据持久化memcache不支持
- redis支持的数据格式比memcache更多
5.简述缓存穿透
缓存穿透指缓存和数据库均没有需要查询的数据攻击者不断发送这种请求使数据库压力过大。
6.简述缓存穿透的解决方法
- 在数据库操作访问前进行校验对不合法请求直接返回。
- 对于经常被访问的并且数据库没有的键缓存层记录键=null。
7.简述缓存击穿
缓存击穿指缓存中没有数据但数据库中有该数据。一般这种情况指特定数据的缓存时间到期但由于并发用户访问该数据特别多因此去数据库去取数据引起数据库访问压力过大
8.简述缓存穿透的解决方法
- 设置热点数据永远不过期。
- 对并发读数据设置并发锁降低并发性
9.简述缓存雪崩
缓存雪崩指缓存中一大批数据到过期时间而从缓存中删除。但该批数据查询数据量巨大查询全部走数据库造成数据库压力过大。
10.简述缓存雪崩的解决方法
- 缓存数据设置随机过期时间防止同一时间大量数据过期。
- 设置热点数据永远不过期。
- 对于集群部署的情况将热点数据均匀分布在不同缓存中。
11.Redis有哪些集群部署方式
- 主从复制
- 哨兵模式
- Cluster集群模式
12.简述主从复制模式
在主从复制中有主库Master节点和从库Slave节点两个角色。
从节点服务启动会连接主库并向主库发送SYNC命令。
主节点收到同步命令启动持久化工作工作执行完成后主节点将传送整个数据库文件到从库从节点接收到数据库文件数据之后将数据进行加载。此后主节点继续将所有已经收集到的修改命令和新的修改命令依次传送给从节点从节点依次执行从而达到最终的数据同步。
通过这种方式可以使写操作作用于主库而读操作作用于从库从而达到读写分离。
13.简述哨兵模式
哨兵模式监控redis集群中Master的工作的状态。在Master主服务器宕机时从slave中选择新机器当作master保证系统高可用。
每个哨兵每10秒向主服务器slave和其他哨兵发送ping。
客户端通过哨兵由哨兵提供可供服务的redis master节点。
哨兵只需要配master节点会自动寻找其对应的slave节点。
监控同一master节点的哨兵会自动互联组成哨兵网络当任一哨兵发现master连接不上即开会投票投票半数以上决定Master下线并从slave节点中选取master节点。
14.cluster集群
cluster提出了虚拟槽的概念。
- redis cluster默认有16384个槽在集群搭建的时候需要给节点分配哈希槽尽可能相同数量虚拟槽。
- 如果目前redis执行get操作redis先对这个key经过CRC16 hash运算并把结果对16384取余得到槽编号。
- 根据槽编号寻找到其对应的redis节点在节点上执行hash命令。
- 如果此时执行set操作节点先验证该key对应的槽编号是不是归本节点管如果是则保存数据。如果不是则发送正确节点编号给客户端。
15.简述Redis的RDB
RDB即将当前数据生成快照并保存于硬盘中。可以通过手动命令也可以设置自动触发。
16.简述Redis的save命令
save命令是redis手动触发RDB过程的命令。使用该命令后服务器阻塞直到RDB过程完成后终止。该过程占用内存较多。
17.简述Redis的bgsave命令
bgsave命令不阻塞主进程严格意义上也不是完全不阻塞详看下面过程该命令fork一个子进程用于执行RDB过程。其具体过程为
- 判断此时有没有子进程用于RDB有的话直接返回。
- redis进行fork子进程过程此时父进程处于阻塞状态。
- 子进程创建RDB文件完成后返回给父进程
18.简述Redis自动触发RDB机制
- 通过配置文件设置一定时间后自动执行RDB
- 如采用主从复制过程会自动执行RDB
- Redis执行shutdown时在未开启AOF后会执行RDB
19.简述Redis的AOF
AOF通过日志对数据的写入修改操作进行记录。这种持久化方式实时性更好。通过配置文件打开AOF。
20.简述AOF的持久化策略
- always。每执行一次数据修改命令就将其命令写入到磁盘日志文件上。
- everysec。每秒将命令写入到磁盘日志文件上。
- no。不主动设置由操作系统决定什么时候写入到磁盘日志文件上。
21.简述AOF的重写
随着客户端不断进行操作AOF对应的文件也越来越大。redis提供了bgrewriteaof函数针对目前数据库中数据在不读取原有AOF文件的基础上重写了一个新的AOF文件减少文件大小。
22.RDB与AOF优缺点比较
AOF占用的文件体积比RDB大。一般来说利用AOF备份对系统的消耗比RDB低。对于备份时出现系统故障RDB数据可能会全丢但AOF只会损失一部分。
RDB恢复速度比AOF快。
23.简述Redis淘汰机制
- noeviction默认禁止驱逐数据。内存不够使用时对申请内存的命令报错。
- volatile-lru从设置了过期时间的数据集中淘汰最近没使用的数据。
- volatile-ttl从设置了过期时间的数据集中淘汰即将要过期的数据。
- volatile-random从设置了过期时间的数据中随机淘汰数据。
- allkeys-lru淘汰最近没使用的数据。
- allkeys-random随机淘汰数据。
24.MySQL与Redis区别
mysql是关系型数据库并且其将数据存储在硬盘中读取速度较慢。
redis是非关系型数据库并且其将数据存储在内存中读取速度较快。
25.简述Redis过期策略
- 定期删除redis默认是每100ms就随机抽取一些设置了过期时间的key并检查其是否过期如果过期就删除。因此该删除策略并不会删除所有的过期key。
- 惰性删除在客户端需要获取某个key时redis将首先进行检查若该key设置了过期时间并已经过期就会删除。
实际上redis结合上述两种手段结合起来保证删除过期的key。
26.Redis基本数据类型实现原理
字符串采用类似数组的形式存储
list采用双向链表进行具体实现
hash:采用hashtable或者ziplist进行具体实现
集合采用intset或hashtable存储
有序集合采用ziplist或skiplist+hashtable实现
27.Redis快的原因
- redis是基于内存的数据库内存数据读取存储效率远高于硬盘型
- redis采用多路复用技术通过采用epoll的非阻塞IO提升了效率
五、设计模式
1.简述设计模式七大原则
开放封闭原则对扩展开放对修改关闭。在程序需要进行拓展的时候不能去修改原有的代码实现一个热插拔的效果。
单一职责原则一个类、接口或方法只负责一个职责降低代码复杂度以及变更引起的风险。
依赖倒置原则针对接口编程依赖于抽象类或接口而不依赖于具体实现类。
接口隔离原则将不同功能定义在不同接口中实现接口隔离。
里氏替换原则任何基类可以出现的地方子类一定可以出现。
迪米特原则每个模块对其他模块都要尽可能少地了解和依赖降低代码耦合度。
合成复用原则尽量使用组合(has-a)/聚合(contains-a)而不是继承(is-a)达到软件复用的目的。
2.简述设计模式的分类
创建型模式在创建对象的同时隐藏创建逻辑不使用 new 直接实例化对象。有工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式通过类和接口间的继承和引用实现创建复杂结构的对象。有适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式通过类之间不同通信方式实现不同行为。有策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
3.简述简单工厂模式
简单工厂模式指由一个工厂对象来创建实例,适用于工厂类负责创建对象较少的情况。例子Spring 中的 BeanFactory 使用简单工厂模式产生 Bean 对象。
4.简述工厂模式
工厂方法模式指定义一个创建对象的接口让接口的实现类决定创建哪种对象让类的实例化推迟到子类中进行。例子Spring 的 FactoryBean 接口的 getObject 方法也是工厂方法。
5.简述抽象工厂模式
抽象工厂模式指提供一个创建一系列相关或相互依赖对象的接口无需指定它们的具体类。例子java.sql.Connection 接口。
6.简述单例模式
一个单例类在任何情况下都只存在一个实例。
饿汉式实现
public class Singleton {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
懒汉式实现
public class Singleton {
private DoubleCheckSingleton(){}
private volatile static Singleton instance;
public static Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
7.简述代理模式
代理模式为其他对象提供一种代理以控制对这个对象的访问。优点是可以增强目标对象的功能降低代码耦合度扩展性好。缺点是在客户端和目标对象之间增加代理对象会导致请求处理速度变慢增加系统复杂度。
静态代理在程序运行前就已经存在代理类的字节码文件代理类和委托类的关系在运行前就确定了。
动态代理程序运行期间动态的生成所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
8.简述适配器模式
适配器模式将一个接口转换成客户希望的另一个接口使接口不兼容的那些类可以一起工作。
9.简述模板模式
模板模式定义了一个操作中的算法的骨架并将一些步骤延迟到子类适用于抽取子类重复代码到公共父类。
可以封装固定不变的部分扩展可变的部分。但每一个不同实现都需要一个子类维护会增加类的数量。
10.简述装饰器模式
装饰器模式可以动态地给对象添加一些额外的属性或行为即需要修改原有的功能但又不愿直接去修改原有的代码时设计一个Decorator套在原有代码外面。
11.简述观察者模式
观察者模式表示的是一种对象与对象之间具有依赖关系当一个对象的状态发生改变时所有依赖于它的对象都得到通知并被自动更新。
六、数据结构与算法
1.简述数据结构栈
栈是一种线性表其限制只能在表尾进行插入或删除操作。由于该特性又称为后进先出的线性表。
2.简述数据结构队列
队列是一种先进先出的线性表。其限制只能在线性表的一端进行插入而在另一端删除元素。
3.简述二叉树
二叉树是n个有限元素的集合该集合或者为空、或者由一个称为根root的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成。
4.简述满二叉树
除最后一层无任何子节点外每一层上的所有结点都有两个子结点的二叉树
5.简述完全二叉树
一棵深度为k的有n个结点的二叉树对树中的结点按从上至下、从左到右的顺序进行编号如果编号为i1≤i≤n的结点与满二叉树中编号为i的结点在二叉树中的位置相同则这棵二叉树称为完全二叉树。
6.简述二叉树的前中后序遍历算法
前序遍历若二叉树为空树则执行空逻辑否则
- 访问根节点
- 递归前序遍历左子树
- 递归前序遍历右子树
中序遍历若二叉树为空树则执行空逻辑否则
- 递归中序遍历左子树
- 访问根节点
- 递归中序遍历右子树
后序遍历若二叉树为空树则执行空逻辑否则
- 递归后序遍历左子树
- 递归后序遍历右子树
- 访问根节点
7.简述解决Hash冲突的方法
开放定址法当发生哈希冲突时如果哈希表未被装满那么可以把这个值存放到冲突位置中的下一个空位置中去
链地址法对相同的哈希地址设置一个单链表单链表内放的都是哈希冲突元素。
8.简述AVL树
AVL树是一种改进版的搜索二叉树其引入平衡因子左子支高度与右子支高度之差的绝对值通过旋转使其尽量保持平衡。
任何一个节点的左子支高度与右子支高度之差的绝对值不超过1。
9.简述红黑树
红黑树本身是有2-3树发展而来红黑树是保持平衡的二叉树其查找会比AVL树慢一点添加和删除元素会比AVL树快一点。增删改查统计性能上讲红黑树更优。
红黑树主要特征是在每个节点上增加一个属性表示节点颜色可以红色或黑色。红黑树和 AVL 树类似都是在进行插入和删除时通过旋转保持自身平衡从而获得较高的查找性能。红黑树保证从根节点到叶尾的最长路径不超过最短路径的 2 倍所以最差时间复杂度是 O(logn)。红黑树通过重新着色和左右旋转更加高效地完成了插入和删除之后的自平衡调整。
10.简述稳定排序和非稳定排序的区别
稳定排序排序前后两个相等的数相对位置不变则算法稳定
非稳定排序排序前后两个相等的数相对位置发生了变化则算法不稳定
11.常见的稳定排序算法有哪些
插入排序、冒泡排序、归并排序
12.常见的不稳定排序算法有哪些
希尔排序、直接选择排序、堆排序、快速排序
13.简述插入排序
插入排序每一趟将一个待排序记录按其关键字的大小插入到已排好序的一组记录的适当位置上直到所有待排序记录全部插入为止。
排序算法稳定。时间复杂度 O(n²)空间复杂度 O(1)。
14.简述希尔排序
希尔排序把记录按下标的一定增量分组对每组进行直接插入排序每次排序后减小增量当增量减至 1 时排序完毕。
排序算法不稳定。时间复杂度 O(nlogn)空间复杂度 O(1)。
15.简述直接选择排序
直接选择排序每次在未排序序列中找到最小元素和未排序序列的第一个元素交换位置再在剩余未排序序列中重复该操作直到所有元素排序完毕。
排序算法不稳定。时间复杂度 O(n²)空间复杂度 O(1)。
16.简述堆排序
堆排序将待排序数组看作一个树状数组建立一个二叉树堆。通过对这种数据结构进行每个元素的插入完成排序工作。
排序算法不稳定时间复杂度 O(nlogn)空间复杂度 O(1)。
17.简述冒泡排序
冒泡排序比较相邻的元素如果第一个比第二个大就进行交换对每一对相邻元素做同样的工作。
排序算法稳定时间复杂度 O(n²)空间复杂度 O(1)。
18.简述快速排序
快速排序随机选择一个基准元素通过一趟排序将要排序的数据分割成独立的两部分一部分全部小于等于基准元素一部分全部大于等于基准元素再按此方法递归对这两部分数据进行快速排序。
排序算法不稳定时间复杂度 O(nlogn)空间复杂度 O(logn)。
19.简述归并排序
归并排序将待排序序列分成两部分然后对两部分分别递归排序最后进行合并。
排序算法稳定时间复杂度都为 O(nlogn)空间复杂度为 O(n)。
20.简述图
图是由顶点集合和顶点之间的边集合组成的一种数据结构分为有向图和无向图。
有向图边具有方向性
无向图边不具有方向性
21.简述邻接矩阵
用一个二维数组存放图顶点间关系的数据这个二维数组称为邻接矩阵。
对于无向图邻接矩阵是对称矩阵
22.简述邻接表
邻接表是通过链表表示图连接关系的一种方。对于表头结点所对应的顶点存在相邻顶点则把相邻顶点依次存放于表头结点所指向的单向链表中。
23.简述图的深度优先搜索DFS
将图中每个顶点的访问标志设为 FALSE, 之后搜索图中每个顶点如果未被访问则以该顶点V0为起始点出发访问此顶点然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图直至图中所有和V0有路径相通的顶点都被访问到。
24.简述图的广度优先搜索
从图中的某个顶点V0出发并在访问此顶点之后依次访问V0的所有未被访问过的邻接点之后按这些顶点被访问的先后次序依次访问它们的邻接点直至图中所有和V0有路径相通的顶点都被访问到。
25.简述最小生成树和其对应的算法
对于有 n 个结点的原图生成原图的极小连通子图其包含原图中的所有 n 个结点并且有保持图连通的最少的边。
普里姆算法取图中任意一个顶点 v 作为生成树的根之后往生成树上添加新的顶点 w。在添加的顶点w 和已经在生成树上的顶点v 之间必定存在一条边并且该边的权值在所有连通顶点 v 和 w 之间的边中取值最小。之后继续往生成树上添加顶点直至生成树上含有 n-1 个顶点为止。
克鲁斯卡尔算法先构造一个只含 n 个顶点的子图 SG然后从权值最小的边开始若它的添加不使SG 中产生回路则在 SG 上加上这条边如此重复直至加上 n-1 条边为止。
26.简述最短路径算法
Dijkstral算法为求解一个点到其余各点最小路径的方法其算法为
假设我们求解的是顶点v到其余各个点的最短距离。n次循环至n个顶点全部遍历
- 从权值数组中找到权值最小的标记该边端点k
- 打印该路径及权值
- 如果存在经过顶点k到顶点i的边比v->i的权值小
- 更新权值数组及对应路径
27.简述堆
堆是一种完全二叉树形式其可分为最大值堆和最小值堆。
最大值堆子节点均小于父节点根节点是树中最大的节点。
最小值堆子节点均大于父节点根节点是树中最小的节点。
28.简述set
Set是一种集合。集合中的对象不按特定的方式排序并且没有重复对象。
29.说一下对于树的理解
数据结构树是一种由有限节点组成的层次关系的集合。其特点如下
- 每个节点有零个或多个子节点
- 只有一个节点没有父节点该节点称为根节点
- 除根节点外每个节点有且只有一个父节点
30.简述二叉查找树
- 二叉查找树的左子树若不为空则左子树上所有结点的值均小于它的根结点的值
- 二叉查找树的右子树若不为空则右子树上所有结点的值均大于它的根结点的值
- 二叉查找树的左、右子树也分别为二叉查找树
- 没有键值相等的结点。
七、操作系统
1.什么是操作系统请简要概述一下
操作系统是管理计算机硬件和软件资源的计算机程序提供一个计算机用户与计算机硬件系统之间的接口。
向上对用户程序提供接口向下接管硬件资源。
操作系统本质上也是一个软件作为最接近硬件的系统软件负责处理器管理、存储器管理、设备管理、文件管理和提供用户接口。
2.操作系统有哪些分类
操作系统常规可分为批处理操作系统、分时操作系统、实时操作系统。
若一个操作系统兼顾批操作和分时的功能则称该系统为通用操作系统。
常见的通用操作系统有Windows、Linux、MacOS等。
3.什么是内核态和用户态
为了避免操作系统和关键数据被用户程序破坏将处理器的执行状态分为内核态和用户态。
内核态是操作系统管理程序执行时所处的状态能够执行包含特权指令在内的一切指令能够访问系统内所有的存储空间。
用户态是用户程序执行时处理器所处的状态不能执行特权指令只能访问用户地址空间。
用户程序运行在用户态,操作系统内核运行在内核态。
4.如何实现内核态和用户态的切换
处理器从用户态切换到内核态的方法有三种系统调用、异常和外部中断。
- 系统调用是操作系统的最小功能单位是操作系统提供的用户接口系统调用本身是一种软中断。
- 异常也叫做内中断是由错误引起的如文件损坏、缺页故障等。
- 外部中断是通过两根信号线来通知处理器外设的状态变化是硬中断。
5.并发和并行的区别
-
并发concurrency指宏观上看起来两个程序在同时运行比如说在单核cpu上的多任务。但是从微观上看两个程序的指令是交织着运行的指令之间交错执行在单个周期内只运行了一个指令。这种并发并不能提高计算机的性能只能提高效率如降低某个进程的相应时间。
-
并行parallelism指严格物理意义上的同时运行比如多核cpu两个程序分别运行在两个核上两者之间互不影响单个周期内每个程序都运行了自己的指令也就是运行了两条指令。这样说来并行的确提高了计算机的效率。所以现在的cpu都是往多核方面发展。
6.什么是进程
进程是操作系统中最重要的抽象概念之一是资源分配的基本单位是独立运行的基本单位。
进程的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文context中。
上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据它是栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
进程一般由以下的部分组成
- 进程控制块PCB是进程存在的唯一标志包含进程标识符PID进程当前状态程序和数据地址进程优先级、CPU现场保护区用于进程切换占有的资源清单等。
- 程序段
- 数据段
7.进程的基本操作
以Unix系统举例
-
进程的创建fork()。新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本这就意味着当父进程调用 fork 时子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的 PID。fork函数是有趣的也常常令人迷惑 因为它只被调用一次却会返回两次一次是在调用进程父进程中一次是在新创建的子进程中。在父进程中fork 返回子进程的 PID。在子进程中fork 返回 0。因为子进程的 PID 总是为非零返回值就提供一个明 确的方法来分辨程序是在父进程还是在子进程中执行。
pid_t fork(void); -
回收子进程当一个进程由于某种原因终止时内核并不是立即把它从系统中清除。相反进程被保持在一种已终止的状态中直到被它的父进程回收reaped。当父进程回收已终止的子进程时内核将子进程的退出状态传递给父进程然后抛弃已终止的进程。一个进程可以通过调用waitpid 函数来等待它的子进程终止或者停止。
pid_t waitpid(pid_t pid, int *statusp, int options); -
加载并运行程序execve 函数在当前进程的上下文中加载并运行一个新程序。
int execve(const char *filename, const char *argv[], const char *envp[]); -
进程终止
void exit(int status);
8.简述进程间通信方法
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
不同进程间的通信本质进程之间可以看到一份公共资源而提供这份资源的形式或者提供者不同造成了通信方式不同。
进程间通信主要包括管道、系统IPC包括消息队列、信号量、信号、共享内存等、以及套接字socket。
9.进程如何通过管道进行通信
管道是一种最基本的IPC机制作用于有血缘关系的进程之间完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质
- 其本质是一个伪文件(实为内核缓冲区)
- 由两个文件描述符引用一个表示读端一个表示写端。
- 规定数据从管道的写端流入管道从读端流出。
管道的原理: 管道实为内核使用环形队列机制借助内核缓冲区实现。
管道的局限性
- 数据自己读不能自己写。
- 数据一旦被读走便不在管道中存在不可反复读取。
- 由于管道采用半双工通信方式。因此数据只能在一个方向上流动。
- 只能在有公共祖先的进程间使用管道。
10.进程如何通过共享内存通信
它使得多个进程可以访问同一块内存空间不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作如互斥锁和信号量等。
特点
- 共享内存是最快的一种IPC因为进程是直接对内存进行操作来实现通信避免了数据在用户空间和内核空间来回拷贝。
- 因为多个进程可以同时操作所以需要进行同步处理。
- 信号量和共享内存通常结合在一起使用信号量用来同步对共享内存的访问。
11.什么是信号
一个信号就是一条小消息它通知进程系统中发生了一个某种类型的事件。 Linux 系统上支持的30 种不同类型的信号。 每种信号类型都对应于某种系统事件。低层的硬件异常是由内核异常处理程序处理的正常情况下对用户进程而言是不可见的。信号提供了一种机制通知用户进程发生了这些异常。
- 发送信号内核通过更新目的进程上下文中的某个状态发送递送一个信号给目的进程。发送
信号可以有如下两种原因- 内核检测到一个系统事件比如除零错误或者子进程终止。
- 一个进程调用了kill 函数 显式地要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
- 接收信号当目的进程被内核强迫以某种方式对信号的发送做出反应时它就接收了信号。进程可以忽略这个信号终止或者通过执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号。
12.如何编写正确且安全的信号处理函数
-
处理程序要尽可能简单。 避免麻烦的最好方法是保持处理程序尽可能小和简单。
例如处理程序可能只是简单地设置全局标志并立即返回所有与接收信号相关的处理都由主程序执行它周期性地检查(并重置)这个标志。 -
在处理程序中只调用异步信号安全的函数。
所谓异步信号安全的函数(或简称安全的函数)能够被信号处理程序安全地调用原因有二要么它是可重入的(例如只访问局部变量要么它不能被信号处理程序中断。 -
保存和恢复errno。
许多Linux 异步信号安全的函数都会在出错返回时设置errno在处理程序中调用这样的函数可能会干扰主程序中其他依赖。解决方法是在进人处理程序时把errno 保存在一个局部变量中在处理程序返回前恢复它。注意只有在处理程序要返回时才有此必要。如果处理程序调用_exit终止该进程那么就不需要这样做了。 -
阻塞所有的信号保护对共享全局数据结构的访问。
如果处理程序和主程序或其他处理程序共享一个全局数据结构那么在访问(读或者写)该数据结构时你的处理程序和主程序应该暂时阻塞所有的信号。这条规则的原因是从主程序访问一个数据结构d 通常需要一系列的指令如果指令序列被访问d 的处理程序中断那么处理程序可能会发现d 的状态不一致得到不可预知的结果。在访问d时暂时阻塞信号保证了处理程序不会中断该指令序列。 -
用volatile 声明全局变量。
考虑一个处理程序和一个main 函数它们共享一个全局变量g 。处理程序更新gmain 周期性地读g 对于一个优化编译器而言main 中g的值看上去从来没有变化过因此使用缓存在寄存器中g 的副本来满足对g 的每次引用是很安全的。如果这样main 函数可能永远都无法看到处理程序更新过的值。可以用volatile 类型限定符来定义一个变量告诉编译器不要缓存这个变量。例如volatile 限定符强迫编译器毎次在代码中引用g时都要从内存中读取g的值。一般来说和其他所有共享数据结构一样应该暂时阻塞信号保护每次对全局变量的访问。
volatile int g; -
用sig_atomic_t声明标志。
在常见的处理程序设计中处理程序会写全局标志来记录收到了信号。主程序周期性地读这个标志响应信号再清除该标志。对于通过这种方式来共享的标志C 提供一种整型数据类型sig_atomic_t对它的读和写保证会是原子的不可中断的。 -
信号的一个与直觉不符的方面是未处理的信号是不排队的。
因为 pending 位向量中每种类型的信号只对应有一位所以每种类型最多只能有一个未处理的信号。关键思想是如果存在一个未处理的信号就表明至少有一个信号到达了。
13.进程调度的时机
- 当前运行的进程运行结束。
- 当前运行的进程由于某种原因阻塞。
- 执行完系统调用等系统程序后返回用户进程。
- 在使用抢占调度的系统中具有更高优先级的进程就绪时。
- 分时系统中分给当前进程的时间片用完。
14.不能进行进程调度的情况
- 在中断处理程序执行时。
- 在操作系统的内核程序临界区内。
- 其它需要完全屏蔽中断的原子操作过程中。
15.进程的调度策略
- 先到先服务调度算法
- 短作业优先调度算法
- 优先级调度算法
- 时间片轮转调度算法
- 高响应比优先调度算法
- 多级队列调度算法
- 多级反馈队列调度算法
16.进程调度策略的基本设计指标
- CPU利用率
- 系统吞吐率即单位时间内CPU完成的作业的数量。
- 响应时间。
- 周转时间。是指作业从提交到完成的时间间隔。从每个作业的角度看完成每个作业的时间也是很关键
- 平均周转时间
- 带权周转时间
- 平均带权周转时间
17.进程的状态与状态转换
进程在运行时有三种基本状态就绪态、运行态和阻塞态。
- 运行running态进程占有处理器正在运行的状态。进程已获得CPU其程序正在执行。在单处理机系统中只有一个进程处于执行状态 在多处理机系统中则有多个进程处于执行状态。
2.就绪ready态进程具备运行条件等待系统分配处理器以便运行的状态。 当进程已分配到除CPU以外的所有必要资源后只要再获得CPU便可立即执行进程这时的状态称为就绪状态。在一个系统中处于就绪状态的进程可能有多个通常将它们排成一个队列称为就绪队列。
3.阻塞wait态又称等待态或睡眠态指进程不具备运行条件正在等待某个时间完成的状态。
各状态之间的转换
- 就绪→执行 处于就绪状态的进程当进程调度程序为之分配了处理机后该进程便由就绪状态转变成执行状态。
- 执行→就绪 处于执行状态的进程在其执行过程中因分配给它的一个时间片已用完而不得不让出处理机于是进程从执行状态转变成就绪状态。
- 执行→阻塞 正在执行的进程因等待某种事件发生而无法继续执行时便从执行状态变成阻塞状态。
- 阻塞→就绪 处于阻塞状态的进程若其等待的事件已经发生于是进程由阻塞状态转变为就绪状态。
18.什么是孤儿进程僵尸进程?
- 孤儿进程 父进程退出子进程还在运行的这些子进程都是孤儿进程孤儿进程将被init进程1号进程所收养并由init进程对他们完成状态收集工作。
- 僵尸进程 进程使用fork创建子进程如果子进程退出而父进程并没有调用wait或waitpid 获取子进程的状态信息那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。
19.什么是线程
- 线程是进程划分的任务是一个进程内可调度的实体是CPU调度的基本单位用于保证程序的实时性实现进程内部的并发。
- 线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器独自的寄存器组指令计数器和处理器状态。
- 每个线程完成不同的任务但是属于同一个进程的不同线程之间共享同一地址空间也就是同样的动态内存映射文件目标代码等等打开的文件队列和其他内核资源。
20.为什么需要线程
线程产生的原因进程可以使多个程序能并发执行以提高资源的利用率和系统的吞吐量
但是其具有一些缺点
- 进程在同一时刻只能做一个任务很多时候不能充分利用CPU资源。
- 进程在执行的过程中如果发生阻塞整个进程就会挂起即使进程中其它任务不依赖于等待的资源进程仍会被阻塞。
引入线程就是为了解决以上进程的不足线程具有以下的优点
- 从资源上来讲开辟一个线程所需要的资源要远小于一个进程。
- 从切换效率上来讲运行于一个进程中的多个线程它们之间使用相同的地址空间而且线程间彼此切换所需时间也远远小于进程间切换所需要的时间这种时间的差异主要由于缓存的大量未命中导致。
- 从通信机制上来讲线程间方便的通信机制。对不同进程来说它们具有独立的地址空间要进行数据的传递只能通过进程间通信的方式进行。线程则不然属于同一个进程的不同线程之间共享同一地址空间所以一个线程的数据可以被其它线程感知线程间可以直接读写进程数据段如全局变量来进行通信需要一些同步措施。
21.简述线程和进程的区别和联系
- 一个线程只能属于一个进程而一个进程可以有多个线程但至少有一个线程。线程依赖于进程而存在。
- 进程在执行过程中拥有独立的地址空间而多个线程共享进程的地址空间。资源分配给进程同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段代码和常量数据段全局变量和静态变量扩展段堆存储。但是每个线程拥有自己的栈段栈段又叫运行时段用来存放所有局部变量和临时变量。
- 进程是资源分配的最小单位线程是CPU调度的最小单位。
- 通信由于同一进程中的多个线程具有相同的地址空间使它们之间的同步和通信的实现也变得比较容易。进程间通信 IPC 线程间可以直接读写进程数据段如全局变量来进行通信需要一些同步方法以保证数据的一致性。
- 进程编程调试简单可靠性高但是创建销毁开销大线程正相反开销小切换速度快但是编程调试相对复杂。
- 进程间不会相互影响一个进程内某个线程挂掉将导致整个进程挂掉。
- 进程适应于多核、多机分布线程适用于多核。
22.进程和线程的基本API
进程API以Unix系统为例线程相关的API属于Posix线程(Pthreads)标准接口
进程源语 | 线程源语 | 描述 |
---|---|---|
fork | pthread_create | 创建新的控制流 |
exit | pthread_exit | 从现有的控制流中退出 |
waitpid | pthread_join | 从控制流中得到退出状态 |
atexit | pthread_cancel_push | 注册在退出控制流时调用的函数 |
getpid | pthread_self | 获取控制流的ID |
abort | pthread_cancel | 请求控制流的非正常退出 |
23.多线程模型
- 多对一模型。将多个用户级线程映射到一个内核级线程上。该模型下线程在用户空间进行管理效率较高。缺点就是一个线程阻塞整个进程内的所有线程都会阻塞。几乎没有系统继续使用这个模型。
- 一对一模型。将内核线程与用户线程一一对应。优点是一个线程阻塞时不会影响到其它线程的执行。该模型具有更好的并发性。缺点是内核线程数量一般有上限会限制用户线程的数量。更多的内核线程数目也给线程切换带来额外的负担。linux和Windows操作系统家族都是使用一对一模型。
- 多对多模型。将多个用户级线程映射到多个内核级线程上。结合了多对一模型和一对一模型的特点。
24.进程同步的方法
操作系统中进程是具有不同的地址空间的两个进程是不能感知到对方的存在的。有时候需要多个进程来协同完成一些任务。
当多个进程需要对同一个内核资源进行操作时这些进程便是竞争的关系操作系统必须协调各个进程对资源的占用进程的互斥是解决进程间竞争关系的方法。 进程互斥指若干个进程要使用同一共享资源时任何时刻最多允许一个进程去使用其他要使用该资源的进程必须等待直到占有资源的进程释放该资源。
当多个进程协同完成一些任务时不同进程的执行进度不一致这便产生了进程的同步问题。需要操作系统干预在特定的同步点对所有进程进行同步这种协作进程之间相互等待对方消息或信号的协调关系称为进程同步。进程互斥本质上也是一种进程同步。
进程的同步方法
- 互斥锁
- 读写锁
- 条件变量
- 记录锁(record locking)
- 信号量
- 屏障barrier
25.线程同步的方法
操作系统中属于同一进程的线程之间具有相同的地址空间线程之间共享数据变得简单高效。遇到竞争的线程同时修改同一数据或是协作的线程设置同步点的问题时需要使用一些线程同步的方法来解决这些问题。
线程同步的方法
- 互斥锁
- 读写锁
- 条件变量
- 信号量
- 自旋锁
- 屏障barrier
26.进程同步与线程同步有什么区别
进程之间地址空间不同不能感知对方的存在同步时需要将锁放在多进程共享的空间。而线程之间共享同一地址空间同步时把锁放在所属的同一进程空间即可。
27.死锁是怎样产生的
死锁是指两个或两个以上进程在执行过程中因争夺资源而造成的相互等待的现象。
产生死锁需要满足下面四个条件
- 互斥条件进程对所分配到的资源不允许其他进程访问若其他进程访问该资源只能等待直至占有该资源的进程使用完成后释放该资源。
- 占有并等待条件进程获得一定的资源后又对其他资源发出请求但是该资源可能被其他进程占有此时请求阻塞但该进程不会释放自己已经占有的资源。
- 非抢占条件进程已获得的资源在未完成使用之前不可被剥夺只能在使用后自己释放。
- 循环等待条件进程发生死锁后必然存在一个进程-资源之间的环形链。
28.如何解决死锁问题
解决死锁的方法即破坏产生死锁的四个必要条件之一主要方法如下:
- 资源一次性分配这样就不会再有请求了破坏请求条件。
- 只要有一个资源得不到分配也不给这个进程分配其他的资源破坏占有并等待条件。
- 可抢占资源即当进程新的资源未得到满足时释放已占有的资源从而破坏不可抢占的条件。
- 资源有序分配法系统给每类资源赋予一个序号每个进程按编号递增的请求资源释放则相反从而破坏环路等待的条件
29.什么是虚拟地址什么是物理地址
地址空间是一个非负整数地址的有序集合。
在一个带虚拟内存的系统中CPU 从一个有N=pow(2,n)个地址的地址空间中生成虚拟地址这个地址空间称为虚拟地址空间virtual address space,现代系统通常支持 32 位或者 64 位虚拟地址空间。
一个系统还有一个物理地址空间physical address space对应于系统中物理内存的M 个字节。
地址空间的概念是很重要的因为它清楚地区分了数据对象字节和它们的属性地址。
一旦认识到了这种区别那么我们就可以将其推广允许每个数据对象有多个独立的地址其中每个地址都选自一个不同的地址空间。这就是虚拟内存的基本思想。
主存中的每字节都有一个选自虚拟地址空间的虚拟地址和一个选自物理地址空间的物理地址。
30.什么是虚拟内存
为了更加有效地管理内存并且少出错现代系统提供了一种对主存的抽象概念叫做虚拟内存(VM)。虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互它为每个进程提供了一个大的、一致的和私有的地址空间。通过一个很清晰的机制虚拟内存提供了三个重要的能力
- 它将主存看成是一个存储在磁盘上的地址空间的高速缓存在主存中只保存活动区域并根据需要在磁盘和主存之间来回传送数据通过这种方式它高效地使用了主存。
- 它为每个进程提供了一致的地址空间从而简化了内存管理。
- 它保护了每个进程的地址空间不被其他进程破坏。
31.为什么要引入虚拟内存
-
虚拟内存作为缓存的工具
- 虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。
- 虚拟内存利用DRAM缓存来自通常更大的虚拟地址空间的页面。
-
虚拟内存作为内存管理的工具。操作系统为每个进程提供了一个独立的页表也就是独立的虚拟地址空间。多个虚拟页面可以映射到同一个物理页面上。
- 简化链接 独立的地址空间允许每个进程的内存映像使用相同的基本格式而不管代码和数据实际存放在物理内存的何处。
- 例如一个给定的 linux 系统上的每个进程都是用类似的内存格式对于64为地址空间代码段总是从虚拟地址 0x400000 开始数据段代码段栈堆等等。
- 简化加载 虚拟内存还使得容易向内存中加载可执行文件和共享对象文件。要把目标文件中.text和.data节加载到一个新创建的进程中Linux加载器为代码和数据段分配虚拟页VP把他们标记为无效未被缓存 将页表条目指向目标文件的起始位置。
- 加载器从不在磁盘到内存实际复制任何数据在每个页初次被引用时虚拟内存系统会按照需要自动的调入数据页。
- 简化共享 独立地址空间为OS提供了一个管理用户进程和操作系统自身之间共享的一致机制。
- 一般每个进程有各自私有的代码数据堆栈是不和其他进程共享的这样OS创建页表将虚拟页映射到不连续的物理页面。
- 某些情况下需要进程来共享代码和数据。例如每个进程调用相同的操作系统内核代码或者C标准库函数。OS会把不同进程中适当的虚拟页面映射到相同的物理页面。
- 简化内存分配 虚拟内存向用户提供一个简单的分配额外内存的机制。当一个运行在用户进程中的程序要求额外的堆空间时如 malloc OS分配一个适当k大小个连续的虚拟内存页面并且将他们映射到物理内存中任意位置的k个任意物理页面因此操作系统没有必要分配k个连续的物理内存页面页面可以随机的分散在物理内存中。
- 简化链接 独立的地址空间允许每个进程的内存映像使用相同的基本格式而不管代码和数据实际存放在物理内存的何处。
虚拟内存作为内存保护的工具。不应该允许一个用户进程修改它的只读段也不允许它修改任何内核代码和数据结构不允许读写其他进程的私有内存不允许修改任何与其他进程共享的虚拟页面。每次CPU生成一个地址时 MMU 会读一个 PTE 通过在 PTE 上添加一些额外的许可位来控制对一个虚拟页面内容的访问十分简单。
32.常见的页面置换算法
当访问一个内存中不存在的页并且内存已满则需要从内存中调出一个页或将数据送至磁盘对换区替换一个页这种现象叫做缺页置换。当前操作系统最常采用的缺页置换算法如下
-
先进先出(FIFO)算法
- 思路置换最先调入内存的页面即置换在内存中驻留时间最久的页面。
- 实现按照进入内存的先后次序排列成队列从队尾进入从队首删除。
- 特点实现简单性能较差调出的页面可能是经常访问的
-
最近最少使用 LRU 算法:
- 思路 置换最近一段时间以来最长时间未访问过的页面。根据程序局部性原理刚被访问的页面可能马上又要被访问而较长时间内没有被访问的页面可能最近不会被访问。
- 实现缺页时计算内存中每个逻辑页面的上一次访问时间选择上一次使用到当前时间最长的页面
- 特点可能达到最优的效果维护这样的访问链表开销比较大
当前最常采用的就是 LRU 算法。
-
最不常用算法 Least Frequently Used, LFU
- 思路缺页时置换访问次数最少的页面
- 实现每个页面设置一个访问计数访问页面时访问计数加1缺页时置换计数最小的页面
- 特点算法开销大开始时频繁使用但以后不使用的页面很难置换
33.请说一下什么是写时复制
- 写时复制是指在并发访问的情景下当需要修改JAVA中Containers的元素时不直接修改该容器而是先复制一份副本在副本上进行修改。修改完成之后将指向原来容器的引用指向新的容器(副本容器)。
- 写时复制带来的影响
①由于不会修改原始容器只修改副本容器。因此可以对原始容器进行并发地读。其次实现了读操作与写操作的分离读操作发生在原始容器上写操作发生在副本容器上。
②数据一致性问题读操作的线程可能不会立即读取到新修改的数据因为修改操作发生在副本上。但最终修改操作会完成并更新容器因此这是最终一致性。
34.实时操作系统的概念
实时操作系统Real-time operating system, RTOS又称即时操作系统它会按照排序运行、管理系统资源并为开发应用程序提供一致的基础。 实时操作系统与一般的操作系统相比最大的特色就是“实时性”如果有一个任务需要执行实时操作系统会马上在较短时间内执行该任务不会有较长的延时。这种特性保证了各个任务的及时执行。
35.优先级反转是什么如何解决
由于多进程共享资源具有最高优先权的进程被低优先级进程阻塞反而使具有中优先级的进程先于高优先级的进程执行导致系统的崩溃。这就是所谓的优先级反转(Priority Inversion)。其实,优先级反转是在高优级(假设为A)的任务要访问一个被低优先级任务(假设为C)占有的资源时,被阻塞.而此时又有优先级高于占有资源的任务©而低于被阻塞的任务(A)的优先级的任务(假设为B)时,于是,占有资源的任务就被挂起(占有的资源仍为它占有),因为占有资源的任务优先级很低,所以,它可能一直被另外的任务挂起.而它占有的资源也就一直不能释放,这样,引起任务A一直没办法执行.而比它优先低的任务却可以执行。
目前解决优先级反转有许多种方法。其中普遍使用的有2种方法一种被称作优先级继承(priority inheritance)另一种被称作优先级极限(priority ceilings)。
- 优先级继承(priority inheritance) 优先级继承是指将低优先级任务的优先级提升到等待它所占有的资源的最高优先级任务的优先级.当高优先级任务由于等待资源而被阻塞时,此时资源的拥有者的优先级将会自动被提升。
- 优先级天花板(priority ceilings)优先级天花板是指将申请某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级.(这个优先级称为该资源的优先级天花板)。
36.简述 select
select是一种多路复用技术。其收到所有输入的文件描述符返回哪些文件有新数据。
其可以设置为阻塞或者非阻塞状态底层采用1024位bitmap做实现因此有文件描述符上限数。
37.简述poll
poll是一种多路复用技术。其收到所有输入的文件描述符返回哪些文件有新数据。
其通过链表代替了之前select的数据结构使得其没有上限限制。
38.简述epoll
epoll是一种多路复用技术。其采用一个文件描述符管理多个输入的文件描述符采用事件回调的方式提高了程序运行效率。
39.简述虚拟地址到物理地址转化过程
虚拟地址由虚拟页号和页偏移两部分组成。
通过虚拟地址的页面号首先在快表中查询是否有该映射查询不成功在页表中找到该页对应的物理地址。
然后通过页物理地址+页偏移得到真实的物理地址
40.简述页表
页表用于存储虚拟地址中的虚拟页面号和物理页面号的映射关系。
除此之外有些页的读写有限制页表也通过其他存储位标记该页访问位是否在内存中可能被页面置换出去了等等。
41.简述多级页表
多级页表用于减少内存的占用。以二级页表为例虚拟地址被分为DIR,PAGE和offset三部分通过顶级页表和DIR寻找到该二级页表的起始位置再通过二级页表的起始位置和PAGE找到页物理地址最后加上页偏移即可得到最终的物理地址。
42.简述快表
快表也称为页表高速缓存。其会存储一定数量的页表项以此加快虚拟地址到物理地址的映射速度。
43.简述MMU
MMU即内存管理单元该硬件负责处理虚拟地址到物理地址的转化工作。快表也存储在MMU上。
44.进程调度算法
先来先服务调度算法创建一个任务队列一旦有新任务就加入这个队列CPU完成一个任务后就从队列取任务。
短作业(进程)优先调度算法针对较短的作业优先调给CPU工作。
时间片轮转算法每个时间片依次执行一个任务时间片结束后将该任务放回任务队列。
多级反馈队列也按时间片轮转算法执行任务设置n个队列当第一个队列任务为空才执行第二个队列依次类推。如果在i队列的任务在该时间片执行后没有完成即插入i+1号队列
八、计算机网络
1.简述OSI七层协议
OSI七层协议包括物理层数据链路层网络层运输层会话层表示层 应用层
2.简述TCP/IP五层协议
TCP/IP五层协议包括物理层数据链路层网络层运输层应用层
3.物理层有什么作用
主要解决两台物理机之间的通信通过二进制比特流的传输来实现二进制数据表现为电流电压上的强弱到达目的地再转化为二进制机器码。网卡、集线器工作在这一层。
4.数据链路层有什么作用
在不可靠的物理介质上提供可靠的传输接收来自物理层的位流形式的数据并封装成帧传送到上一层同样也将来自上层的数据帧拆装为位流形式的数据转发到物理层。这一层在物理层提供的比特流的基础上通过差错控制、流量控制方法使有差错的物理线路变为无差错的数据链路。提供物理地址寻址功能。交换机工作在这一层。
5.网络层有什么作用
将网络地址翻译成对应的物理地址并决定如何将数据从发送方路由到接收方通过路由选择算法为分组通过通信子网选择最佳路径。路由器工作在这一层。
6.传输层有什么作用
传输层提供了进程间的逻辑通信传输层向高层用户屏蔽了下面网络层的核心细节使应用程序看起来像是在两个传输层实体之间有一条端到端的逻辑通信信道。
7.会话层有什么作用
建立会话身份验证权限鉴定等
保持会话对该会话进行维护在会话维持期间两者可以随时使用这条会话传输数据
断开会话当应用程序或应用层规定的超时时间到期后OSI会话层才会释放这条会话。
8.表示层有什么作用
对数据格式进行编译对收到或发出的数据根据应用层的特征进行处理如处理为文字、图片、音频、视频、文档等还可以对压缩文件进行解压缩、对加密文件进行解密等。
9.应用层有什么作用
提供应用层协议如HTTP协议FTP协议等等方便应用程序之间进行通信。
10.TCP与UDP区别
TCP作为面向流的协议提供可靠的、面向连接的运输服务并且提供点对点通信
UDP作为面向报文的协议不提供可靠交付并且不需要连接不仅仅对点对点也支持多播和广播
11.为何TCP可靠
TCP有三次握手建立连接四次挥手关闭连接的机制。
除此之外还有滑动窗口和拥塞控制算法。最最关键的是还保留超时重传的机制。
对于每份报文也存在校验保证每份报文可靠性。
12.为何UDP不可靠
UDP面向数据报无连接的数据报发出去就不保留数据备份了。
仅仅在IP数据报头部加入校验和复用。
UDP没有服务器和客户端的概念。
UDP报文过长的话是交给IP切成小段如果某段报废报文就废了。
13.简述TCP粘包现象
TCP是面向流协议发送的单位是字节流因此会将多个小尺寸数据被封装在一个tcp报文中发出去的可能性。
可以简单的理解成客户端调用了两次send服务器端一个recv就把信息都读出来了。
14.TCP粘包现象处理方法
固定发送信息长度或在两个信息之间加入分隔符。
15.简述TCP协议的滑动窗口
滑动窗口是传输层进行流量控制的一种措施接收方通过通告发送方自己的窗口大小从而控制发送方的发送速度防止发送方发送速度过快而导致自己被淹没。
16.简述TCP协议的拥塞控制
拥塞是指一个或者多个交换点的数据报超载TCP又会有重传机制导致过载。
为了防止拥塞窗口cwnd增长过大引起网络拥塞还需要设置一个慢开始门限ssthresh状态变量.
当cwnd < ssthresh 时使用慢开始算法。
当cwnd > ssthresh 时停止使用慢开始算法而改用拥塞避免算法。
当cwnd = ssthresh 时既可使用慢开始算法也可使用拥塞避免算法。
慢开始由小到大逐渐增加拥塞窗口的大小每接一次报文cwnd指数增加。
拥塞避免cwnd缓慢地增大即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1。
快恢复之前的策略发送方判断网络出现拥塞就把ssthresh设置为出现拥塞时发送方窗口值的一半继续执行慢开始之后进行拥塞避免。
快恢复发送方判断网络出现拥塞就把ssthresh设置为出现拥塞时发送方窗口值的一半并把cwnd设置为ssthresh的一半之后进行拥塞避免。
17.简述快重传
如果在超时重传定时器溢出之前接收到连续的三个重复冗余ACK发送端便知晓哪个报文段在传输过程中丢失了于是重发该报文段不需要等待超时重传定时器溢出再发送该报文。
18.TCP三次握手过程
- 第一次握手:客户端将标志位SYN置为1随机产生一个值序列号seq=x并将该数据包发送给服务端客户端进入syn_sent状态等待服务端确认。
- 第二次握手:服务端收到数据包后由标志位SYN=1知道客户端请求建立连接服务端将标志位SYN和ACK都置为1ack=x+1,随机产生一个值seq=y并将该数据包发送给客户端以确认连接请求服务端进入syn_rcvd状态。
- 第三次握手:客户端收到确认后检查,如果正确则将标志位ACK为1ack=y+1并将该数据包发送给服务端服务端进行检查如果正确则连接建立成功客户端和服务端进入established状态完成三次握手随后客户端和服务端之间可以开始传输数据了
19.为什么TCP握手需要三次两次行不行
不行。TCP进行可靠传输的关键就在于维护一个序列号三次握手的过程即是通信双方相互告知序列号起始值 并确认对方已经收到了序列号起始值。
如果只是两次握手 至多只有客户端的起始序列号能被确认 服务器端的序列号则得不到确认。
20.简述半连接队列
TCP握手中当服务器处于SYN_RCVD 状态服务器会把此种状态下请求连接放在一个队列里该队列称为半连接队列。
21.简述SYN攻击
SYN攻击即利用TCP协议缺陷通过发送大量的半连接请求占用半连接队列耗费CPU和内存资源。
优化方式
- 缩短SYN Timeout时间
- 记录IP若连续受到某个IP的重复SYN报文从这个IP地址来的包会被一概丢弃。
22.TCP四次挥手过程
- 第一次挥手客户端发送一个FIN用来关闭客户端到服务端的数据传送客户端进入fin_wait_1状态。
- 第二次挥手服务端收到FIN后发送一个ACK给客户端确认序号为收到序号+1服务端进入Close_wait状态。此时TCP连接处于半关闭状态即客户端已经没有要发送的数据了但服务端若发送数据则客户端仍要接收。
- 第三次挥手服务端发送一个FIN用来关闭服务端到客户端的数据传送服务端进入Last_ack状态。
- 第四次挥手客户端收到FIN后客户端进入Time_wait状态接着发送一个ACK给服务端确认后服务端进入Closed状态完成四次挥手。
23.为什么TCP挥手需要4次
主要原因是当服务端收到客户端的 FIN 数据包后服务端可能还有数据没发完不会立即close。
所以服务端会先将 ACK 发过去告诉客户端我收到你的断开请求了但请再给我一点时间这段时间用来发送剩下的数据报文发完之后再将 FIN 包发给客户端表示现在可以断了。之后客户端需要收到 FIN包后发送 ACK 确认断开信息给服务端。
24.为什么四次挥手释放连接时需要等待2MSL
MSL即报文最大生存时间。设置2MSL可以保证上一次连接的报文已经在网络中消失不会出现与新TCP连接报文冲突的情况。
25.简述DNS协议
DNS协议是基于UDP的应用层协议它的功能是根据用户输入的域名解析出该域名对应的IP地址从而给客户端进行访问。
26.简述DNS解析过程
- 客户机发出查询请求在本地计算机缓存查找若没有找到就会将请求发送给dns服务器
- 本地dns服务器会在自己的区域里面查找找到即根据此记录进行解析若没有找到就会在本地的缓存里面查找
- 本地服务器没有找到客户机查询的信息就会将此请求发送到根域名dns服务器
- 根域名服务器解析客户机请求的根域部分它把包含的下一级的dns服务器的地址返回到客户机的dns服务器地址
- 客户机的dns服务器根据返回的信息接着访问下一级的dns服务器
- 这样递归的方法一级一级接近查询的目标最后在有目标域名的服务器上面得到相应的IP信息
- 客户机的本地的dns服务器会将查询结果返回给我们的客户
27.简述HTTP协议
http协议是超文本传输协议。它是基于TCP协议的应用层传输协议即客户端和服务端进行数据传输的一种规则。该协议本身HTTP 是一种无状态的协议。
28.简述cookie
HTTP 协议本身是无状态的为了使其能处理更加复杂的逻辑HTTP/1.1 引入 Cookie 来保存状态信息。
Cookie是由服务端产生的再发送给客户端保存当客户端再次访问的时候服务器可根据cookie辨识客户端是哪个以此可以做个性化推送免账号密码登录等等。
29.简述session
session用于标记特定客户端信息存在在服务器的一个文件里。
一般客户端带Cookie对服务器进行访问可通过cookie中的session id从整个session中查询到服务器记录的关于客户端的信息。
30.简述http状态码和对应的信息
1XX接收的信息正在处理
2XX请求正常处理完毕
3XX重定向
4XX客户端错误
5XX服务端错误
常见错误码
301永久重定向
302临时重定向
304资源没修改用之前缓存就行
400客户端请求的报文有错误
403表示服务器禁止访问资源
404表示请求的资源在服务器上不存在或未找到
31.转发和重定向的区别
转发是服务器行为。服务器直接向目标地址访问URL,将相应内容读取之后发给浏览器用户浏览器地址栏URL不变转发页面和转发到的页面可以共享request里面的数据。
重定向是利用服务器返回的状态码来实现的如果服务器返回301或者302浏览器收到新的消息后自动跳转到新的网址重新请求资源。用户的地址栏url会发生改变而且不能共享数据。
32.简述http1.0
规定了请求头和请求尾响应头和响应尾get post
每一个请求都是一个单独的连接做不到连接的复用
33.简述http1.1的改进
HTTP1.1默认开启长连接在一个TCP连接上可以传送多个HTTP请求和响应。使用 TCP 长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。
支持管道pipeline网络传输只要第一个请求发出去了不必等其回来就可以发第二个请求出去可以减少整体的响应时间。
服务端无法主动push
34.简述HTTP短连接与长连接区别
HTTP中的长连接短连接指HTTP底层TCP的连接。
短连接 客户端与服务器进行一次HTTP连接操作就进行一次TCP连接连接结束TCP关闭连接。
长连接如果HTTP头部带有参数keep-alive即开启长连接网页完成打开后底层用于传输数据的TCP连接不会直接关闭会根据服务器设置的保持时间保持连接保持时间过后连接关闭。
35.简述http2.0的改进
提出多路复用。多路复用前文件是串行传输的请求a文件b文件只能等待并且连接数过多。引入多路复用a文件b文件可以同时传输。
引入了二进制数据帧。其中帧对数据进行顺序标识有了序列id服务器就可以进行并行传输数据。
36.http与https的区别
http所有传输的内容都是明文并且客户端和服务器端都无法验证对方的身份。
https具有安全性的ssl加密传输协议加密采用对称加密
https协议需要到ca申请证书一般免费证书很少需要交费。
37.简述TLS/SSL, HTTP, HTTPS的关系
SSL全称为Secure Sockets Layer即安全套接层其继任为TLSTransport Layer Security传输层安全协议均用于在传输层为数据通讯提供安全支持。
可以将HTTPS协议简单理解为HTTP协议+TLS/SSL
38.https的连接过程
- 浏览器将支持的加密算法信息发给服务器
- 服务器选择一套浏览器支持的加密算法以证书的形式回发给浏览器
- 客户端(SSL/TLS)解析证书验证证书合法性生成对称加密的密钥我们将该密钥称之为clientkey即客户端密钥用服务器的公钥对客户端密钥进行非对称加密。
- 客户端会发起HTTPS中的第二个HTTP请求将加密之后的客户端对称密钥发送给服务器
- 服务器接收到客户端发来的密文之后会用自己的私钥对其进行非对称解密解密之后的明文就是客户端密钥然后用客户端密钥对数据进行对称加密这样数据就变成了密文。
- 服务器将加密后的密文发送给客户端
- 客户端收到服务器发送来的密文用客户端密钥对其进行对称解密得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束整个HTTPS传输完成
39.Get与Post区别
Get指定资源请求数据刷新无害Get请求的数据会附加到URL中传输数据的大小受到url的限制。
Post向指定资源提交要被处理的数据。刷新会使数据会被重复提交。post在发送数据前会先将请求头发送给服务器进行确认然后才真正发送数据。
40.Get方法参数有大小限制吗
一般HTTP协议里并不限制参数大小限制。但一般由于get请求是直接附加到地址栏里面的由于浏览器地址栏有长度限制因此使GET请求在浏览器实现层面上看会有长度限制。
41.了解REST API吗
REST API全称为表述性状态转移Representational State TransferREST即利用HTTP中get、post、put、delete以及其他的HTTP方法构成REST中数据资源的增删改查操作
Create POST
Read GET
Update PUT/PATCH
Delete DELETE
42.浏览器中输入一个网址后具体发生了什么
- 进行DNS解析操作根据DNS解析的结果查到服务器IP地址
- 通过ip寻址和arp找到服务器并利用三次握手建立TCP连接
- 浏览器生成HTTP报文发送HTTP请求等待服务器响应
- 服务器处理请求并返回给浏览器
- 根据HTTP是否开启长连接进行TCP的挥手过程
- 浏览器根据收到的静态资源进行页面渲染
43.http请求包含了什么
包含请求方法字段、URL字段、HTTP协议版本
产生请求的浏览器类型请求数据主机地址。
44.Put与Delete区别
Put规定默认为更新某一资源和Post一样一般该操作会对服务器资源进行改变
Delete规定默认为删除某一资源和Post一样一般该操作会对服务器资源进行改变
45.简述DNS劫持
DNS是指将网页域名翻译为对应的IP的一种方法。DNS劫持指攻击者篡改结果使用户对域名的解析IP变成了另一个IP