整整7天,梳理 Java開發(fā)2022年(圖文+代碼)面試題及答案
應各位小伙伴要求,今天抽空來整理整理java開發(fā)面試中的那些事情,幫助那些正在找工作或想跳槽找工作的伙伴們!分享目前Java常見的面試問題以及答案。
本文所有答案相對權(quán)威,有不對之處還請不吝賜教。篇幅較長,后續(xù)還會繼續(xù)更新~
需要這份文檔的小伙伴直接私信【37】就可以無償免費領(lǐng)取,絕不需要關(guān)注什么公眾號!
Spring由哪些模塊組成?
Spring 總共大約有 20 個模塊, 由 1300 多個不同的文件構(gòu)成。 而這些組件被分別整合在一起
核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和設備支持
(Instrmentation)
數(shù)據(jù)訪問與集成(Data Access/Integeration)
Web
消息(Messaging)
Test
等 6 個模塊中。 以下是 spring 5 的模塊結(jié)構(gòu)圖:
- spring core:提供了框架的基本組成部分,包括控制反轉(zhuǎn)(Inversion of Control,IOC)和依賴
注入(Dependency Injection,DI)功能。 - spring beans:提供了beanFactory,是工廠模式的一個經(jīng)典實現(xiàn),Spring將管理對象稱為
Bean。 - spring context:構(gòu)建于 core 封裝包基礎上的 context 封裝包,提供了一種框架式的對象訪問方式
法。 - spring jdbc:提供了一個JDBC的抽象層,消除了煩瑣的JDBC編碼和數(shù)據(jù)庫廠商特有的錯誤代碼解
析, 用于簡化JDBC。 - spring aop:提供了面向切面的編程實現(xiàn),讓你可以自定義攔截器、切點等。
- spring Web:提供了針對 Web 開發(fā)的集成特性,例如文件上傳,利用 servlet listeners 進行 ioc容器初始化和針對 Web 的 ApplicationContext。
- spring test:主要為測試提供支持的,支持使用JUnit或TestNG對Spring組件進行單元測試和集成測試
Spring的優(yōu)缺點是什么?
優(yōu)點
- 方便解耦,簡化開發(fā)
Spring就是一個大工廠,可以將所有對象的創(chuàng)建和依賴關(guān)系的維護,交給Spring管理。 - AOP編程的支持
Spring提供面向切面編程,可以方便的實現(xiàn)對程序進行權(quán)限攔截、運行監(jiān)控等功能。 - 聲明式事務的支持
只需要通過配置就可以完成對事務的管理,而無需手動編程。 - 方便程序的測試
Spring對Junit4支持,可以通過注解方便的測試Spring程序。 - 方便集成各種優(yōu)秀框架
Spring不排斥各種優(yōu)秀的開源框架,其內(nèi)部提供了對各種優(yōu)秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。 - 降低JavaEE API的使用難度
Spring對JavaEE開發(fā)中非常難用的一些API(JDBC、JavaMail、遠程調(diào)用等),都提供了封裝,使這些API應用難度大大降低。
缺點
- Spring明明一個很輕量級的框架,卻給人感覺大而全
- Spring依賴反射,反射影響性能
- 使用門檻升高,入門Spring需要較長時間
Spring 應用程序有哪些不同組件?
Spring 應用一般有以下組件:
- 作為一個成熟的 Spring Web 應用程序。
- 作為第三方 Web 框架,使用 Spring Frameworks 中間層。
- 作為企業(yè)級 Java Bean,它可以包裝現(xiàn)有的 POJO(Plain Old Java Objects)。
用于遠程使用。
IOC的優(yōu)點是什么?
- IOC 或 依賴注入把應用的代碼量降到最低。
- 它使應用容易測試,單元測試不再需要單例和JNDI查找機制。
- 最小的代價和最小的侵入性使松散耦合得以實現(xiàn)。
- IOC容器支持加載服務時的餓漢式初始化和懶加載。
Spring IOC 的實現(xiàn)機制
Spring 中的 IOC 的實現(xiàn)原理就是工廠模式加反射機制。
示例:
interface Fruit {public abstract void eat();}class Apple implements Fruit {public void eat(){System.out.println("Apple");}}class Orange implements Fruit {public void eat(){System.out.println("Orange");}}class Factory {public static Fruit getInstance(String ClassName) {Fruit f=null;try {f=(Fruit)Class.forName(ClassName).newInstance();} catch (Exception e) {e.printStackTrace();}return f;}}class Client {public static void main(String[] a) {Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple");if(f!=null){f.eat();}}}
Spring如何處理線程并發(fā)問題?
- 在一般情況下,只有無狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域,因為Spring對一些Bean中非線程安全狀態(tài)采用ThreadLocal進行處理,解決線程安全問題。
- ThreadLocal和線程同步機制都是為了解決多線程中相同變量的訪問沖突問題。同步機制采用了“時間換空間”的方式,僅提供一份變量,不同的線程在訪問前需要獲取鎖,沒獲得鎖的線程則需要排隊。而ThreadLocal采用了“空間換時間”的方式。
- ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數(shù)據(jù)的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。
解釋Spring框架中bean的生命周期
- 在傳統(tǒng)的Java應用中,bean的生命周期很簡單。使用Java關(guān)鍵字new進行bean實例化,然后該bean就可以使用了。一旦該bean不再被使用,則由Java自動進行垃圾回收。相比之下,Spring容器中的bean的生命周期就顯得相對復雜多了。正確理解Spring bean的生命周期非常重要,因為你或許要利用Spring提供的擴展點來自定義bean的創(chuàng)建過程。下圖展示了bean裝載到Spring應用上
下文中的一個典型的生命周期過程。
- bean在Spring容器中從創(chuàng)建到銷毀經(jīng)歷了若干階段,每一階段都可以針對Spring如何管理bean進行個性化定制。
- 正如你所見,在bean準備就緒之前,bean工廠執(zhí)行了若干啟動步驟。
我們對上圖進行詳細描述:
- Spring對bean進行實例化;
- Spring將值和bean的引用注入到bean對應的屬性中;
- 如果bean實現(xiàn)了BeanNameAware接口,Spring將bean的ID傳遞給setBean-Name()方法;
- 如果bean實現(xiàn)了BeanFactoryAware接口,Spring將調(diào)用setBeanFactory()方法,將BeanFactory容器實例傳入;
- 如果bean實現(xiàn)了ApplicationContextAware接口,Spring將調(diào)用setApplicationContext()方法,將bean所在的應用上下文的引用傳入進來;
- 如果bean實現(xiàn)了BeanPostProcessor接口,Spring將調(diào)用它們的postProcessBeforeInitialization()方法;
- 如果bean實現(xiàn)了InitializingBean接口,Spring將調(diào)用它們的after-PropertiesSet()方法。類似地,如果bean使用initmethod聲明了初始化方法,該方法也會被調(diào)用;
- 如果bean實現(xiàn)了BeanPostProcessor接口,Spring將調(diào)用它們的post-ProcessAfterInitialization()方法;
- 此時,bean已經(jīng)準備就緒,可以被應用程序使用了,它們將一直駐留在應用上下文中,直到該應用上下文被銷毀;
- 如果bean實現(xiàn)了DisposableBean接口,Spring將調(diào)用它的destroy()接口方法。同樣,如果bean使用destroy-method聲明了銷毀方法,該方法也會被調(diào)用。
現(xiàn)在你已經(jīng)了解了如何創(chuàng)建和加載一個Spring容器。但是一個空的容器并沒有太大的價值,在你把東西放進去之前,它里面什么都沒有。為了從Spring的DI(依賴注入)中受益,我們必須將應用對象裝配進Spring容器中。
什么是基于Java的Spring注解配置? 給一些注解的例子
- 基于Java的配置,允許你在少量的Java注解的幫助下,進行你的大部分Spring配置而非通過XML文件。
- 以@Configuration 注解為例,它用來標記類可以當做一個bean的定義,被Spring IOC容器使用。
- 另一個例子是@Bean注解,它表示此方法將要返回一個對象,作為一個bean注冊進Spring應用上下文。
@Configurationpublic class StudentConfig {@Beanpublic StudentBean myStudent() {return new StudentBean();}}
MySQL由哪些部分組成, 分別用來做什么
1. Server
- 連接器: 管理連接, 權(quán)限驗證.
- 分析器: 詞法分析, 語法分析.
- 優(yōu)化器: 執(zhí)行計劃生成, 索引的選擇.
- 執(zhí)行器: 操作存儲引擎, 返回執(zhí)行結(jié)果.
2. 存儲引擎: 存儲數(shù)據(jù), 提供讀寫接口.
MySQL怎么恢復半個月前的數(shù)據(jù)
通過整庫備份 binlog進行恢復. 前提是要有定期整庫備份且保存了binlog日志.
一千萬條數(shù)據(jù)的表, 如何分頁查詢
數(shù)據(jù)量過大的情況下, limit offset 分頁會由于掃描數(shù)據(jù)太多而越往后查詢越慢. 可以配合當前頁最后一條ID進行查詢, SELECT * FROM T WHERE id > #{ID} LIMIT #{LIMIT} . 當然, 這種情況下ID必須是有序的, 這也是有序ID的好處之一
SQL 語言包括哪幾部分?每部分都有哪些操作關(guān)鍵字?
SQL 語言包括數(shù)據(jù)定義(DDL)、數(shù)據(jù)操縱(DML),數(shù)據(jù)控制(DCL)和數(shù)據(jù)查詢( DQL) 四個部分。
數(shù)據(jù)定義: Create Table,Alter Table,Drop Table, Craete/Drop Index 等數(shù)據(jù)操縱: Select
,insert,update,delete,數(shù)據(jù)控制: grant,revoke 數(shù)據(jù)查詢: select
簡單 Linux 文件系統(tǒng)?
在 Linux 操作系統(tǒng)中,所有被操作系統(tǒng)管理的資源,例如網(wǎng)絡接口卡、磁盤驅(qū)動器、打印機、輸入輸出設備、普通文件或是目錄都被看作是一個文件。
- 也就是說在 Linux 系統(tǒng)中有一個重要的概念:一切都是文件。其實這是 Unix 哲學的一個體現(xiàn),而Linux 是重寫 Unix 而來,所以這個概念也就傳承了下來。在 Unix 系統(tǒng)中,把一切資源都看作是文件,包括硬件設備。UNIX系統(tǒng)把每個硬件都看成是一個文件,通常稱為設備文件,這樣用戶就可以用讀寫文件的方式實現(xiàn)對硬件的訪問。
- Linux 支持 5 種文件類型,如下圖所示:
Linux 的目錄結(jié)構(gòu)是怎樣的?
這個問題,一般不會問。更多是實際使用時,需要知道。
- Linux 文件系統(tǒng)的結(jié)構(gòu)層次鮮明,就像一棵倒立的樹,最頂層是其根目錄:
Unix和Linux有什么區(qū)別?
Linux和Unix都是功能強大的操作系統(tǒng),都是應用廣泛的服務器操作系統(tǒng),有很多相似之處,甚至有一部分人錯誤地認為Unix和Linux操作系統(tǒng)是一樣的,然而,事實并非如此,以下是兩者的區(qū)別。
- 開源性
Linux是一款開源操作系統(tǒng),不需要付費,即可使用;Unix是一款對源碼實行知識產(chǎn)權(quán)保護的傳統(tǒng)商業(yè)軟件,使用需要付費授權(quán)使用。 - 跨平臺性
Linux操作系統(tǒng)具有良好的跨平臺性能,可運行在多種硬件平臺上;Unix操作系統(tǒng)跨平臺性能較弱,大多需與硬件配套使用。 - 可視化界面
Linux除了進行命令行操作,還有窗體管理系統(tǒng);Unix只是命令行下的系統(tǒng)。 - 硬件環(huán)境
Linux操作系統(tǒng)對硬件的要求較低,安裝方法更易掌握;Unix對硬件要求比較苛刻,按照難度較大。 - 用戶群體
Linux的用戶群體很廣泛,個人和企業(yè)均可使用;Unix的用戶群體比較窄,多是安全性要求高
的大型企業(yè)使用,如銀行、電信部門等,或者Unix硬件廠商使用,如Sun等。
相比于Unix操作系統(tǒng),Linux操作系統(tǒng)更受廣大計算機愛好者的喜愛,主要原因是Linux操作系統(tǒng)具有Unix操作系統(tǒng)的全部功能,并且能夠在普通PC計算機上實現(xiàn)全部的Unix特性,開源免費的特性,更容易普及使用!
什么是 Linux 內(nèi)核?
- Linux 系統(tǒng)的核心是內(nèi)核。內(nèi)核控制著計算機系統(tǒng)上的所有硬件和軟件,在必要時分配硬件,并根據(jù)需要執(zhí)行軟件。
- 系統(tǒng)內(nèi)存管理
- 應用程序管理
- 硬件設備管理
- 文件系統(tǒng)管理
Linux 的體系結(jié)構(gòu)
- 從大的方面講,Linux 體系結(jié)構(gòu)可以分為兩塊:
- 用戶空間(User Space) :用戶空間又包括用戶的應用程序(User Applications)、C 庫(C Library) 。
- 內(nèi)核空間(Kernel Space) :內(nèi)核空間又包括系統(tǒng)調(diào)用接口(System Call Interface)、內(nèi)核(Kernel)、平臺架構(gòu)相關(guān)的代碼(Architecture-Dependent Kernel Code) 。
為什么 Linux 體系結(jié)構(gòu)要分為用戶空間和內(nèi)核空間的原因?
1、現(xiàn)代 CPU 實現(xiàn)了不同的工作模式,不同模式下 CPU 可以執(zhí)行的指令和訪問的寄存器不同。
2、Linux 從 CPU 的角度出發(fā),為了保護內(nèi)核的安全,把系統(tǒng)分成了兩部分。
用戶空間和內(nèi)核空間是程序執(zhí)行的兩種不同的狀態(tài),我們可以通過兩種方式完成用戶空間到內(nèi)核空
間的轉(zhuǎn)移:
- 系統(tǒng)調(diào)用;
- 硬件中斷。
JVM內(nèi)存模型的相關(guān)知識了解多少,比如重排序,內(nèi)存屏障,happen-before,主內(nèi)存,工作內(nèi)存。
思路: 先畫出Java內(nèi)存模型圖,結(jié)合例子volatile ,說明什么是重排序,內(nèi)存屏障,最好能給面試官寫以下demo說明。
1)Java內(nèi)存模型圖:
Java內(nèi)存模型規(guī)定了所有的變量都存儲在主內(nèi)存中,每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了該線程中是用到的變量的主內(nèi)存副本拷貝,線程對變量的所有操作都必須在工作內(nèi)存中進行,而不能直接讀寫主內(nèi)存。不同的線程之間也無法直接訪問對方工作內(nèi)存中的變量,線程間變量的傳遞均需要自己的工作內(nèi)存和主存之間進行數(shù)據(jù)同步進行。
2)指令重排序。
在這里,先看一段代碼
public class PossibleReordering {static int x = 0, y = 0;static int a = 0, b = 0;public static void main(String[] args) throws InterruptedException {Thread one = new Thread(new Runnable() { public void run() { a = 1; x = b; }});Thread other = new Thread(new Runnable() { public void run() { b = 1; y = a; }}); one.start();other.start(); one.join();other.join(); System.out.println(“(” x “,” y “)”);}
運行結(jié)果可能為(1,0)、(0,1)或(1,1),也可能是(0,0)。因為,在實際運行時,代碼指令可能并不是嚴格按照代碼語句順序執(zhí)行的。大多數(shù)現(xiàn)代微處理器都會采用將指令亂序執(zhí)行(out-of-order execution,簡稱OoOE或OOE)的方法,在條件允許的情況下,直接運行當前有能力立即執(zhí)行的后續(xù)指令,避開獲取下一條指令所需數(shù)據(jù)時造成的等待3。通過亂序執(zhí)行的技術(shù),處理器可以大大提高執(zhí)行效率。而這就是指令重排。
3)內(nèi)存屏障
內(nèi)存屏障,也叫內(nèi)存柵欄,是一種CPU指令,用于控制特定條件下的重排序和內(nèi)存可見性問題。
- LoadLoad屏障:對于這樣的語句Load1; LoadLoad; Load2,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
- StoreStore屏障:對于這樣的語句Store1; StoreStore; Store2,在Store2及后續(xù)寫入操作執(zhí)行前,保證Store1的寫入操作對其它處理器可見。
- LoadStore屏障:對于這樣的語句Load1; LoadStore; Store2,在Store2及后續(xù)寫入操作被刷出前,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
- StoreLoad屏障:對于這樣的語句Store1; StoreLoad; Load2,在Load2及后續(xù)所有讀取操作執(zhí)行
前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。 在大多數(shù)處理器的實現(xiàn)中,這個屏障是個萬能屏障,兼具其它三種內(nèi)存屏障的功能。
4)happen-before原則
- 單線程happen-before原則:在同一個線程中,書寫在前面的操作happen-before后面的操作。鎖的happen-before原則:同一個鎖的unlock操作happen-before此鎖的lock操作。
- volatile的happen-before原則:對一個volatile變量的寫操作happen-before對此變量的任意操作(當然也包括寫操作了)。
- happen-before的傳遞性原則:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
- 線程啟動的happen-before原則:同一個線程的start方法happen-before此線程的其它方法。
- 線程中斷的happen-before原則 :對線程interrupt方法的調(diào)用happen-before被中斷線程的檢測到中斷發(fā)送的代碼。
- 線程終結(jié)的happen-before原則: 線程中的所有操作都happen-before線程的終止檢測。
- 對象創(chuàng)建的happen-before原則: 一個對象的初始化完成先于他的finalize方法調(diào)用。
怎么打出線程棧信息
思路: 可以說一下jps,top ,jstack這幾個命令,再配合一次排查線上問題進行解答。
- 輸入jps,獲得進程號。
- top -Hp pid 獲取本進程中所有線程的CPU耗時性能
- jstack pid命令查看當前java進程的堆棧狀態(tài)
- 或者 jstack -l > /tmp/output.txt 把堆棧信息打到一個txt文件。
- 可以使用fastthread 堆棧定位,fastthread.io/