【面试】网易游戏社招一面总结
網易游戲社招一面總結
- 基本情況
- 問題列表
- Java部分
- Python部分
- Linux部分
- 計算機網絡部分
- 參考資料
基本情況
面試崗位:Python游戲開發崗
面試方式:視頻面試
面試官數量:2人
面試感覺:首先,開場之后,無需自我介紹,從簡歷開始問,這一點很有技術范兒。面試過程中,兩位面試官交叉提問,并且在回答過程中根據技術點隨時打斷補充提問,很像是一個開發小組在就有個問題討論。另外在回答問題過程中,可能有些問題回答不上來,面試官還能夠給與一點提示,這一點感覺非常好。最后,雖然自己水平很差,但是能夠有這樣的交流,收獲滿滿,也非常感謝兩位年輕帥氣的面試官。
問題列表
Java部分
在公司做了哪些項目?主要用到的哪些技術?自己的貢獻是什么?
答:結合實際情況回答,例如,Spring,SpringBoot,MyBatis,Redis, MySQL,Zabbix,Ubuntu等
使用的應用服務器是什么?它的啟動流程,它是如何根據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的支持更加全面,很多特性會直接集成進來。
- 架構比較
在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。
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的空間花費則體現在它的每一個元素都需要消耗相當的空間
什么是線程安全和非線程安全?
答:線程安全就是在多線程環境下也不會出現數據不一致,而非線程安全就有可能出現數據不一致的情況。線程安全由于要確保數據的一致性,所以對資源的讀寫進行了控制,換句話說增加了系統開銷。所以在單線程環境中效率比非線程安全的效率要低些,但是如果線程間數據相關,需要保證讀寫順序,用線程安全模式。線程安全是通過線程同步控制來實現的,也就是synchronized關鍵字。
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。
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中使用的是堆內存模型
垃圾回收機制中,對新生代和老生代了解嗎?原理是什么?用到了哪些算法?
答:先了解以下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 出當前的內存堆轉儲快照,以便分析用
如果有一個環形引用,如何對其進行垃圾回收?
答: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使用的就是可達性分析法,即根搜索算法;
序列化和反序列化
答:1)序列化:把對象轉換為字節序列存儲于磁盤或者進行網絡傳輸的過程稱為對象的序列化。對象序列化過程可以分為兩步:第一: 將對象轉換為字節數組;第二: 將字節數組存儲到磁盤
2)反序列化:把磁盤或網絡節點上的字節序列恢復到內存中的對象的過程稱為對象的反序列化。可以是文件中的,也可以是網絡傳輸過來的。
3)對象的序列化和反序列化主要就是使用ObjectOutputStream 和 ObjectInputStream
Python部分
介紹Python的項目經驗
答:結合實際情況回答
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() :等待所有進程結束后,才關閉進程池。
進程池內部維護一個進程序列,當使用時,去進程池中獲取一個進程,如果進程池序列中沒有可供使用的進程,那么程序就會等待,直到進程池中有可用進程為止。
是否了解Python的協程?
答:線程和進程的操作是由程序觸發系統接口,最后的執行者是系統,它本質上是操作系統提供的功能。而協程的操作則是程序員指定的,在python中通過yield,人為的實現并發處理。
協程存在的意義:對于多線程應用,CPU通過切片的方式來切換線程間的執行,線程切換時需要耗時。協程,則只使用一個線程,分解一個線程成為多個“微線程”,在一個線程中規定某個代碼塊的執行順序。
協程的適用場景:當程序中存在大量不需要CPU的操作時(IO)。
常用第三方模塊:gevent和greenlet。(本質上,gevent是對greenlet的高級封裝,因此一般用它就行,這是一個相當高效的模塊。)
①gevent:通過joinall將任務f和它的參數進行統一調度,實現單線程中的協程。代碼封裝層次很高,實際使用只需要了解它的幾個主要方法即可。
②greenlet:greenlet就是通過switch方法在不同的任務之間進行切換。
Python的裝飾器的用法和原理
答:Python中的裝飾器是通過利用了函數的特定的閉包實現的,所以我們需要了解Python閉包的原理,以及函數的功能特性。
函數特性:①函數作為變量傳遞;②函數作為參數傳遞;③函數作為返回值;④函數嵌套及跨域訪問。
閉包原理:閉包其實就是在一個函數中嵌套另一個函數的定義。閉包的作用:包括了外部函數的局部變量,這些局部變量在外部函數返回后也繼續存在,并能被內部函數引用。
裝飾器的功能:閉包的另一種表現形式,主要用于裝飾功能,在不改變原代碼以及原代碼的調用方式,另外的添加額外的功能,就是對已經存在的某些類進行裝飾,以此來擴展或者說增強函數的一些功能。
裝飾器的用法:本質上是一個函數,只不過這個函數需要遵循以下規則:①入參只能有一個,類型為函數。 被裝飾的函數將入會被傳入這個參數;②返回值是必須是一個函數, 屆時被調用的時候實際上調用的是返回出來的這個函數,所以返回的函數入參通常是:
以滿足所有函數需要,之后通過@語法糖即可裝飾到任意函數上。示例代碼如下:
# 不帶參數的裝飾器 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”)
帶參數的裝飾器的例子(參數控制的是裝飾器的行為):只需要寫一個返回,裝飾器(入參只有一個,返回值是一個函數)函數的函數,同樣也能利用@語法糖,代碼如下:
這里需要注意的是語法糖中的(”Foo“)不能忽略。實際上@后面并不是對pre_do_sth這個函數生效,而是對pre_do_sth的返回值生效。
對于多個裝飾器的調用順序而言,先聲明的裝飾器先執行,即在最外層:
Linux部分
如何查看當前系統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 顯示所有當前調用棧的所有變量
awk實現一條命令將某一個目錄里的所有文件分別進行備份,后綴為.bak
答:命令如下:
答:在Linux中啟動一個進程有手工啟動和調度啟動兩種方式:
(1)手工啟動:用戶在輸入端發出命令,直接啟動一個進程的啟動方式可以分為:①前臺啟動:直接在SHELL中輸入命令進行啟動。②后臺啟動:啟動一個目前并不緊急的進程,如打印進程.
(2)調度啟動:系統管理員根據系統資源和進程占用資源的情況,事先進行調度安排,指定任務運行的時間和場合,到時候系統會自動完成該任務。經常使用的進程調度命令為:at、batch、crontab。
計算機網絡部分
- 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
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狀態還需要等2MSL(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個探測報文仍然沒反應,服務器就認為客戶端出了故障,接著就關閉連接。
瀏覽器和服務器之間如何建立連接?
答:這里分為兩種情況:一種是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完成一次讀寫之后,它們之間的連接并不會主動關閉,后續的讀寫操作會繼續使用這個連接。
IO中同步與異步,阻塞與非阻塞區別
答:同步和異步關注的是消息通信機制 (synchronous communication/asynchronous communication)。所謂同步,就是在發出一個調用時,在沒有得到結果之前,該調用不返回。但是一旦調用返回,就得到返回值了。換句話說,就是由調用者主動等待這個調用的結果。而異步則是相反,調用在發出之后,這個調用就直接返回了,所以沒有返回結果。換句話說,當一個異步過程調用發出后,調用者不會立刻得到結果。而是在調用發出后,被調用者通過狀態、通知來通知調用者,或通過回調函數處理這個調用。
阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態。阻塞調用是指調用結果返回之前,當前線程會被掛起。函數只有在得到結果之后才會返回。非阻塞:不能立刻得到結果之前,該函數不會阻塞當前線程,而會立刻返回。
DNS進行域名解析的過程.
答:客戶端發出DNS請求翻譯IP地址或主機名,DNS服務器在收到客戶機的請求后:
(1)檢查DNS服務器的緩存,若查到請求的地址或名字,即向客戶機發出應答信息;
(2)若沒有查到,則在數據庫中查找,若查到請求的地址或名字,即向客戶機發出應答信息;
(3)若沒有查到,則將請求發給根域DNS服務器,并依序從根域查找頂級域,由頂級查找二級域,二級域查找三級,直至找到要解析的地址或名字,即向客戶機所在網絡的DNS服務器發出應答信息,DNS服務器收到應答后先在緩存中存儲,然后將解析結果發給客戶機;
(4)若沒有找到,則返回錯誤信息。
在瀏覽器中輸入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
總結
以上是生活随笔為你收集整理的【面试】网易游戏社招一面总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java虚拟机参数优化_JAVA虚拟机J
- 下一篇: 从离散值中把值相近的放在一起