抖音、騰訊、阿里、美團春招服務(wù)端開發(fā)崗位硬核面試(二)
在上一篇?文章中,我們分享了幾大互聯(lián)網(wǎng)公司面試的題目,本文就來詳細(xì)分析面試題答案以及復(fù)習(xí)參考和整理的面試資料,小民同學(xué)的私藏珍品?。
首先是面試題答案公布,在講解時我們主要分成如下幾塊:語言的基礎(chǔ)知識、中間件、操作系統(tǒng)、計算機網(wǎng)絡(luò)、手寫算法、開放題和項目經(jīng)歷。對面試題和涉及的知識點進(jìn)行整理,這樣更容易讓各位同學(xué)理解。不會按照提問的順序進(jìn)行講解,還請見諒。
其次是 Java 復(fù)習(xí)參考和整理的面試資料。由于內(nèi)容比較多,學(xué)習(xí)有?道?非常重要,我們介紹一下其中的要點和目錄,完整文件可以參見筆者提供的 pdf 資料。
面試題解析
Java 的語言基礎(chǔ)
Future 的缺陷?
Future 在異步編程中經(jīng)常用到,F(xiàn)uture 表示異步計算的結(jié)果。它提供了檢查計算是否完成的方法,以等待計算的完成,并獲取計算的結(jié)果。
然而 Future 接口調(diào)用 get()方法取得處理的結(jié)果值時是阻塞性的,如果調(diào)用 Future 對象的 get()方法時,如果這個線程還沒執(zhí)行完成,就一直主線程main阻塞到此線程完成為止,就算和它同時進(jìn)行的其它線程已經(jīng)執(zhí)行完了,也要等待這個耗時線程執(zhí)行完才能獲取結(jié)果,大大影響運行效率。那么使用多線程就沒什么意義了。
CompletionService 在依賴任務(wù)之間是如何實現(xiàn)的?
接上一個問你題,鑒于 Future 的缺陷,JDK 1.8 并發(fā)包也提供了CompletionService接口可以解決這個問題,它的take()方法哪個線程先完成就先獲取誰的 Futrue 對象。
volatile 怎么搞
出現(xiàn) volatile,是因為多線程的場景下存在臟讀。Java內(nèi)存模型規(guī)定所有的變量都是存在主存當(dāng)中,每個線程都有自己的工作內(nèi)存。線程對變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接對主存進(jìn)行操作。并且每個線程不能訪問其他線程的工作內(nèi)存。變量的值何時從線程的工作內(nèi)存寫回主存,無法確定。

volatile關(guān)鍵字的作用:保證了變量的可見性(visibility)。被volatile關(guān)鍵字修飾的變量,如果值發(fā)生了變更,其他線程立馬可見,避免出現(xiàn)臟讀的現(xiàn)象。如以下代碼片段,isShutDown被置為true后,doWork方法仍有執(zhí)行。如用volatile修飾isShutDown變量,可避免此問題。
volatile只能保證變量的可見性,不能保證對volatile變量操作的原子性。
類加載機制?
JVM將class文件字節(jié)碼文件加載到內(nèi)存中, 并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)中的運行時數(shù)據(jù)結(jié)構(gòu),在堆(并不一定在堆中,HotSpot在方法區(qū)中)中生成一個代表這個類的java.lang.Class 對象,作為方法區(qū)類數(shù)據(jù)的訪問入口。

JVM類加載機制分為五個部分:加載,驗證,準(zhǔn)備,解析,初始化,下面我們就分別來看一下這五個過程。其中加載、檢驗、準(zhǔn)備、初始化和卸載這個五個階段的順序是固定的,而解析則未必。為了支持動態(tài)綁定,解析這個過程可以發(fā)生在初始化階段之后。
加載:加載過程主要完成三件事情,通過類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流;將這個類字節(jié)流代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu);在堆中生成一個代表此類的java.lang.Class對象,作為訪問方法區(qū)這些數(shù)據(jù)結(jié)構(gòu)的入口。
驗證:此階段主要確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機的要求,并且不會危害虛擬機的自身安全。文件格式驗證:基于字節(jié)流驗證;元數(shù)據(jù)驗證:基于方法區(qū)的存儲結(jié)構(gòu)驗證;字節(jié)碼驗證:基于方法區(qū)的存儲結(jié)構(gòu)驗證;
符號引用驗證:基于方法區(qū)的存儲結(jié)構(gòu)驗證。準(zhǔn)備:為類變量分配內(nèi)存,并將其初始化為默認(rèn)值。(此時為默認(rèn)值,在初始化的時候才會給變量賦值)即在方法區(qū)中分配這些變量所使用的內(nèi)存空間。例如:
public?static?int?value?=?123;
此時在準(zhǔn)備階段過后的初始值為0而不是123。
解析:把類型中的符號引用轉(zhuǎn)換為直接引用。符號引用與虛擬機實現(xiàn)的布局無關(guān),引用的目標(biāo)并不一定要已經(jīng)加載到內(nèi)存中。各種虛擬機實現(xiàn)的內(nèi)存布局可以各不相同,但是它們能接受的符號引用必須是一致的,因為符號引用的字面量形式明確定義在Java虛擬機規(guī)范的Class文件格式中;直接引用可以是指向目標(biāo)的指針,相對偏移量或是一個能間接定位到目標(biāo)的句柄。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。
初始化:初始化階段是執(zhí)行類構(gòu)造器方法的過程。方法是由編譯器自動收集類中的類變量的賦值操作和靜態(tài)語句塊中的語句合并而成的。虛擬機會保證方法執(zhí)行之前,父類的方法已經(jīng)執(zhí)行完畢。如果一個類中沒有對靜態(tài)變量賦值也沒有靜態(tài)語句塊,那么編譯器可以不為這個類生成()方法。
java中,對于初始化階段,有且只有以下五種情況才會對要求類立刻“初始化”(加載,驗證,準(zhǔn)備,自然需要在此之前開始):使用new關(guān)鍵字實例化對象、訪問或者設(shè)置一個類的靜態(tài)字段(被final修飾、編譯器優(yōu)化時已經(jīng)放入常量池的例外)、調(diào)用類方法,都會初始化該靜態(tài)字段或者靜態(tài)方法所在的類。
初始化類的時候,如果其父類沒有被初始化過,則要先觸發(fā)其父類初始化。
使用java.lang.reflect包的方法進(jìn)行反射調(diào)用的時候,如果類沒有被初始化,則要先初始化。
虛擬機啟動時,用戶會先初始化要執(zhí)行的主類(含有main)
jdk 1.7后,如果java.lang.invoke.MethodHandle的實例最后對應(yīng)的解析結(jié)果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄,并且這個方法所在類沒有初始化,則先初始化。
類加載器?
把類加載階段的“通過一個類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流”這個動作交給虛擬機之外的類加載器來完成。這樣的好處在于,我們可以自行實現(xiàn)類加載器來加載其他格式的類,只要是二進(jìn)制字節(jié)流就行,這就大大增強了加載器靈活性。系統(tǒng)自帶的類加載器分為三種:
啟動類加載器。
擴展類加載器。
應(yīng)用程序類加載器。

雙親委派機制工作過程:
如果一個類加載器收到了類加載器的請求.它首先不會自己去嘗試加載這個類.而是把這個請求委派給父加載器去完成.每個層次的類加載器都是如此.因此所有的加載請求最終都會傳送到Bootstrap類加載器(啟動類加載器)中.只有父類加載反饋自己無法加載這個請求(它的搜索范圍中沒有找到所需的類)時.子加載器才會嘗試自己去加載。
雙親委派模型的優(yōu)點:java類隨著它的加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系。
例如類java.lang.Object,它存放在rt.jart之中.無論哪一個類加載器都要加載這個類.最終都是雙親委派模型最頂端的Bootstrap類加載器去加載.因此Object類在程序的各種類加載器環(huán)境中都是同一個類.相反.如果沒有使用雙親委派模型.由各個類加載器自行去加載的話.如果用戶編寫了一個稱為“java.lang.Object”的類.并存放在程序的ClassPath中.那系統(tǒng)中將會出現(xiàn)多個不同的Object類.java類型體系中最基礎(chǔ)的行為也就無法保證.應(yīng)用程序也將會一片混亂。
JDBC 加載機制?SPI 與雙親委派?
JDBC 加載機制:SPI ,全稱為(Service Provider Interface) ,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機制;主要被框架的開發(fā)人員使用,比如java.sql.Driver接口,數(shù)據(jù)庫廠商實現(xiàn)此接口即可,當(dāng)然要想讓系統(tǒng)知道具體實現(xiàn)類的存在,還需要使用固定的存放規(guī)則,需要在classpath下的META-INF/services/目錄里創(chuàng)建一個以服務(wù)接口命名的文件,這個文件里的內(nèi)容就是這個接口的具體的實現(xiàn)類。
SPI 服務(wù)機制破壞了雙親委派模型??梢钥闯鲭p親委派機制是一種至下而上的加載方式,那么SPI是如何打破這種關(guān)系?
以JDBC加載驅(qū)動為例:在JDBC4.0之后支持SPI方式加載java.sql.Driver的實現(xiàn)類。SPI實現(xiàn)方式為,通過ServiceLoader.load(Driver.class)方法,去各自實現(xiàn)Driver接口的lib的META-INF/services/java.sql.Driver文件里找到實現(xiàn)類的名字,通過Thread.currentThread().getContextClassLoader()類加載器加載實現(xiàn)類并返回實例。
驅(qū)動加載的過程大致如上,那么是在什么地方打破了雙親委派模型呢?
先看下如果不用Thread.currentThread().getContextClassLoader()加載器加載,整個流程會怎么樣。
從META-INF/services/java.sql.Driver文件得到實現(xiàn)類名字DriverA
Class.forName("xx.xx.DriverA")來加載實現(xiàn)類Class.forName()方法默認(rèn)使用當(dāng)前類的ClassLoader,JDBC是在D riverManager類里調(diào)用Driver的,當(dāng)前類也就是DriverManager,它的加載器是BootstrapClassLoader。
用BootstrapClassLoader去加載非rt.jar包里的類xx.xx.DriverA,就會找不到。
要加載xx.xx.DriverA需要用到AppClassLoader或其他自定義ClassLoader
最終矛盾出現(xiàn)在,要在BootstrapClassLoader加載的類里,調(diào)用AppClassLoader去加載實現(xiàn)類。
因此在父加載器加載的類中,去調(diào)用子加載器去加載類:
jdk提供了兩種方式,Thread.currentThread().getContextClassLoader()和ClassLoader.getSystemClassLoader()一般都指向AppClassLoader,他們能加載classpath中的類
SPI 則用 Thread.currentThread().getContextClassLoader() 來加載實現(xiàn)類,實現(xiàn)在核心包里的基礎(chǔ)類調(diào)用用戶代碼
面向?qū)ο蟮脑瓌t
單一原則。一個類應(yīng)該有且只有一個變化的原因。單一職責(zé)原則將不同的職責(zé)分離到單獨的類,每一個職責(zé)都是一個變化的中心。需求變化時,將通過更改職責(zé)相關(guān)的類來體現(xiàn)。如果一個類擁有多于一個的職責(zé),則多個職責(zé)耦合在一起,會有多于一個原因來導(dǎo)致這個類發(fā)生變化。一個職責(zé)的變化可能會影響到其他的職責(zé),另外,把多個職責(zé)耦合在一起,影響復(fù)用性。
里氏替換原則,就是要求繼承是嚴(yán)格的is-a關(guān)系。所有引用基類的地方必須能透明地使用其子類的對象。在軟件中將一個基類對象替換成它的子類對象,程序?qū)⒉粫a(chǎn)生任何錯誤和異常,反過來則不成立,如果一個軟件實體使用的是一個子類對象的話,那么它不一定能夠使用基類對象。例如:我喜歡動物,那我一定喜歡狗,因為狗是動物的子類;但是我喜歡狗,不能據(jù)此斷定我喜歡動物,因為我并不喜歡老鼠,雖然它也是動物。
依賴倒置原則。依賴倒置原則的核心就是要我們面向接口編程,理解了面向接口編程,也就理解了依賴倒置。低層模塊盡量都要有抽象類或接口,或者兩者都有。變量的聲明類型盡量是抽象類或接口。
接口分離原則。一個類對另一個類的依賴應(yīng)該建立在最小的接口上,通俗的講就是需要什么就提供什么,不需要的就不要提供。接口中的方法應(yīng)該盡量少,不要使接口過于臃腫,不要有很多不相關(guān)的邏輯方法。
多用組合(has-a),少用繼承(is-a)。如果新對象的某些功能在別的已經(jīng)創(chuàng)建好的對象里面已經(jīng)實現(xiàn),那么應(yīng)當(dāng)盡量使用別的對象提供的功能,使之成為新對象的一部分,而不要再重新創(chuàng)建??梢越档皖惻c類之間的耦合程度。
開閉原則。對修改關(guān)閉,對擴展開放。在軟件的生命周期內(nèi),因為變化,升級和維護(hù)等原因需要對軟件原有代碼進(jìn)行修改,可能會給舊代碼引入錯誤,也有可能會使我們不得不對整個功能進(jìn)行重構(gòu),并且需要原有代碼經(jīng)過重新測試。解決方案:當(dāng)軟件需要變化時,盡量通過擴展軟件實體的行為來實現(xiàn)變化,而不是通過修改已有的代碼來實現(xiàn)。不過這要求,我們要對需求的變更有前瞻性和預(yù)見性。其實只要遵循前面5種設(shè)計模式,設(shè)計出來的軟件就是符合開閉原則的。
對象創(chuàng)建過程
一個對象在可以被使用之前必須要被正確地實例化。在Java代碼中,有很多行為可以引起對象的創(chuàng)建,最為直觀的一種就是使用new關(guān)鍵字來調(diào)用一個類的構(gòu)造函數(shù)顯式地創(chuàng)建對象,這種方式在Java規(guī)范中被稱為:由執(zhí)行類實例創(chuàng)建表達(dá)式而引起的對象創(chuàng)建。除此之外,我們還可以使用反射機制(Class類的newInstance方法、使用Constructor類的newInstance方法)、使用Clone方法、使用反序列化等方式創(chuàng)建對象。
當(dāng)一個對象被創(chuàng)建時,虛擬機就會為其分配內(nèi)存來存放對象自己的實例變量及其從父類繼承過來的實例變量(即使這些從超類繼承過來的實例變量有可能被隱藏也會被分配空間)。在為這些實例變量分配內(nèi)存的同時,這些實例變量也會被賦予默認(rèn)值(零值)。在內(nèi)存分配完成之后,Java虛擬機就會開始對新創(chuàng)建的對象按照開發(fā)人員的意志進(jìn)行初始化。
類加載檢查-->分配內(nèi)存-->初始化零值-->設(shè)置對象頭-->執(zhí)行init方法

策略模式 不同策略怎么轉(zhuǎn)化?
策略模式是一種比較簡單的模式,他的定義是:定義一組算法,將每個算法都封裝起來,并且使他們之間可以互換。

Context封裝角色,也叫做上下文角色,起承上啟下封裝作用,屏蔽高層模塊對策略、算法的直接訪問,封裝可能存在的變化。
Spring AOP 如何實現(xiàn)及應(yīng)用?
基于代理思想,對原來目標(biāo)對象,創(chuàng)建代理對象,在不修改原對象代碼情況下,通過代理對象,調(diào)用增強功能的代碼,從而對原有業(yè)務(wù)方法進(jìn)行增強 !Spring中AOP的有兩種實現(xiàn)方式:JDK動態(tài)代理以及Cglib動態(tài)代理。
使用場景:記錄日志、監(jiān)控方法運行時間 (監(jiān)控性能)、權(quán)限控制、緩存優(yōu)化 (第一次調(diào)用查詢數(shù)據(jù)庫,將查詢結(jié)果放入內(nèi)存對象, 第二次調(diào)用, 直接從內(nèi)存對象返回,不需要查詢數(shù)據(jù)庫 )、事務(wù)管理 (調(diào)用方法前開啟事務(wù), 調(diào)用方法后提交關(guān)閉事務(wù) )
你項目中如何捕獲業(yè)務(wù)異常以及記錄日志的?
AOP 思想,Spring 統(tǒng)一異常處理有 3 種方式,分別為:
使用 @ ExceptionHandler 注解
實現(xiàn) HandlerExceptionResolver 接口
使用 @controlleradvice 注解
編譯時異常和運行時異常RuntimeException,前者通過捕獲異常從而獲取異常信息,后者主要通過規(guī)范代碼開發(fā)、測試通過手段減少運行時異常的發(fā)生。在開發(fā)中,不管是dao層、service層還是controller層,都有可能拋出異常,在springmvc中,能將所有類型的異常處理從各處理過程解耦出來,既保證了相關(guān)處理過程的功能較單一,也實現(xiàn)了異常信息的統(tǒng)一處理和維護(hù)。
java 枚舉類型是否可以繼承 (final)?
enum類是無法被繼承的,編譯器會自動把枚舉用繼承enum類來表示,但這一過程是由編譯器完成的,枚舉也不過是個語法糖。
如果一個類的實例是有限且確定的,那么可以使用枚舉類。比如:季節(jié)類,只有春夏秋冬四個實例。
enum類默認(rèn)被final修飾的情況下,是無法有子類的。enum本身不存在final、abstract的說法。就是不能被繼承。運行時生成的class才有final、abstract的說法。
注解是否可以繼承?
我們知道在編寫自定義注解時,可以通過指定@Inherited注解,指明自定義注解是否可以被繼承。但實現(xiàn)情況又可細(xì)分為多種。

@Inherited 只可控制 對類名上注解是否可以被繼承。不能控制方法上的注解是否可以被繼承。
java 內(nèi)存結(jié)構(gòu)
JAVA內(nèi)存結(jié)構(gòu):堆、棧、方法區(qū);堆:存放所有 new出來的東西(堆空間是所有線程共享,虛擬機啟動的時候建立);棧:存放局部變量(線程創(chuàng)建的時候 被創(chuàng)建);方法區(qū):被虛擬機加載的類信息、常量、靜態(tài)常量等。

程序計數(shù)器
程序計數(shù)器可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器,是一塊線程隔離的內(nèi)存空間。在虛擬機的概念模型中,字節(jié)碼解釋器通過改變程序計數(shù)器的值來選取下一條執(zhí)行的字節(jié)碼命令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要計數(shù)器完成。每個線程都有獨立的程序計數(shù)器內(nèi)存空間,它們之間相互隔離、互不影響。當(dāng)線程上下文進(jìn)行切換時,線程獨占的程序計數(shù)器也會被加載。
當(dāng)線程在執(zhí)行Java方法時,計數(shù)器中記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址;如果執(zhí)行的是Native方法,計數(shù)器的值為空。程序計數(shù)器在Java虛擬機規(guī)范中沒有規(guī)定如何的OutOfMemoryError情況的區(qū)域。
Java虛擬機棧
Java虛擬機棧也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時會創(chuàng)建一個棧幀,用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等。每一個方法從調(diào)用到執(zhí)行完成的過程,對應(yīng)著一個棧幀從虛擬機棧中入棧到出棧的過程。
局部變量表中存放了編譯期可知的各種基本的數(shù)據(jù)類型,對象引用和returnAddress類型(指向了一條字節(jié)碼指令的地址)。局部變量表所需的內(nèi)存空間在編譯期間完成分配,方法運行期間不會改變局部變量表的大小。
當(dāng)線程請求的棧深度大于虛擬機允許的深度,將拋出StackOverflowError異常;如果虛擬機棧可以動態(tài)擴展,如果擴展時無法申請到足夠的內(nèi)存,將會拋出OutOfMemoryError異常。
本地方法棧
本地方法棧描述虛擬機使用到的Native方法執(zhí)行的內(nèi)存模型,其作用與Java虛擬機棧類似,同樣可能拋出StackOverflowError和OutOfMemoryError異常。
Java堆
Java堆是所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機啟動時創(chuàng)建。Java堆作為運行時數(shù)據(jù)區(qū)域,存放著所有的類實例和數(shù)組,這是Java虛擬機規(guī)范中的規(guī)定。但是JIT編譯器的發(fā)展和逃逸分析技術(shù)的逐漸成熟,棧上分配、標(biāo)量替換等優(yōu)化技術(shù)使得所有對象都在堆上分配變得不那么絕對。
Java堆是垃圾收集器管理的主要區(qū)域。從內(nèi)存回收的角度來講,現(xiàn)在的收集器基本都是采取分代收集算法,所以Java堆可以細(xì)分為新生代和老年代,再細(xì)致一點新生代中有Eden空間、From Survivor空間、 To Survivor空間等。從內(nèi)存分配的角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩沖區(qū)。
進(jìn)一步劃分的是為了更好地回收內(nèi)存或者更快地分配內(nèi)存。
如果在Java堆中沒有內(nèi)存完成實例分配,并且堆無法再擴展,將會拋出OutOfMemoryError異常。
方法區(qū)
方法區(qū)作為所有線程共享的內(nèi)存區(qū)域,存儲了被虛擬機加載的類信息、常量、靜態(tài)變量、及時編譯器編譯后的代碼等數(shù)據(jù)。
在HotSpot 1.8之前,HotSpot通過永久代的方式實現(xiàn)了方法區(qū),GC分代收集的方式擴展到了方法區(qū),減少了專門管理方法區(qū)內(nèi)存管理代碼的編寫。在HotSpot 1.8中,方法區(qū)通過元數(shù)據(jù)區(qū)實現(xiàn),永久代被廢棄,在1.7時字符串常量池已經(jīng)被遷移到堆空間中。
方法區(qū)中的內(nèi)存回收目標(biāo)主要是針對常量池的回收和對類型的卸載。
當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,將會拋出OutOfMemoryError異常。
運行時常量池
運行時常量池作為方法區(qū)的一部分存在。Class文件中的常量池用于存放編譯期間生成的各種字面量和符號引用,在類加載后進(jìn)入方法區(qū)的運行時常量池中存放。
運行時常量池相對于Class文件常量池的另一個重要特征是具備動態(tài)性,即在運行時也可以將新的常量放入池中(String#intern方法)。
直接內(nèi)存
直接內(nèi)存并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分。在JDK 1.4中新加入的NIO類,引入了一種基于通道與緩沖區(qū)的I/O方式,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用進(jìn)行操作,這樣能夠在一些場景中避免Java堆和Native堆中來回復(fù)制數(shù)據(jù),提高性能。
一般來講,本機直接內(nèi)存的分配不會收到Java堆大小的限制,但是總會受到本機物理內(nèi)存以及尋址空間的限制。如果各個內(nèi)存區(qū)域的總和大于物理內(nèi)存限制,容易導(dǎo)致動態(tài)擴展時出現(xiàn)OutOfMemoryError異常。
注意不要回答成內(nèi)存模型!
jvm參數(shù) 為什么要配置?
堆空間主要組成部分:
新生代(new generation),新生代又劃分為3部分:
eden
From Survivor(s0區(qū)域)
To Survivor(s1區(qū)域)
其中s0和s1區(qū)域大小相等
老年代(tenured generation)
new出來的對象都會存放在堆內(nèi)存中。新生代和老年代的存在主要用于垃圾回收機制,其中主要針對的是新生代,因為對象首先分配在eden區(qū),在新生代回收后,如果對象還存活,則進(jìn)入s0或s1區(qū),之后每經(jīng)過一次新生代回收,如果對象存活則它的年齡就加1,對象達(dá)到一定的年齡后,則進(jìn)入老年代。

在JVM啟動參數(shù)中,可以設(shè)置跟內(nèi)存、垃圾回收相關(guān)的一些參數(shù)設(shè)置,默認(rèn)情況不做任何設(shè)置JVM會工作的很好,但對一些配置很好的Server和具體的應(yīng)用必須仔細(xì)調(diào)優(yōu)才能獲得最佳性能。通過設(shè)置我們希望達(dá)到一些目標(biāo):
GC的時間足夠的小
GC的次數(shù)足夠的少
發(fā)生Full GC(新生代和老年代)的周期足夠的長
要想GC時間小必須要一個更小的堆,要保證GC次數(shù)足夠少,必須保證一個更大的堆,我們只能取其平衡。
針對JVM堆的設(shè)置,一般可以通過-Xms -Xmx限定其最小、最大值,為了防止垃圾收集器在最小、最大之間收縮堆而產(chǎn)生額外的時間,我們通常把最大、最小設(shè)置為相同的值
年輕代和年老代將根據(jù)默認(rèn)的比例(1:2)分配堆內(nèi)存,可以通過調(diào)節(jié)二者的比例為1:3或者1:4,當(dāng)然也可以設(shè)置新生代的大小,原則上為堆空間的1/3或者1/4。
8G內(nèi)存的機器 java進(jìn)程最大配置多少?
增大堆內(nèi)存(-Xms,-Xmx)會減少可創(chuàng)建的線程數(shù)量,增大線程棧內(nèi)存(-Xss,32位系統(tǒng)中此參數(shù)值最小為60K)也會減少可創(chuàng)建的線程數(shù)量。

操作系統(tǒng)限制,系統(tǒng)最大可開線程數(shù),主要受以下幾個參數(shù)影響:
/proc/sys/kernel/thread-max:系統(tǒng)可以生成的最大線程數(shù)量
/proc/sys/kernel/pid_max:并不是threads-max,就能創(chuàng)建越多的線程,發(fā)現(xiàn)線程數(shù)量在達(dá)到一定數(shù)量以后不再增長。創(chuàng)建的線程數(shù)還受到系統(tǒng)可創(chuàng)建的最大pid數(shù)影響,默認(rèn)值為32768。
max_user_process:64位Linux系統(tǒng),這個參數(shù)還是會限制線程數(shù)量,可通過ulimit –a查看。
/proc/sys/vm/max_map_count:包含限制一個進(jìn)程可以擁有的VMA(虛擬內(nèi)存區(qū)域)的數(shù)量。
平衡樹的種類
平衡樹是一顆二叉搜索樹,并且它的深度保持相對穩(wěn)定,也就是不會退化成鏈的樹。平衡樹可以說是區(qū)間操作的數(shù)據(jù)結(jié)構(gòu)中效率高的一種,它最大的用處自然是維護(hù)區(qū)間了。細(xì)分為:splay、有旋/無旋treap、AVL樹、替罪羊樹、二叉查找樹(SBT)樹等。
跳表和平衡樹區(qū)別
Redis sortedset 使用的是跳表。跳表是一種可以替代平衡樹的數(shù)據(jù)結(jié)構(gòu)。跳表追求的是概率性平衡,而不是嚴(yán)格平衡。因此,跟平衡二叉樹相比,跳表的插入和刪除操作要簡單得多,執(zhí)行也更快。
二叉樹可以用來實現(xiàn)字典和有序表等抽象數(shù)據(jù)結(jié)構(gòu)。在元素隨機插入的場景,二叉樹可以很好應(yīng)對。然而,在有序插入的情況下,二叉樹就退化了(鏈表),性能非常差。如果有辦法對待插入元素進(jìn)行隨機排列,二叉樹大概率可以運行良好。大部分情況下,插入是在線進(jìn)行的,因此隨機排列并不具有可行性。平衡樹在操作時對樹結(jié)構(gòu)進(jìn)行調(diào)整以滿足平衡條件,因此獲得理想性能。
跳表是一種概率性可行的平衡二叉樹替代數(shù)據(jù)結(jié)構(gòu)。跳表通過一個隨機數(shù)生成器實現(xiàn)平衡。雖然跳表最壞情況下(worst-case)性能也很差,但是沒有任何輸入序列必然會導(dǎo)致最壞情況發(fā)生(這點類似劃分元素(pivot point)隨機選定的快排)。跳表極度不平衡發(fā)生的概率非常低(一個包含250個元素的字典,一次查找需要花3倍期望時間的概率小于百萬分之一)。跳表平衡概率跟隨機插入的二叉樹差不多,好處是插入順序不要求隨機。
實現(xiàn)概率性平衡比嚴(yán)格控制平衡要簡單得多。對很多應(yīng)用來說,跳表用起來比平衡樹更自然,而且算法更簡單。跳表算法簡單性意味著更容易實現(xiàn),而且與平衡樹和自適應(yīng)樹相比有常數(shù)倍數(shù)的性能提升。跳表在空間上也比較高效。平均每個元素只需要額外耗費個2指針(甚至可以配置得更低),并不需要在每個節(jié)點上都存與平衡和優(yōu)先級相關(guān)的數(shù)據(jù)。
volatile,內(nèi)存重排序到底怎么避免的?
Volatile 變量具有 synchronized 的可見性特性,但是不具備原子特性。
一般來說,處理器為了提高程序運行效率,可能會對輸入代碼進(jìn)行優(yōu)化,它不保證程序中各個語句的執(zhí)行先后順序同代碼中的順序一致,但是它會保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的。
在單線程程序中,對存在控制依賴的操作重排序,不會改變執(zhí)行結(jié)果;但在多線程程序中,對存在控制依賴的操作重排序,可能會改變程序的執(zhí)行結(jié)果。這是就需要內(nèi)存屏障來保證可見性了。
內(nèi)存屏障分為兩種:Load Barrier 和 Store Barrier即讀屏障和寫屏障。
對于Load Barrier來說,在指令前插入Load Barrier,可以讓高速緩存中的數(shù)據(jù)失效,強制從新從主內(nèi)存加載數(shù)據(jù);
對于Store Barrier來說,在指令后插入Store Barrier,能讓寫入緩存中的最新數(shù)據(jù)更新寫入主內(nèi)存,讓其他線程可見。
volatile的內(nèi)存屏障策略非常嚴(yán)格保守,非常悲觀且毫無安全感的心態(tài):
在每個volatile寫操作前插入StoreStore屏障,在寫操作后插入StoreLoad屏障;
在每個volatile讀操作前插入LoadLoad屏障,在讀操作后插入LoadStore屏障;
由于內(nèi)存屏障的作用,避免了volatile變量和其它指令重排序、線程之間實現(xiàn)了通信,使得volatile表現(xiàn)出了鎖的特性。
一個線程在內(nèi)存中如何存儲
從線程和進(jìn)程的角度來說,進(jìn)程是資源分配的最小單位,線程是獨立調(diào)度的最小單位。
同一個進(jìn)程中的多個線程之間可以并發(fā)執(zhí)行,他們共享進(jìn)程資源。
線程不擁有資源,線程可以訪問隸屬進(jìn)程的資源,進(jìn)程有自己的獨立空間地址,線程沒有自己的獨立空間地址,但是線程有自己的堆棧和局部變量。
線程的棧、程序計數(shù)器、本地方法區(qū)也是存放在進(jìn)程的地址空間上,只是這些棧、程序計數(shù)器、本地方法區(qū)都只能有某個特定的線程去訪問、其他的線程訪問不到。如果使用C/C++語言的話,數(shù)組越界后,很容易就訪問到其他線程的棧了,以致有可能導(dǎo)致其他線程的異常。
線程池,堵塞隊列為什么要用堵塞?
多線程環(huán)境中,通過隊列可以很容易實現(xiàn)數(shù)據(jù)共享,比如經(jīng)典的“生產(chǎn)者”和“消費者”模型中,通過隊列可以很便利地實現(xiàn)兩者之間的數(shù)據(jù)共享。假設(shè)我們有若干生產(chǎn)者線程,另外又有若干個消費者線程。如果生產(chǎn)者線程需要把準(zhǔn)備好的數(shù)據(jù)共享給消費者線程,利用隊列的方式來傳遞數(shù)據(jù),就可以很方便地解決他們之間的數(shù)據(jù)共享問題。但如果生產(chǎn)者和消費者在某個時間段內(nèi),萬一發(fā)生數(shù)據(jù)處理速度不匹配的情況呢?理想情況下,如果生產(chǎn)者產(chǎn)出數(shù)據(jù)的速度大于消費者消費的速度,并且當(dāng)生產(chǎn)出來的數(shù)據(jù)累積到一定程度的時候,那么生產(chǎn)者必須暫停等待一下(阻塞生產(chǎn)者線程),以便等待消費者線程把累積的數(shù)據(jù)處理完畢,反之亦然。然而,在concurrent包發(fā)布以前,在多線程環(huán)境下,我們每個程序員都必須去自己控制這些細(xì)節(jié),尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的復(fù)雜度。好在此時,強大的concurrent包橫空出世了,而他也給我們帶來了強大的BlockingQueue。(在多線程領(lǐng)域:所謂阻塞,在某些情況下會掛起線程(即阻塞),一旦條件滿足,被掛起的線程又會自動被喚醒)。
中間件
Tomcat框架的 selevet
Tomcat是目前市場上主流Web服務(wù)器之一,是用Java語言開發(fā)的項目。Tomcat支持Servlet和JSP的規(guī)范,它由一組嵌套的層次和組件組成。所有組件都實現(xiàn)lifecycle生命周期方法,里面包含了init、start、stop、destroy等方法,用來控制生命周期。
Servlet是用Java編寫的Server端程序,它與協(xié)議和平臺無關(guān)。Servlet運行于Java-enabled Web Server中。Java Servlet可以動態(tài)地擴展Server的能力,并采用請求-響應(yīng)模式提供Web服務(wù)。最早支持Servlet技術(shù)的是JavaSoft的Java Web Server。此后,一些其它的基于Java的Web Server開始支持標(biāo)準(zhǔn)的Servlet API。Servlet的主要功能在于交互式地瀏覽和修改數(shù)據(jù),生成動態(tài)Web內(nèi)容。

Tomcat服務(wù)器本質(zhì)是通過ServerSocket與客戶端進(jìn)行通信,要進(jìn)行通信首先就要進(jìn)行TCP連接,Tomcat有兩個核心組件,Connecter和Container,Connecter將在某個指定的端口上偵聽客戶請求,接收瀏覽器的發(fā)過來的 tcp 連接請求,創(chuàng)建一個 Request 和 Response 對象分別用于和請求端交換數(shù)據(jù),Request包含了用戶的請求信息,Response負(fù)責(zé)記錄了服務(wù)器的答復(fù)內(nèi)容。然后會產(chǎn)生一個線程來處理這個請求并把產(chǎn)生的 Request 和 Response 對象傳給Container處理。
Connector 最重要的功能就是接收連接請求然后分配線程讓 Container 來處理這個請求,所以這必然是多線程的,多線程的處理是 Connector 設(shè)計的核心。
當(dāng)Connector處理完后會調(diào)用Container的invoke()方法,你可以想象Container容器里有一條管道,管道上有很多閥門,每個閥門都會根據(jù)request進(jìn)行一些操作,request和response請求會依次經(jīng)過這些閥門,而Servlet就是該管道的最后一道閥門,之前的閥門就是filter。
Tomcat容器也分有上下層級關(guān)系如下圖,Tomcat的四層容器不都是必須的,一般簡單的容器只有Context和Wrapper兩層,Contenxt負(fù)責(zé)管理多個Wrapper,負(fù)責(zé)將映射轉(zhuǎn)發(fā)到對應(yīng)Wrapper,當(dāng)然期間還要經(jīng)過filter過濾。Wrapper是最低層的容器,它只包裹著一個Servlet,Wrapper負(fù)責(zé)加載并管理調(diào)用Servlet服務(wù)。
mysql B+ B- B區(qū)別?
B樹,即二叉搜索樹,有如下特點:
所有非葉子節(jié)點至多擁有兩個兒子(Leaf和Right)
左右結(jié)點存儲一個關(guān)鍵字
非葉子節(jié)點的左指針指向小于其關(guān)鍵字的子樹,右指針指向大于其關(guān)鍵字的子樹
B樹的搜索,從根結(jié)點開始,如果查詢的關(guān)鍵字與結(jié)點的關(guān)鍵字相等,那么就命中;否則,如果查詢關(guān)鍵字比結(jié)點關(guān)鍵字小,就進(jìn)入左兒子;如果比結(jié)點關(guān)鍵字大,就進(jìn)入右兒子;如果左兒子或右兒子的指針為空,則報告找不到相應(yīng)的關(guān)鍵字;如果B樹的所有非葉子結(jié)點的左右子樹的結(jié)點數(shù)目均保持差不多(平衡),那么B樹的搜索性能逼近二分查找;但它比連續(xù)內(nèi)存空間的二分查找的優(yōu)點是,改變B樹結(jié)構(gòu)(插入與刪除結(jié)點)不需要移動大段的內(nèi)存數(shù)據(jù),甚至通常是常數(shù)開銷。
B-樹,是一種多路搜索樹(并不是二叉的):
B樹和B-樹是同一種樹,只不過英語中B-tree被中國人翻譯成了B-樹,讓人以為B樹和B-樹是兩種樹,實際上,兩者就是同一種樹。此處單從算法的角度進(jìn)行了劃分,區(qū)別于 B+ 樹,可以參見:https://en.wikipedia.org/wiki/Binary_search_tree。
關(guān)鍵字集合分布在整顆樹中
任何一個關(guān)鍵字出現(xiàn)且只出現(xiàn)在一個結(jié)點中
搜素有可能在非葉子節(jié)點結(jié)束
其搜索性能等價于在關(guān)鍵字全集內(nèi)做一次二分查找
自動層次控制
由于限制了除根結(jié)點以外的非葉子結(jié)點,至少含有M/2個兒子,確保了結(jié)點的至少利用率。所以B-樹的性能總是等價于二分查找(與M值無關(guān)),也就沒有B樹平衡的問題,由于 M/2的限制,在插入結(jié)點時,如果結(jié)點已滿,需要將結(jié)點分裂為兩個各占 M/2 的結(jié)點,刪除結(jié)點時,需將兩個不足M/2的兄弟節(jié)點合并。B-樹的搜索,從根結(jié)點開始,對結(jié)點內(nèi)的關(guān)鍵字(有序)序列進(jìn)行二分查找,如果
命中則結(jié)束,否則進(jìn)入查詢關(guān)鍵字所屬范圍的兒子結(jié)點;重復(fù),直到所對應(yīng)的兒子指針為空,或已經(jīng)是葉子結(jié)點。
B+樹,B+樹是B-樹的變體,也是一種多路搜索樹,其定義基本與B-樹同,除了:
非葉子結(jié)點的子樹指針與關(guān)鍵字個數(shù)相同;
非葉子結(jié)點的子樹指針P[i],指向關(guān)鍵字值屬于[K[i], K[i+1])的子樹(B-樹是開區(qū)間);
為所有葉子結(jié)點增加一個鏈指針;
所有關(guān)鍵字都在葉子結(jié)點出現(xiàn)
B+的搜索與B-樹也基本相同,區(qū)別是B+樹只有達(dá)到葉子結(jié)點才命中(B-樹可以在非葉子結(jié)點命中),其性能也等價于在關(guān)鍵字全集做一次二分查找;非葉子結(jié)點相當(dāng)于是葉子結(jié)點的索引(稀疏索引),葉子結(jié)點相當(dāng)于是存儲(關(guān)鍵字)數(shù)據(jù)的數(shù)據(jù)層;更適合文件索引系統(tǒng)。
mysql 隔離級別?
數(shù)據(jù)庫事務(wù)是數(shù)據(jù)庫管理系統(tǒng)執(zhí)行過程中的一個邏輯單位,由一個有限的數(shù)據(jù)庫操作序列構(gòu)成。事務(wù)擁有四個重要的特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability),人們習(xí)慣稱之為 ACID 特性。
SQL 標(biāo)準(zhǔn)定義的四種隔離級別:
READ UNCOMMITTED(讀未提交):該隔離級別的事務(wù)會讀到其它未提交事務(wù)的數(shù)據(jù),此現(xiàn)象也稱之為臟讀。
READ COMMITTED(讀提交):
一個事務(wù)可以讀取另一個已提交的事務(wù),多次讀取會造成不一樣的結(jié)果,此現(xiàn)象稱為不可重復(fù)讀問題,Oracle 和 SQL Server 的默認(rèn)隔離級別。REPEATABLE READ(可重復(fù)讀):
該隔離級別是 MySQL 默認(rèn)的隔離級別,在同一個事務(wù)里,select 的結(jié)果是事務(wù)開始時時間點的狀態(tài),因此,同樣的 select 操作讀到的結(jié)果會是一致的,但是,會有幻讀現(xiàn)象。MySQL 的 InnoDB 引擎可以通過 next-key locks 機制來避免幻讀。SERIALIZABLE(序列化):
在該隔離級別下事務(wù)都是串行順序執(zhí)行的,MySQL 數(shù)據(jù)庫的 InnoDB 引擎會給讀操作隱式加一把讀共享鎖,從而避免了臟讀、不可重讀復(fù)讀和幻讀問題。

MVCC如何保證可重復(fù)讀?
MySQL的innodb引擎是如何實現(xiàn)MVCC的。innodb會為每一行添加兩個字段,分別表示該行創(chuàng)建的版本和刪除的版本,填入的是事務(wù)的版本號,這個版本號隨著事務(wù)的創(chuàng)建不斷遞增。在repeated read的隔離級別下,具體各種數(shù)據(jù)庫操作的實現(xiàn):
事務(wù)開始,第一次不加鎖SELECT時,InnoDB從全局事務(wù)鏈表中,篩選所有活動事務(wù)(事務(wù)trx_id嚴(yán)格遞增),生成當(dāng)前一致性視圖。
根據(jù)當(dāng)前一致性視圖高低水位,計算事務(wù)可見性。
根據(jù)可見事務(wù)redo log,逆向算出歷史版本。SELECT快照讀,讀之前版本數(shù)據(jù)。SELECT FOR UPDATE 或 UPDATE 當(dāng)前讀,加行鎖讀當(dāng)前值,不會創(chuàng)建一致性視圖,有其它事務(wù)更新時,等待其它事務(wù)提交。(2PL更新時加寫鎖,事務(wù)提交時才會釋放)
間隙鎖怎么使用?
InnoDB 行級鎖是通過給索引上的索引項加鎖來實現(xiàn)的,InnoDB行級鎖只有通過索引條件檢索數(shù)據(jù),才使用行級鎖;否則,InnoDB使用表鎖。
在不通過索引(主鍵)條件查詢的時候,InnoDB是表鎖而不是行鎖。也就是說,在沒有使用索引的情況下,使用的就是表鎖。
間隙鎖可以理解為是對于一定范圍內(nèi)的數(shù)據(jù)進(jìn)行鎖定,如果說這個區(qū)間沒有這條數(shù)據(jù)的話也是會鎖住的;主要是解決幻讀的問題,如果沒有添加間隙鎖。如果其他事務(wù)中添加 id 在 1 到 100 之間的某條記錄,此時會發(fā)生幻讀;另一方面,視為了滿足其恢復(fù)和賦值的需求(幻讀的概念在上面有提到)。
默認(rèn)情況下,innodb_locks_unsafe_for_binlog是0(禁用),這意味著啟用了間隙鎖定:InnoDB使用下一個鍵鎖進(jìn)行搜索和索引掃描。若要啟用該變量,請將其設(shè)置為1。這將導(dǎo)致禁用間隙鎖定:InnoDB只使用索引記錄鎖進(jìn)行搜索和索引掃描。
innodb自動使用間隙鎖的條件:
必須在RR級別下
檢索條件必須有索引(沒有索引的話,mysql會全表掃描,那樣會鎖定整張表所有的記錄,包括不存在的記錄,此時其他事務(wù)不能修改不能刪除不能添加)
間隙鎖的目的是為了防止幻讀,其主要通過兩個方面實現(xiàn)這個目的:
防止間隙內(nèi)有新數(shù)據(jù)被插入
防止已存在的數(shù)據(jù),更新成間隙內(nèi)的數(shù)據(jù)(例如防止 number=3 的記錄通過update變成 number=5)
mysql hash索引使用場景?
hash索引的特點:
hash索引是基于hash表實現(xiàn)的,只有查詢條件精確匹配hash索引中的所有列的時候,才能用到hash索引。
對于hash索引中的所有列,存儲引擎都會為每一行計算一個hash碼,hash索引中存儲的就是hash碼。
hash索引包括鍵值、hash碼和指針 。
在MySQL的存儲引擎中,MyISAM 不支持哈希索引,而 InnoDB 中的hash索引是存儲引擎根據(jù)B-Tree索引自建的。因為hash索引本身只需要存儲對應(yīng)的hash值,所以索引的結(jié)構(gòu)十分緊湊,這也讓hash索引查找的速度非???。然而,哈希索引也有限制,如下:
哈希索引只包含哈希值和行指針,而不存儲字段值,所以不能使用索引中的值來避免讀取行(即不能使用哈希索引來做覆蓋索引掃描),不過,訪問內(nèi)存中的行的速度很快(因為memory引擎的數(shù)據(jù)都保存在內(nèi)存里),所以大部分情況下這一點對性能的影響并不明顯。
哈希索引數(shù)據(jù)并不是按照索引列的值順序存儲的,所以也就無法用于排序
哈希索引也不支持部分索引列匹配查找,因為哈希索引始終是使用索引的全部列值內(nèi)容來計算哈希值的。如:數(shù)據(jù)列(a,b)上建立哈希索引,如果只查詢數(shù)據(jù)列a,則無法使用該索引。哈希索引只支持等值比較查詢,如:=,in(),<=>(注意,<>和<=>是不同的操作),不支持任何范圍查詢(必須給定具體的where條件值來計算hash值,所以不支持范圍查詢)。
訪問哈希索引的數(shù)據(jù)非???,除非有很多哈希沖突,當(dāng)出現(xiàn)哈希沖突的時候,存儲引擎必須遍歷鏈表中所有的行指針,逐行進(jìn)行比較,直到找到所有符合條件的行。
如果哈希沖突很多的話,一些索引維護(hù)操作的代價也很高,如:如果在某個選擇性很低的列上建立哈希索引(即很多重復(fù)值的列),那么當(dāng)從表中刪除一行時,存儲引擎需要遍歷對應(yīng)哈希值的鏈表中的每一行,找到并刪除對應(yīng)的引用,沖突越多,代價越大。
redis 為什么快?
完全基于內(nèi)存,絕大多數(shù)請求是純粹的內(nèi)存操作,非??焖?。數(shù)據(jù)存儲在內(nèi)存中,類似于HashMap,具備較快的查找和操作的時間復(fù)雜度O(1)。
數(shù)據(jù)結(jié)構(gòu)簡單,對數(shù)據(jù)操作也簡單,Redis中的數(shù)據(jù)結(jié)構(gòu)是專門進(jìn)行設(shè)計的。
采用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進(jìn)程或者多線程導(dǎo)致的切換而消耗CPU,不用考慮各種鎖的問題,不存在加鎖釋放鎖(上下文的切換),沒有因為可能出現(xiàn)死鎖而導(dǎo)致的性能消耗。
使用多路I/O復(fù)用模型,非阻塞IO。
使用底層模型不同,它們之間底層實現(xiàn)方式以及與客戶端之間的通信的應(yīng)用協(xié)議不一樣,Redis直接自己構(gòu)建了VM機制,因為一般的系統(tǒng)調(diào)用系統(tǒng)函數(shù)的話,會浪費一定的時間去移動和請求(用戶態(tài)和內(nèi)核態(tài)之間的切換)。
未完待續(xù)
本來本文的標(biāo)題是?抖音、騰訊、阿里、美團春招服務(wù)端開發(fā)崗位硬核面試(下),然題目比較多,限于篇幅,只能改成?二,下篇我們繼續(xù)。
這是筆者對題目的解答,讀者如發(fā)現(xiàn)答案有問題,歡迎留言指出,謝謝。
另外幫忙插播一個內(nèi)推崗位,有興趣的同學(xué)可以投簡歷或者后臺和我私聊。
阿里云智能事業(yè)群-ECS資深Java工程師/專家-北京/杭州
大量HC,歡迎來聊。
團隊組合:一群有情有義有夢想的工程師和云計算行業(yè)技術(shù)大牛
產(chǎn)品是啥:全球第三中國第一的公有云服務(wù)平臺,負(fù)責(zé)阿里云 ECS 的資源管理、售賣、資源調(diào)度、資源供給服務(wù),構(gòu)建全球計算力的基礎(chǔ)設(shè)施
崗位職責(zé):
參與建設(shè)大規(guī)模的資源調(diào)度系統(tǒng),承載每天百萬次的 ECS 調(diào)度決策,為每臺 ECS 選擇最佳資源供給
運用數(shù)據(jù)挖掘、數(shù)據(jù)分析和智能算法,構(gòu)建用戶畫像與資源畫像,預(yù)測未來各個區(qū)域不同產(chǎn)品的購買行為和趨勢,為ECS資源供給提供最佳決策,打造云計算的彈性能力與性價比,實現(xiàn)成本與售賣的雙贏
構(gòu)建資源管理系統(tǒng),從宏觀的物理機管理,到微觀的虛擬資源管理,讓每一份資源物盡其用
參與基于 ECS 的產(chǎn)品研發(fā),打造后端能力的新玩法,實現(xiàn)技術(shù)能力的變現(xiàn),物盡其用,讓技術(shù)產(chǎn)生更多社會紅利價值
職位要求:
至少熟悉Java/Python語言的一種或多種,理解該語言涉及的基礎(chǔ)框架,對您使用過的框架能夠了解到它的原理和機制
熟悉linux操作系統(tǒng)、常用工具和命令,熟悉mysql數(shù)據(jù)庫
熟練掌握多線程等高并發(fā)系統(tǒng)編程和優(yōu)化技能;熟悉分布式系統(tǒng)的設(shè)計和應(yīng)用,熟悉分布式、緩存、消息等機制;能對分布式常用技術(shù)進(jìn)行合理應(yīng)用,解決問題
具備快速學(xué)習(xí)能力,較強的團隊溝通和協(xié)作能力,較強的自我驅(qū)動能力
熟悉OpenStack/Kubernetes/Mesos/Borg等平臺經(jīng)驗者優(yōu)先,有大規(guī)模調(diào)度系統(tǒng)、資源管理系統(tǒng)的實際建設(shè)經(jīng)驗者優(yōu)先
