网易游戏社招一面总结

  • 基本情况
  • 问题列表
    • Java部分
    • Python部分
    • Linux部分
    • 计算机网络部分
  • 参考资料

基本情况

面试岗位:Python游戏开发岗
面试方式:视频面试
面试官数量:2人
面试感觉:首先,开场之后,无需自我介绍,从简历开始问,这一点很有技术范儿。面试过程中,两位面试官交叉提问,并且在回答过程中根据技术点随时打断补充提问,很像是一个开发小组在就有个问题讨论。另外在回答问题过程中,可能有些问题回答不上来,面试官还能够给与一点提示,这一点感觉非常好。最后,虽然自己水平很差,但是能够有这样的交流,收获满满,也非常感谢两位年轻帅气的面试官。

问题列表

Java部分

  1. 在公司做了哪些项目?主要用到的哪些技术?自己的贡献是什么?
    答:结合实际情况回答,例如,Spring,SpringBoot,MyBatis,Redis, MySQL,Zabbix,Ubuntu等

  2. 使用的应用服务器是什么?它的启动流程,它是如何根据URL找到对应的处理逻辑?
    答:使用的是jetty。外部启动一个Jetty服务器的流程如下:
    1)java start.jar进行启动,解析命令行参数并读取start.ini中配置的所有参数;
    2)解析start.config确定jetty模块的类路径并确定首先执行的MainClass;
    3)可以选择是否另起一个进程来,如果不另起进程,则通过反射来调用MainClass,start.ini中配置的JVM参数不会生效;
    4)MainClass默认是XmlConfiguration,解析etc/jetty.xml,etc/jetty-deploy.xml等,创建实例并组装Server(是根据在start.ini中定义的顺序创建,而且顺序很重要,这里的IOC是jetty自己实现的),然后调用start()启动Server()。
    5)Server启动其他组件的顺序是:首先启动设置到Server的Handler,通常这个Handler会有很多子handler,这些handler将组成一个Handler链,Server会依次启动这个链上的所有Handler,接着会启动注册在Server上JMX的Mbean,让Mbean也一起工作,最后会启动Connector,打开端口,接受客户端请求。

补充知识:Jetty处理请求流程
Jetty接收到一个请求时,Jetty就把这个请求交给在Server中注册的而代理Handler去执行,如何执行注册的Handler同样由你规定,Jetty要做的就是调用你注册的第一个Handler的handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)方法,接下来要怎么做,完全由你决定。要能接收一个Web请求访问,首先要创建一个ContextHandler
当我们在浏览器中输http://localhost:8080时请求将会代理到Server类的handle方法,Server的handle方法将请求代理给ContextHandler的handle方法,ContextHandler又调用另一个Handler的handle方法。这个调用方式是和Servlet的工作方式类似,在启动之前初始化,创建对象后调用Servlet的service方法。在Servlet的API中我通常也只实现它的一个包装好的类,在Jetty中也是如此。虽然ContextHandler也只是一个handler,但是这个Handler通常由Jetty帮你实现,我们一般只要实现一些与具体要做的业务逻辑有关的Handler就好了,而一些流程性的或某些规范的Handler,我们直接用就好了。下图是请求Servlet的时序图。
Jetty处理请求的过程就是Handler链上handle方法的执行过程。这里需要解释的一点是ScopeHandler的处理规则,ServletContextHandler、SessionHandler和ServletHandler都继承了ScopeHandler,那么这三个类组成一个Handler链,他们的执行规则是ServletContextHandler.handler→ServletContextHandler.doScope→SessionHandler.doScope→ServletHandler.doScope→ServletContextHandler.doHandle→SessionHandler.doHandle→ServletHandler.doHandle,这种机制使得我们可以在duScope阶段做一些额外工作。

补充问题:Jetty和Tomcat的比较

  • 相同点:Tomcat和Jetty都是一种Servlet引擎,他们都支持标准的servlet规范和JavaEE的规范。
  • 不同点:
    • 架构比较

      • Jetty的架构比Tomcat的更为简单
      • Jetty的架构是基于Handler来实现的,主要的扩展功能都可以用Handler来实现,扩展简单。
      • Tomcat的架构是基于容器设计的,进行扩展是需要了解Tomcat的整体设计结构,不易扩展。
    • 性能比较
      • Jetty和Tomcat性能方面差异不大
      • Jetty可以同时处理大量连接而且可以长时间保持连接,适合于web聊天应用等等。
      • Jetty的架构简单,因此作为服务器,Jetty可以按需加载组件,减少不需要的组件,减少了服务器内存开销,从而提高服务器性能。
      • Jetty默认采用NIO结束在处理I/O请求上更占优势,在处理静态资源时,性能较高
      • 少数非常繁忙;Tomcat适合处理少数非常繁忙的链接,也就是说链接生命周期短的话,Tomcat的总体性能更高。Tomcat默认采用BIO处理I/O请求,在处理静态资源时,性能较差。
    • 其它比较
      • Jetty的应用更加快速,修改简单,对新的Servlet规范的支持较好。
      • Tomcat目前应用比较广泛,对JavaEE和Servlet的支持更加全面,很多特性会直接集成进来。
  1. 在Spring框架中web.xml的结构是什么?
    答:1)Spring框架解决字符串编码问题:过滤器 CharacterEncodingFilter(filter-name), 过滤器就是针对于每次浏览器请求进行过滤的
    2)在web.xml配置监听器ContextLoaderListener(listener-class),ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。 在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。
    3)部署applicationContext的xml文件:contextConfigLocation(context-param下的param-name)
    4)DispatcherServlet是前置控制器,配置在web.xml文件中的。拦截匹配的请求,Servlet拦截匹配规则要自已定义,把拦截下来的请求,依据某某规则分发到目标Controller来处理。
    5)DispatcherServlet(servlet-name、servlet-class、init-param、param-name(contextConfigLocation)、param-value) ,在DispatcherServlet的初始化过程中,框架会在web应用的 WEB-INF文件夹下寻找名为[servlet-name]-servlet.xml 的配置文件,生成文件中定义的bean。

  2. Java的集合类中List有哪几种实现?Arralist与Vector区别?Arraylist与LinkedList区别?以及它们的扩增方式。
    答:常见的实现有三种:ArrayList,LinkedList,Vector。另外还有AbstractList,AbstractSequentialList等。它们之间的区别如下:
    ArrayList:
    1)ArrayList底层通过数组实现,随着元素的增加而动态扩容。
    2)ArrayList是Java集合框架中使用最多的一个类,是一个数组队列,线程不安全集合。
    3)它继承于AbstractList,实现了List, RandomAccess, Cloneable, Serializable接口。①ArrayList实现List,得到了List集合框架基础功能;②ArrayList实现RandomAccess,获得了快速随机访问存储元素的功能,RandomAccess是一个标记接口,没有任何方法;③ArrayList实现Cloneable,得到了clone()方法,可以实现克隆功能;④ArrayList实现Serializable,表示可以被序列化,通过序列化去传输,典型的应用就是hessian协议。
    4)ArrayList的特点:容量不固定,随着容量的增加而动态扩容(阈值基本不会达到);有序集合(插入的顺序==输出的顺序);插入的元素可以为null;增删改查效率更高(相对于LinkedList来说);线程不安全
    ArrayList扩增源码如下:

    LinkedList
    1)LinkedList底层通过链表来实现,随着元素的增加不断向链表的后端增加节点。
    2)LinkedList是一个双向链表,每一个节点都拥有指向前后节点的引用。相比于ArrayList来说,LinkedList的随机访问效率更低。
    3)它继承AbstractSequentialList,实现了List, Deque, Cloneable, Serializable接口。①LinkedList实现List,得到了List集合框架基础功能;②LinkedList实现Deque,Deque 是一个双向队列,也就是既可以先入先出,又可以先入后出,说简单点就是既可以在头部添加元素,也可以在尾部添加元素;③LinkedList实现Cloneable,得到了clone()方法,可以实现克隆功能;④LinkedList实现Serializable,表示可以被序列化,通过序列化去传输,典型的应用就是hessian协议。
    Vector
    和ArrayList基本相似,利用数组及扩容实现List,但Vector是一种线程安全的List结构,它的读写效率不如ArrayList,其原因是在该实现类内在方法上加上了同步关键字。源码如下:

    其不同之处还在于Vector的增长速度不同:即


    Vector在默认情况下是以两倍速度递增,所以capacityIncrement可以用来设置递增速度,因此Vector的初始化多了一种方式,即设置数组增量。
    Arralist与Vector区别与联系
    1) ArrayList出现于jdk1.2,Vector出现于1.0。两者底层的数据存储都使用的Object数组实现,因为是数组实现,所以具有查找快(因为数组的每个元素的首地址是可以得到的,数组是0序的,所以: 被访问元素的首地址=首地址+元素类型字节数*下标 ),增删慢(因为往数组中间增删元素时,会导致后面所有元素地址的改变)的特点
    2)继承的类实现的接口都是一样的,都继承了AbstractList类(继承后可以使用迭代器遍历),实现了RandomAccess(标记接口,标明实现该接口的list支持快速随机访问),cloneable接口(标识接口,合法调用clone方法),serializable(序列化标识接口)
    3)当两者容量不够时,都会进行对Object数组的扩容,arraylist默认增长1.5倍;Vector可以自定义若不自定义,则增长2倍
    4)构造方法略有不同
    ①ArrayList的构造方法:
    ArrayList a1 = new ArrayList(int i); 指定初始化容量的构造方法
    ArrayList a2 = new ArrayList(); 默认构造方法,在添加第一个元素过程中初始化一个长度为10的Object数组
    ArrayList a3 = new ArrayList(Collection); 在构造方法中添加集合,本方法创建的集合的object数组长度等于实际元素个数
    ②Vector的构造方法:
    Vector v1 = new Vector(10,2); 指定初始长度(initialCapacity)与增长因子(capacityIncrement)注意这里的增长因子不是oldCapacity * capacityIncrement而是+,如果不指定或者指定为0,则默认扩容当前容量的两倍。
    Vector v2 = new Vector(10); 通过this关键字调用上面的构造方法,自定义初始数组长度,增长因子默认为0
    Vector v3 = new Vector(); 默认构造方法,在创建对象时便分配长度为10的Object数组
    5)线程的安全性不同,Vector是线程安全的,在Vector的大多数方法都使用synchronized关键字修饰,ArrayList是线程不安全的(可以通过Collections.synchronizedList()实现线程安全)
    6)性能上的差别,由于Vector的方法都有同步锁,在方法执行时需要加锁、解锁,所以在执行过程中效率会低于ArrayList,另外,性能上的差别还体现在底层的Object数组上,ArrayList多了一个transient关键字,这个关键字的作用是防止序列化,然后在ArrayList中重写了readObject和writeObject方法,这样是为了在传输时提高效率。
    ArrayList和LinkedList比较
    1)对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
    2)在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
    3)LinkedList不支持高效的随机元素访问
    4)ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间

  3. 什么是线程安全和非线程安全?
    答:线程安全就是在多线程环境下也不会出现数据不一致,而非线程安全就有可能出现数据不一致的情况。线程安全由于要确保数据的一致性,所以对资源的读写进行了控制,换句话说增加了系统开销。所以在单线程环境中效率比非线程安全的效率要低些,但是如果线程间数据相关,需要保证读写顺序,用线程安全模式。线程安全是通过线程同步控制来实现的,也就是synchronized关键字。

  4. Volatile关键字的作用?
    答:回答volatile关键之前,先说明一下与内存模型相关的概念和知识,然后分析了volatile关键字的实现原理,最后给出了几个使用volatile关键字的场景。
    内存模型:计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里面就有了高速缓存。也就是说,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中
    在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存。如果一个变量在多个CPU中都存在缓存(一般在多线程编程时才会出现),那么就可能存在缓存不一致的问题。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量
    为了解决缓存不一致性问题,通常来说有以下2种解决方法:
    1)通过在总线加LOCK #锁的方式 => 由于在锁住总线期间,其他CPU无法访问内存,导致效率低下
    2)通过缓存一致性协议 => 当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
    这2种方式都是硬件层面上提供的方式。
    并发编程的三个重要概念:原子性问题,可见性问题,有序性问题。
    1)原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
    2)可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
    3)有序性:即程序执行的顺序按照代码的先后顺序执行。这里存在指令重排序(Instruction Reorder)的问题。
    什么是指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性
    Java内存模型提供了哪些保证以及在java中提供了哪些方法和机制来让保证在进行多线程编程时程序能够正确执行
    1)原子性问题:在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。也就是说,只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
    2)可见性问题:对于可见性,Java提供了volatile关键字来保证可见性当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
    3)有序性问题:在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序
    深入剖析volatile关键字
    1)volatile关键字的两层语义:①保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。②禁止进行指令重排序。
    2)volatile保证原子性吗?volatile没办法保证对变量的操作的原子性,可以使用的其他方法有:synchronized,Lock,AtomicInteger
    3)volatile能保证有序性吗?volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性。这里有两层意思:①当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;②在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
    4)volatile的原理和实现机制:“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”。lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:①它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;②它会强制将对缓存的修改操作立即写入主存;③如果是写操作,它会导致其他CPU中对应的缓存行无效。
    使用volatile关键字的场景:通常来说,使用volatile必须具备以下2个条件:①对变量的写操作不依赖于当前值,②该变量没有包含在具有其他变量的不变式中。使用volatile的几个场景:状态标记量、double check。

  5. JDK1.8有哪些变化?
    答:Lambda表达式、函数式接口、*方法引用和构造器调用、Stream API、接口中的默认方法和静态方法、新时间日期API
    在jdk1.8中对hashMap等map集合的数据结构优化。hashMap数据结构的优化:原来的hashMap采用的数据结构是哈希表(数组+链表),hashMap默认大小是16,一个0-15索引的数组,如何往里面存储元素,首先调用元素的hashcode方法,计算出哈希码值,经过哈希算法算成数组的索引值,如果对应的索引处没有元素,直接存放,如果有对象在,那么比较它们的equals方法比较内容,如果内容一样,后一个value会将前一个value的值覆盖,如果不一样,在1.7的时候,后加的放在前面,形成一个链表,形成了碰撞,在某些情况下如果链表
    无限下去,那么效率极低,碰撞是避免不了的;加载因子:0.75,数组扩容,达到总容量的75%,就进行扩容,但是无法避免碰撞的情况发生。在1.8之后,在数组+链表+红黑树来实现hashmap,当碰撞的元素个数大于8时 & 总容量大于64,会有红黑树的引入,除了添加之后,效率都比链表高,1.8之后链表新进元素加到末尾;ConcurrentHashMap (锁分段机制),concurrentLevel,jdk1.8采用CAS算法(无锁算法,不再使用锁分段),数组+链表中也引入了红黑树的使用
    什么是函数式接口?简单来说就是只定义了一个抽象方法的接口(Object类的public方法除外),就是函数式接口,并且还提供了注解:@FunctionalInterface
    Stream操作的三个步骤:创建stream => 中间操作(过滤、map)=> 终止操作
    新的日期API: LocalDate | LocalTime | LocalDateTime
    JVM变化:在JDK1.8之后,堆的永久区取消了,由元空间取代;在JDK 1.7中使用的是堆内存模型

  6. 垃圾回收机制中,对新生代和老生代了解吗?原理是什么?用到了哪些算法?
    答:先了解以下JVM的内容管理,其中包括判断对象存活还是死亡的算法(引用计数算法、可达性分析算法),常见的垃圾收集算法(复制算法、分代收集算法等以及这些算法适用于什么代)以及常见的垃圾收集器的特点(这些收集器适用于什么年代的内存收集)。JVM运行时数据区由程序计数器、堆、虚拟机栈、本地方法栈、方法区部分组成JVM内存结构由程序计数器、堆、栈、本地方法栈、方法区等部分组成,结构图如下所示:

    ①程序计数器,也指pc寄存器:几乎不占有内存。用于取下一条执行的指令
    ②堆:所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和老生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成,(也指s0,s1)结构图如下所示:

    新生代:新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例。
    旧生代:用于存放新生代中经过多次垃圾回收仍然存活的对象。
    ③栈:每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。
    ④本地方法栈:用于支持native方法的执行,存储了每个native方法调用的状态
    ⑤方法区:存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用永久代(PermanetGeneration)来存放方法区,可通过-XX:PermSize和-XX:MaxPermSize来指定最小值和最大值。
    JVM的垃圾回收机制:JVM分别对新生代和旧生代采用不同的垃圾回收机制。
    1)新生代的GC:新生代通常存活时间较短,因此基于复制算法来进行回收,所谓复制算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和其中一个Survivor,复制到另一个之间Survivor空间中然后清理掉原来就是在Eden和其中一个Survivor中的对象。新生代采用空闲指针的方式来控制GC触发指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到 survivor,最后到老年代。在执行机制上JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)
    ①串行GC:在整个扫描和复制过程采用单线程的方式来进行,适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定
    ②并行回收GC:在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式,可用**-XX:+UseParallelGC来强制指定**,用**-XX:ParallelGCThreads=4来指定线程数**
    ③并行GC:与老生代的并发GC配合使用
    2)老生代的GC:老生代与新生代不同,对象存活的时间比较长,比较稳定,因此采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。在执行机制上JVM提供了串行 GC(SerialMSC)、并行GC(parallelMSC)和并发GC(CMS),具体算法细节还有待进一步深入研究。
    补充知识:JVM中堆的内存管理
    Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定),默认的,Edem : from : to = 8 :1 : 1 ( 可以通过参数–XX:SurvivorRatio 来设定 ), JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块Survivor。 因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
    GC堆: Java 中的堆也是 GC 收集垃圾的主要区域。GC 分为两种:Minor GC、FullGC ( 或称为 Major GC )
    1)Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法
    2)Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法
    区域是空闲着的。
    下面只列举其中的几个常用和容易掌握的配置选项
    -Xms:初始堆大小。如:-Xms256m
    -Xmx:最大堆大小。如:-Xmx512m
    -Xmn:新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%
    -Xss:JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。
    -XX:NewRatio:新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3
    -XX:SurvivorRatio:新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10
    -XX:PermSize:永久代(方法区)的初始大小
    -XX:MaxPermSize:永久代(方法区)的最大值
    -XX:+PrintGCDetails:打印 GC 信息
    -XX:+HeapDumpOnOutOfMemoryError:让虚拟机在发生内存溢出时 Dump 出当前的内存堆转储快照,以便分析用

  7. 如果有一个环形引用,如何对其进行垃圾回收?
    答:1. 如何判定对象为垃圾对象:引用计数法,可达性分析法;2.如何回收:回收策略(标记-清除算法,复制算法,标记-整理算法,分带收集算法),垃圾回收器(serial,parnew,Cms,G1);3. 何时回收
    判定对象为垃圾的方法
    1)引用计数法:在对象中添加一个引用计数器,当有地方引用这个对象的时候,引用技术器得值就+1,当引用失效的时候,计数器得值就-1。算法缺点:当某个引用被收集时,下个引用并不会清0,因此不被回收造成内存泄露。
    2)可达性分析法:可达性分析法就是从GCroot结点开始,看能否找到对象。GCroot结点开始向下搜索,路径称为引用链,当对象没有任何一条引用链链接的时候,就认为这个对象是垃圾,并进行回收。那么什么是GCroot呢(虚拟机在哪查找GCroot):①虚拟机栈(局部变量表),②方法区的类属性所引用的对象,③方法区中常量所引用的对象,④本地方法栈中引用的对象。目前主流JVM采用的垃圾判定算法就是可达性分析法。
    垃圾回收算法
    1)标记清除算法。存在的问题:效率问题;内存小块过多。
    2)复制算法。将Eden中需要回收的对象放到Survivor,然后清除。也就是两个Survivor中进行复制与清除。这里我们即提高了效率,又减少了内存分配。如果Survivor不够放,那就扔到老年代里,或者其他方法,有内存作担保。复制算法主要针对新生代内存收集方法。
    3)标记整理算法:标记-整理算法主要针对的是老年代内存收集方法。主要步骤:标记-整理-清除
    4)分代收集算法:分代收集算法是根据内存的分代选择不同的算法。对于新生代,一般选择复制算法。对于老年代,一般选择标记-整理-清除算法。
    垃圾回收器
    1)Serial收集器。特点:出现的最早的,发展最悠久的垃圾收集器;单线程垃圾收集器;主要针对新生代内存进行收集;缺点:慢。用处:在客户端上运行还是比较有效;没有线程的开销,所以在客户端还是比较好用的。
    2)ParNew收集器。特点:由单线程变成了多线程垃圾收集器;如果要用CMS进行收集的话,最好采用ParNew收集器。实现原理都是复制算法。缺点:性能较慢。
    3)Parallel Scavenge 收集器。主用算法:复制算法(新生代收集器);吞吐量 = (执行用户代码消耗的时间)/(执行用户代码的时间)+ 垃圾回收时所占用的时间;优点:吞吐量优化(CPU用于运行用户代码的时间与CPU消耗的总时间的比值)
    关于控制吞吐量的参数如下:
    ① -XX:MaxGCPauseMills #垃圾收集器的停顿时间
    ② -XX:GCTimeRatio #吞吐量大小
    当停顿时间过小时,内存对应变小,回收的频率增大。因此第一个参数需要设置的合理才比较好。第二个参数值越大,吞吐量越大,默认是99,(垃圾回收时间最多只能占到1%)
    4)CMS收集器(Concurrent Mark Sweep)。采用算法:标记清除算法。
    工作过程:初始标记(可达性分析法)->并发标记->重新标记(为了修正并发期间,因对象重新运作而修正)->并发清理(直接清除了)
    优点:并发收集,低停顿
    缺点:占用大量的CPU资源,无法处理浮动垃圾,出现ConcurrentMode Failure,空间碎片
    CMS是一个并发的收集器。目标是:减少延迟,增加响应速度。总的来说:客户端可用,服务端最好不用。
    5)G1收集器(面向服务端)。优势:集中了前面所有收集器的优点;G1能充分利用了多核的并行特点,能缩短停顿时间;分代收集(分成各种Region);空间整合(类似于标记清理算法);可预测的停顿()。
    步骤:初始标记->并发标记->最终标记->筛选回收
    在目前发布的Java8中,默认的虚拟机使用的是HotSpot(另一种是JRockit),对应的垃圾回收机制也就是HotSpot的GC机制;而JVM HotSpot使用的就是可达性分析法,即根搜索算法

  8. 序列化和反序列化
    答:1)序列化:把对象转换为字节序列存储于磁盘或者进行网络传输的过程称为对象的序列化。对象序列化过程可以分为两步:第一: 将对象转换为字节数组;第二: 将字节数组存储到磁盘
    2)反序列化:把磁盘或网络节点上的字节序列恢复到内存中的对象的过程称为对象的反序列化。可以是文件中的,也可以是网络传输过来的。
    3)对象的序列化和反序列化主要就是使用ObjectOutputStream 和 ObjectInputStream

Python部分

  1. 介绍Python的项目经验
    答:结合实际情况回答

  2. Python的多线程与多进程
    答:1)什么是线程:线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务一个线程是一个execution context(执行上下文),即一个cpu执行时所需要的一串指令
    2)什么是进程:一个程序的执行实例就是一个进程。每一个进程提供执行程序所需的所有资源(进程本质上是资源的集合);一个进程有一个虚拟的地址空间、可执行的代码、操作系统的接口、安全的上下文(记录启动该进程的用户和权限等等)、唯一的进程ID、环境变量、优先级类、最小和最大的工作空间(内存空间),还要有至少一个线程;每一个进程启动时都会最先产生一个线程,即主线程,然后主线程会再创建其他的子线程
    3)进程与线程的区别:
    ①同一个进程中的线程共享同一内存空间,但是进程之间是独立的
    ②同一个进程中的所有线程的数据是共享的(进程通讯),进程之间的数据是独立的
    ③对主线程的修改可能会影响其他线程的行为,但是父进程的修改(除了删除以外)不会影响其他子进程
    线程是一个上下文的执行指令,而进程则是与运算相关的一簇资源。
    同一个进程的线程之间可以直接通信,但是进程之间的交流需要借助中间代理来实现
    创建新的线程很容易,但是创建新的进程需要对父进程做一次复制
    一个线程可以操作同一进程的其他线程,但是进程只能操作其子进程
    线程启动速度快进程启动速度慢(但是两者运行速度没有可比性)。
    4)Python的多线程
    ①线程常用方法:
    start():线程准备就绪,等待CPU调度
    setName():为线程设置名称
    getName():获取线程名称
    setDaemon(True):设置为守护线程
    join():逐个执行每个线程,执行完毕后继续往下执行
    run():线程被CPU调度后自动执行线程对象的run方法,如果想自定义线程类,直接重写run方法就行了
    ②创建方式:
    普通创建方式:import threading
    继承threading.Thread来自定义线程类,其本质是重构Thread类中的run方法
    ③GIL:在python中,无论有多少核,同时只能执行一个线程,这是由于GIL的存在导致的。GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以它不能直接操作cpu,只能利用GIL保证同一时间只能有一个线程拿到数据。而在pypy和jpython中是没有GIL的。
    Python多线程的工作过程
    a)拿到公共数据
    b)申请gil
    c)python解释器调用os原生线程
    d)os操作cpu执行运算
    e)当线程执行时间到后,无论运算是否已经执行完,gil都被要求释放
    f)进而由其他线程重复上面的过程
    g)等其他线程执行完后,又会切换到之前的线程(从它记录的上下文继续执行),整个过程是每个线程执行自己的运算,当执行时间到就进行切换(context switch)。
    ④线程锁:由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,可以定义多个锁。
    ⑤事件(Event类):python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下几个方法:
    clear:将flag设置为“False”
    set:将flag设置为“True"
    is_set:判断是否设置了flag
    wait:会一直监听flag,如果没有检测到flag就一直处于阻塞状态
    事件处理的机制:全局定义了一个“Flag”,当flag值为“False”,那么event.wait()就会阻塞,当flag值为“True”,那么event.wait()便不再阻塞。
    5)Python的多进程
    在linux中,每个进程都是由父进程提供的。每启动一个子进程就从父进程克隆一份数据,但是进程之间的数据本身是不能共享的。python中使用的类库为multiprocessing。
    进程间通信:由于进程之间数据是不共享的,所以不会出现多线程GIL带来的问题。多进程之间的通信通过Queue()或Pipe()来实现
    Queue():使用方法跟threading里的queue差不多
    Pipe():Pipe的本质是进程之间的数据传递,而不是数据共享,这和socket有点像。pipe()返回两个连接对象分别表示管道的两端,每端都有send()和recv()方法。如果两个进程试图在同一时间的同一端进行读取和写入那么,这可能会损坏管道中的数据。
    Manager:通过Manager可实现进程间数据的共享。Manager()返回的manager对象会通过一个服务进程,来使其他进程通过代理的方式操作python对象。manager对象支持 list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value ,Array。
    进程锁(进程同步):数据输出的时候保证不同进程的输出内容在同一块屏幕正常显示,防止数据乱序的情况。
    进程池:由于进程启动的开销比较大,使用多进程的时候会导致大量内存空间被消耗。为了防止这种情况发生可以使用进程池,(由于启动线程的开销比较小,所以不需要线程池这种概念,多线程只会频繁得切换cpu导致系统变慢,并不会占用过多的内存空间)。进程池中常用方法:
    apply() :同步执行(串行)
    apply_async() :异步执行(并行)
    terminate() :立刻关闭进程池
    join() :主进程等待所有子进程执行完毕。必须在close或terminate()之后。
    close() :等待所有进程结束后,才关闭进程池。
    进程池内部维护一个进程序列,当使用时,去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止

  3. 是否了解Python的协程?
    答:线程和进程的操作是由程序触发系统接口,最后的执行者是系统,它本质上是操作系统提供的功能。而协程的操作则是程序员指定的,在python中通过yield,人为的实现并发处理
    协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时。协程,则只使用一个线程,分解一个线程成为多个“微线程”,在一个线程中规定某个代码块的执行顺序
    协程的适用场景:当程序中存在大量不需要CPU的操作时(IO)
    常用第三方模块:gevent和greenlet。(本质上,gevent是对greenlet的高级封装,因此一般用它就行,这是一个相当高效的模块。)
    ①gevent:通过joinall将任务f和它的参数进行统一调度,实现单线程中的协程。代码封装层次很高,实际使用只需要了解它的几个主要方法即可。
    ②greenlet:greenlet就是通过switch方法在不同的任务之间进行切换

  4. Python的装饰器的用法和原理
    答:Python中的装饰器是通过利用了函数的特定的闭包实现的,所以我们需要了解Python闭包的原理,以及函数的功能特性。
    函数特性:①函数作为变量传递;②函数作为参数传递;③函数作为返回值;④函数嵌套及跨域访问。
    闭包原理:闭包其实就是在一个函数中嵌套另一个函数的定义。闭包的作用:包括了外部函数的局部变量,这些局部变量在外部函数返回后也继续存在,并能被内部函数引用。
    装饰器的功能:闭包的另一种表现形式,主要用于装饰功能,在不改变原代码以及原代码的调用方式,另外的添加额外的功能,就是对已经存在的某些类进行装饰,以此来扩展或者说增强函数的一些功能。
    装饰器的用法:本质上是一个函数,只不过这个函数需要遵循以下规则:①入参只能有一个,类型为函数。 被装饰的函数将入会被传入这个参数;②返回值是必须是一个函数, 届时被调用的时候实际上调用的是返回出来的这个函数,所以返回的函数入参通常是:

(*args, **kwargs):

以满足所有函数需要,之后通过@语法糖即可装饰到任意函数上。示例代码如下:

# 不带参数的装饰器
def pre_do_deco(func):def wrapper(*args, **kwargs):print("Do something before call one")func(*args, **kwargs)return wrapper@pre_do_deco
def echo(msg):print(msg)echo("Hello world")

上面实际调用的是wrapper(“Hello World”) --> echo(“Hello World”)
带参数的装饰器的例子(参数控制的是装饰器的行为):只需要写一个返回,装饰器(入参只有一个,返回值是一个函数)函数的函数,同样也能利用@语法糖,代码如下:

# -*- coding: utf-8 -*-
# @Time    : 5/16/2020 4:46 PM
# @Author  : Xinzhe
# @File    : Decorator2.py
# @Software: PyCharm
# 带参数的装饰器
def pre_do_sth(msg):def decorator(func):def wrapper(*args, **kwargs):print("Do something before call two, print:%s" % msg)func(*args, **kwargs)return wrapperreturn decorator@pre_do_sth("Foo")
def echo(msg):print(msg)echo("Hello World")

这里需要注意的是语法糖中的(”Foo“)不能忽略。实际上@后面并不是对pre_do_sth这个函数生效,而是对pre_do_sth的返回值生效。
对于多个装饰器的调用顺序而言,先声明的装饰器先执行,即在最外层:

# -*- coding: utf-8 -*-
# @Time    : 5/16/2020 4:46 PM
# @Author  : Xinzhe
# @File    : Decorator2.py
# @Software: PyCharm
# 不带参数的装饰器
def pre_do_deco(func):def wrapper(*args, **kwargs):print("Do something before call one")func(*args, **kwargs)return wrapper# 带参数的装饰器
def pre_do_deco_2(msg):def decorator(func):def wrapper(*args, **kwargs):print("Do something before call two, print:%s" % msg)func(*args, **kwargs)return wrapperreturn decorator@pre_do_deco
@pre_do_deco_2("Foo")
def echo(msg):print(msg)echo("Hello World")

Linux部分

  1. 如何查看当前系统CPU使用率最高的线程和进程?
    答: 进程查看的命令是ps和top。进程调度的命令有at,crontab,batch,kill。
    (gdb)info threads 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID。 前面有*的是当前调试的线程。
    (gdb)thread ID 切换当前调试的线程为指定ID的线程。
    (gdb)thread apply ID1 ID2 command 让一个或者多个线程执行GDB命令command。
    (gdb)thread apply all command 让所有被调试线程执行GDB命令command。
    (gdb)set scheduler-locking off|on|step 估计是实际使用过多线程调试的人都可以发现,在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。 off 不锁定任何线程,也就是所有线程都执行,这是默认值。 on 只有当前被调试程序会执行。 step 在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。
    (gdb) bt 察看所有的调用栈
    (gdb) f 3 调用框层次
    (gdb) i locals 显示所有当前调用栈的所有变量

  2. awk实现一条命令将某一个目录里的所有文件分别进行备份,后缀为.bak
    答:命令如下:

# ls
demo1.txt  demo2.txt  demo3.txt
# ls | awk '{print "cp",$1,$1".bak"|"/bin/bash"}'
# ls
demo1.txt  demo1.txt.bak  demo2.txt  demo2.txt.bak  demo3.txt  demo3.txt.bak
  1. 简述进程的启动、终止的方式以及如何进行进程的查看.
    答:在Linux中启动一个进程有手工启动和调度启动两种方式:
    (1)手工启动:用户在输入端发出命令,直接启动一个进程的启动方式可以分为:①前台启动:直接在SHELL中输入命令进行启动。②后台启动:启动一个目前并不紧急的进程,如打印进程.
    (2)调度启动:系统管理员根据系统资源和进程占用资源的情况,事先进行调度安排,指定任务运行的时间和场合,到时候系统会自动完成该任务。经常使用的进程调度命令为:at、batch、crontab。

计算机网络部分

  1. 网络的7层协议
  • OSI分层 (7层):物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
  • TCP/IP分层(4层):网络接口层、 网际层、运输层、 应用层。
  • 五层协议 (5层):物理层、数据链路层、网络层、运输层、 应用层。
    补充知识:每一层的协议如下:
  • 物理层:RJ45、CLOCK、IEEE802.3 (中继器,集线器,网关)
  • 数据链路:PPP、FR、HDLC、VLAN、MAC (网桥,交换机)
  • 网络层:IP、ICMP、ARP、RARP、OSPF、IPX、RIP、IGRP、 (路由器)
  • 传输层:TCP、UDP、SPX
  • 会话层:NFS、SQL、NETBIOS、RPC
  • 表示层:JPEG、MPEG、ASII
  • 应用层:FTP、DNS、Telnet、SMTP、HTTP、WWW、NFS
  1. TCP的4次分手过程。如果大量出现close_wait状态,可能是什么原因?
    答:1)Client发起断开连接,给Server发送FIN,进入FIN_WAIT1状态,表示Client想主动断开连接;2)Server接受到FIN字段后,会继续发送数据给Client端,并发送ACK给Client端,表明自己知道了,但是还没有准备好断开,请等我的消息;3)当Server确定自己的数据已经发送完成,就发送FIN到Client;4)Client接受到来自Server的FIN,发送ACK给Server端,表示可以断开连接了,再等待2MSL,没有收到Server端的数据后,表示可以正常断开连接。如下图所示:
    补充知识:TCP的三次握手
    补充问题:**为什么TIME_WAIT状态还需要等2
    MSL(Max SegmentLifetime,最大分段生存期)秒之后才能返回到CLOSED状态呢?**
    答:因为虽然双方都同意关闭连接了,而且握手的4个报文也都发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SENT状态到ESTABLISH状态那样),但是我们必须假想网络是不可靠的,你无法保证你最后发送的ACK报文一定会被对方收到,就是说对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文
    补充问题:为什么要4次挥手?
    答:TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议,是一个全双工模式:
    1、当主机A确认发送完数据且知道B已经接受完了,想要关闭发送数据口(当然确认信号还是可以发),就会发FIN给主机B。
    2、主机B收到A发送的FIN,表示收到了,就会发送ACK回复。
    3、但这是B可能还在发送数据,没有想要关闭数据口的意思,所以FIN与ACK不是同时发送的,而是等到B数据发送完了,才会发送FIN给主机A。
    4、A收到B发来的FIN,知道B的数据也发送完了,回复ACK, A等待2MSL以后,没有收到B传来的任何消息,知道B已经收到自己的ACK了,A就关闭链接,B也关闭链接了。
    确保数据能够完成传输。
    补充问题:如果已经建立了连接,但是客户端突然出现故障了怎么办?
    答:TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

  2. 浏览器和服务器之间如何建立连接?
    答:这里分为两种情况:一种是HTTP连接,一种是HTTPs连接。
    1)浏览器和服务器之间建立HTTP连接的过程: HTTP通信机制是在一次完整的HTTP通信过程中,Web浏览器与Web服务器之间将完成下列7个步骤:
    建立TCP连接:在HTTP工作开始之前,Web浏览器首先要通过网络与Web服务器建立连接,该连接是通过TCP来完成的,该协议与IP协议共同构建Internet, 即著名的TCP/IP协议族,因此Internet又被称作是TCP/IP网络。HTTP是比TCP更高层次的应用层协议,根据规则,只有低层协议建立之 后才能,才能进行更层协议的连接,因此,首先要建立TCP连接,一般TCP连接的端口号是80。
    Web浏览器向Web服务器发送请求命令:一旦建立了TCP连接,Web浏览器就会向Web服务器发送请求命令。
    Web浏览器发送请求头信息:浏览器发送其请求命令之后,还要以头信息的形式向Web服务器发送一些别的信息,之后浏览器发送了一空白行来通知服务器,它已经结束了该头信息的发送。
    Web服务器应答:客户机向服务器发出请求后,服务器会客户机回送应答, HTTP/1.1 200 OK ,应答的第一部分是协议的版本号和应答状态码。
    Web服务器发送应答头信息:正如客户端会随同请求发送关于自身的信息一样,服务器也会随同应答向用户发送关于它自己的数据及被请求的文档。
    Web服务器向浏览器发送数据:Web服务器向浏览器发送头信息后,它会发送一个空白行来表示头信息的发送到此为结束,接着,它就以Content-Type应答头信息所描述的格式发送用户所请求的实际数据。
    ⑦Web服务器关闭TCP连接:一般情况下,一旦Web服务器向浏览器发送了请求数据,它就要关闭TCP连接,然后如果浏览器或者服务器在其头信息加入了这行代码:
      Connection:keep-alive
    TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。
    2)浏览器和服务器之间建立HTTPs连接:HTTPS是在HTTP的基础上和ssl/tls证书结合起来的一种协议,保证了传输过程中的安全性,减少了被恶意劫持的可能.很好的解决了解决了http的三个缺点(被监听、被篡改、被伪装)。具体过程如下:

    ①在使用HTTPS是需要保证服务端配置正确了对应的安全证书
    ②客户端发送请求到服务端
    ③服务端返回公钥和证书到客户端
    ④客户端接收后会验证证书的安全性,如果通过则会随机生成一个随机数,用公钥对其加密,发送到服务端
    ⑤服务端接受到这个加密后的随机数后会用私钥对其解密得到真正的随机数,随后用这个随机数当做私钥对需要发送的数据进行对称加密
    ⑥客户端在接收到加密后的数据使用私钥(即生成的随机值)对数据进行解密并且解析数据呈现结果给客户
    ⑦SSL加密建立

补充知识:HTTP的长连接和短连接?
HTTP的长连接和短连接本质上是TCP长连接和短连接。HTTP属于应用层协议.

  • 短连接:浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。
  • 长连接:当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接要客户端和服务端都支持长连接。
  • TCP短连接: client向server发起连接请求,server接到请求,然后双方建立连接。client向server发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起 close操作。短连接一般只会在 client/server间传递一次读写操作
  • TCP长连接: client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。
  1. IO中同步与异步,阻塞与非阻塞区别
    答:同步和异步关注的是消息通信机制 (synchronous communication/asynchronous communication)。所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用
    阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。非阻塞不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

  2. DNS进行域名解析的过程.
    答:客户端发出DNS请求翻译IP地址或主机名,DNS服务器在收到客户机的请求后:
    (1)检查DNS服务器的缓存,若查到请求的地址或名字,即向客户机发出应答信息;
    (2)若没有查到,则在数据库中查找,若查到请求的地址或名字,即向客户机发出应答信息;
    (3)若没有查到,则将请求发给根域DNS服务器,并依序从根域查找顶级域,由顶级查找二级域,二级域查找三级,直至找到要解析的地址或名字,即向客户机所在网络的DNS服务器发出应答信息,DNS服务器收到应答后先在缓存中存储,然后将解析结果发给客户机;
    (4)若没有找到,则返回错误信息。

  3. 在浏览器中输入www.163.com后执行的全部过程
    答:1、客户端浏览器通过DNS解析到www.163.com的IP地址210.11.27.79,通过这个IP地址找到客户端到服务器的路径。客户端浏览器发起一个HTTP会话到210.11.27.79,然后通过TCP进行封装数据包,输入到网络层。
    2、在客户端的传输层,把HTTP会话请求分成报文段,添加源和目的端口,如服务器使用80端口监听客户端的请求,客户端由系统随机选择一个端口如5000,与服务器进行交换,服务器把相应的请求返回给客户端的5000端口。然后使用IP层的IP地址查找目的端。
    3、客户端的网络层不用关心应用层或者传输层的东西,主要做的是通过查找路由表确定如何到达服务器,期间可能经过多个路由器,这些都是由路由器来完成的工作,不作过多的描述,无非就是通过查找路由表决定通过那个路径到达服务器。
    4、客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定IP地址的MAC地址,然后发送ARP请求查找目的地址,如果得到回应后就可以使用ARP的请求应答交换的IP数据包现在就可以传输了,然后发送IP数据包到达服务器的地址。

参考资料

  • https://blog.csdn.net/diaopai5230/article/details/101210745
  • https://blog.csdn.net/u010843421/article/details/82026427
  • https://blog.csdn.net/shantf93/article/details/79775702
  • https://blog.csdn.net/lqglqglqg/article/details/48293141
  • https://blog.csdn.net/qq_37113604/article/details/80836025
  • https://www.cnblogs.com/Ferda/p/10833863.html
  • https://www.cnblogs.com/dolphin0520/p/3920373.html
  • https://www.cnblogs.com/godoforange/p/11552865.html
  • https://www.cnblogs.com/whatisfantasy/p/6440585.html

【面试】网易游戏社招一面总结相关推荐

  1. 【编程题】网易游戏社招编程题题解

    网易游戏社招编程题题解 题目1:地形最低值的和 题目2:最少花的钱数 题目3:粉刷墙壁 题目4:被n到达的数 题目1:地形最低值的和 题目描述:在一个 n × m n \times m n×m的地图中 ...

  2. 一年经验大数据开发网易游戏社招面经(已拿offer)

    先说一下背景,我是19年毕业的一所普通211的本科生,在某国企工作一年后,感觉技术没有什么进步空间,就想跳槽了.不得不说今年行情确实很差,加上我工作年限太短了,投了一个多月的简历才拿到一个网易游戏的面 ...

  3. 2018年网易游戏春招笔试题

    2018年网易游戏春招笔试题(2018.04.16) 题目一 生产线问题 题目描述 作为一条生产线的负责人,你手头上有N个订单,你需要通过这条生产线去处理这些订单.每个订单可以用两个属性去描述:耗时p ...

  4. 面试归来——梳理社招面试以及浅述对程序员职业生涯的看法

    原谅我是一个后知后觉的人,已经在新的岗位工作了两个月,才写这篇文章. 本文会先讲述博主一个月的面试经历,梳理一下技术面试,浅述关于程序员职业生涯的一些看法. 从创业到再就业 大概4个月以前,终止创业已 ...

  5. 【面试招聘】社招如何拿到心仪公司的offer

    作者:被包养的程序猿,360广告算法工程师,负责排序相关工程和算法研究. 最近一段时间更新的比较慢,也是因为最近这边工作上也有一些变动,目前大致也确定了下来,也是打算做一个总结,希望能帮助到同样在社招 ...

  6. 2020年网易unity社招面试经历

    文章目录 废话 网易一面 网易二面 网易三面 废话 总的来说,网易的一面难度高一些,二面问的都是项目,相对容易回答,一面会尽量问基础,问底层,基础知识不好的话很容易挂在一面.三面自求多福... 网易一 ...

  7. 网易 java社招面试_一个妹子网易Java岗社招面试经历分享

    博客群发软件--用 Windows Live Writer完美发布新浪网易blogcnbl 博客群发软件--用 Windows Live Writer完美发布新浪.网易.blogcn.blogbus. ...

  8. 网易 java社招面试_《最新面经》—网易Java岗社招面试经历分享

    (面试题+答案领取方式:关注公众号回复[666]) 网易面试题 1. 面向对象的特点有哪些? 2. 列举几个java常用的package及其作用 3. 接口和抽象类有什么联系和区别 4. 重载和重写有 ...

  9. 网易 java社招面试_十月底—最新网易Java岗社招面试经历分享

    image (面试题+答案领取方式:关注公众号白楠楠获取) 网易面试题 面向对象的特点有哪些? 列举几个java常用的package及其作用 接口和抽象类有什么联系和区别 重载和重写有什么区别 jav ...

  10. 网易Java社招面试

    •写一些类似行测里面的看图选规则之类的题目 •查找出目前正在运行的TCP/UDP服务 •查找出文件test中的所有含"apa"的单词,并保存到文件testc中 •List.Vect ...

最新文章

  1. 二叉树的遍历(递归与非递归)
  2. 【转载】无需图片,使用CSS3实现圆角按钮
  3. 设计模式之模板模式学习笔记
  4. 数据中心机房建设几个重要的国家标准
  5. Alwayson--问题总结二
  6. 【已解决】applicationContext.xml cannot be opened because it does not exist
  7. android jni示例_Android TextInputLayout示例
  8. 深入理解JS中和||
  9. serv-u+win2008R2+cutFtp配置
  10. 如何将OFD文件转成Word?教你2种实用方法
  11. [数据库]MySQL表分区
  12. manjaro Linux 手动安装mysql 5.7 每次重启系统都无法直接启动
  13. opencv 识别黄色物体的程序代码
  14. 11G新特性 -- archival(long-term)backups
  15. MATLAB根据正态分布样本计算概率密度函数,生成服从样本正态分布的随机数
  16. Linux忘记密码的解决方法
  17. 智能信息检索课程设计
  18. 租一个月的云服务器要花费多少?
  19. dw网页设计期末设计一个网页_Dw网页设计制作九个小技巧,你都会吗?
  20. remote: Repository not found. fatal: repository 'https://github.com/***/***.git/' not found

热门文章

  1. Linux解决忘记密码的方法
  2. Word多级标题测试
  3. Quartus安装破解失败试了一些方法汇总
  4. 网上书店系统需求分析说明书
  5. 管理观察杂志 管理观察
  6. java数独最快解,Java解数独--世界最难数独
  7. 软件项目的全生命周期
  8. 联想服务器没有安装iis组件,win10没有iis怎么安装_手把手教你安装iis的详细步骤...
  9. MAC设置JDK环境变量
  10. sqlplus连接mysql_sqlplus连接的三种方式