1、String類能不能被繼承?為什么?
不能
因為string類是被final修飾的類,final修飾過的類不能被繼承、final修飾過的變數不能被修改
2、實作單例設計模式(懶漢、餓漢)
//懶漢,顧名思義比較懶,在用的時候才實體化
public class Singleton {
//創建實體,注意,此時沒有new
private static Singleton instance = null;
//構造方法私有化
private Singleton() {}
//公有的靜態方法,回傳實體物件
public static Singleton getInstance() {
if (instance == null) {
//這里才new
instance = new Singleton();
}
return instance;
}
}
//餓漢,顧名思義很饑餓,創建物件的時候就直接new
public class Singleton {
//創建實體的時候就new
private static Singleton instance = new Singleton();
// 私有化構造方法,外部不能new
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
3、簡述Java的反射機制和使用場景
反射是Java的一種機制,可以讓我們在運行時獲取類的資訊
通過反射我們可以獲取到類的所有資訊,比如它的屬性、構造器、方法、注解等
適用于需要動態創建物件的場景
關于反射能說的太多,已單獨出一篇博客來記錄,請出門右轉至信不信十分鐘讓你徹底搞懂java反射
4、什么是記憶體泄漏,怎么確定記憶體泄漏?
概念:記憶體泄漏就是指jvm記憶體沒有及時釋放,用人話說就是使用完的物件沒有被回收,一般造成原因都是編碼不規范,new了很多值為null的物件,然后又不呼叫
怎么確認:linux有個工具叫valgrind,一兩句話說不清楚,單獨拎出來講,移步使用valgrind來檢查記憶體泄漏
5、簡述動態代理和靜態代理
靜態代理:
由程式員創建或由特定工具自動生成源代碼,再對其編譯,在程式運行前,代理類的.class檔案就已經存在了
靜態代理通常只代理一個類
靜態代理事先知道要代理的是什么
動態代理:
在程式運行時,運用反射機制動態創建而成
動態代理是代理一個介面下的多個實作類
動態代理不知道要代理什么東西,只有在運行時才知道
6、手寫生產者消費者模型
倉庫:Warehouse
public class Warehouse {
private volatile int apple = 0;
public synchronized void increace() {
while (apple == 5) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
apple++;
System.out.println("蘋果生產成功!");
notify();
}
public synchronized void decreace() {
while (apple == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
apple--;
System.out.println("蘋果消費成功!");
notify();
}
public static void main(String[] args) {
Warehouse warehouse = new Warehouse();
Consumer con = new Consumer(warehouse);
Producer pro = new Producer(warehouse);
Thread t1 = new Thread(con);
Thread t2 = new Thread(pro);
t1.start();
t2.start();
}
}
生產者:Producer
public class Producer implements Runnable {
private Warehouse warehouse;
public Producer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for( int i=0;i<10;i++)
{
try {
System. out .println("pro i:" +i);
Thread. sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
warehouse.increace();
}
}
}
消費者:Consumer
public class Consumer implements Runnable {
private Warehouse warehouse;
public Consumer(Warehouse warehouse) {
this.warehouse = warehouse;
}
@Override
public void run() {
for( int i=0;i<10;i++)
{
try {
System. out .println("Con: i " +i);
// 這里設定跟上面30不同是為了 倉庫中的蘋果能夠增加,不會生產一個馬上被消費
Thread. sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
warehouse.decreace();
}
}
}
7、Java中介面和抽象類的區別?
1、抽象類只能繼承一次,但是可以實作多個介面
2、介面和抽象類必須實作其中所有的方法,抽象類中如果有未實作的抽象方法,那么子類也需要定義為抽象類,抽象類中可以有非抽象的方法
3、介面中的變數必須用 public static final 修飾,并且需要給出初始值,所以實作類不能重新定義,也不能改變其值,
4、介面中的方法默認是 public abstract,也只能是這個型別,不能是 static,介面中的方法也不允許子類覆寫,抽象類中允許有static 的方法
8、Java中sleep和wait的區別?
1、sleep是Thread的方法,wait是Object的方法
2、sleep方法沒有釋放鎖,而wait方法釋放了鎖
3、wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用
4、sleep必須捕獲例外,而wait,notify和notifyAll不需要捕獲例外
9、Java如何進行高效的陣列拷貝?
Arrays.copyOf或 System.arraycopy,是自己new陣列, 然后for回圈復制效率的兩倍左右
為什么快,因為它們是native方法;
10、工廠模式使用場景
設計模式共23種,按功能可以分為創建型、結構型、行為型,工廠模式屬于創建型模式,主要用于創建物件;
比方說造一輛車
如果不使用工廠模式,我需要造寶馬的時候,就寫一個造寶馬的方法,需要造奔馳的時候就寫一個造奔馳的方法,缺陷很明顯,后期不易維護、代碼冗余、不符合面向物件的思想;
使用工廠模式時,我只需要寫一個造汽車的方法,在內部做一些判斷,如果引數是寶馬我就造寶馬,如果是奔馳我就造奔馳,這樣后期如果造汽車,只需要呼叫它的方法傳入引數就行;
工廠模式能細分為簡單工廠,工廠方法和抽象工廠三種
常見的有簡單工廠和工廠方法兩種, 簡單工廠中包含工廠、產品和具體產品三個角色,其中工廠是整個模式的核心,這個類當中包含有必要的判斷邏輯,可以決定在什么時候創建哪一個產品類的物件,而客戶端則可以免除直接創建產品物件的責任,從這個角度上說簡單工廠實作了對責任的分割,另外,由于客戶端所使用的物件都由工廠生成,并統一轉型為產品型別,所以客戶端無需關心自己得到的是哪一個具體產品,這樣在添加新的具體產品時,就不需要修改客戶端的代碼,從某種程度上也實作了開放-封閉法則,但是每當需要添加新的具體產品時,就需要修改工廠類, 工廠方法使用了面向物件的多型性,保留了簡單工廠的優點,而且克服了它的缺點,首先,在工廠模式當中,核心的工廠類不再負責所有產品的創建,而是將具體的產品創建交給子類去做,這個核心類成了抽象工廠角色,僅僅負責給出具體工廠子類必須實作的介面,這種進一步抽象的結果是,可以允許系統在不修改具體工廠角色的情況下引進新的產品, 在現實的開發當中,典型的例子就是在servcie層中需要得到DAO物件,通常就會抽象出DAO介面作為產品,介面的實作類作為具體產品,然后提供工廠供service使用,這樣就可以分離service和dao的耦合,當DAO添加新的實作類是,service不需要修改,提升系統的擴展性和維護性,
11、成員變數和方法的區別?
成員變數有兩種:實體變數和類變數(也稱靜態變數,靜態域),
成員方法有三種:實體方法,類方法(也稱靜態方法),構造方法(無回傳值,方法名和類名一致),
public class Person {
public static final int defaultAge = 18;//常量,類編譯時放到常量池
public static int age = 18;//類變數,在類加載的準備階段,分配到方法區
private String like;//實體變數,在類被實體化時,分配到堆中
//靜態模塊
static {
System.out.println("唱 跳 rap 籃球");
}
//類方法,分配到方法區
static int getAge() {
return age;
}
public Person() {
//類的構造方法
}
//實體方法,分配到方法區
public String getLike() {
return like;
}
//實體方法
public void setLike(String like) {
this.like = like;
}
}
類變數的特點:
它是該類所有實體共享的屬性,在記憶體中只有一個地方存盤這個變數(在方法區),在類加載的準備階段,分配到方法區,初始化階段正式賦值,
所有實體都可以修改這個類變數的值,(前提是沒有被final修飾)
訪問類變數不用實體化物件,直接通過類可以使用,
生命周期取決于類的生命周期,
類方法的特點:
直接通過類就可以呼叫,
類方法可以直接呼叫類變數和類方法,
類方法不可以直接呼叫實體變數和實體方法,
類方法沒有this,因為沒有實體,
實體變數和實體方法的特點:
都必須通過實體物件才可以訪問,(實體變數位于堆中,生命周期取決于實體的生命周期)
12、Java編譯后的.class檔案包含了哪些內容?
編譯后的.class檔案是一組以8位位元組為基礎單位的二進制流,各個資料專案嚴格按照順序緊湊地排列在.class檔案之中,中間沒有添加任何分隔符;
根據Java虛擬機規范的規定,.class檔案格式采用一種類似于C語言的偽結構來存盤資料,包含無符號數和表:
無符號數:以u1、u2、u4、u8來分別代表1個位元組、2個位元組、4個位元組和8個位元組的無符號數,無符號數可以用來描述數字、索引參考、數量值或者按照UTF-8編碼構成的字串值;
表:由多個無符號數或者其他表作為資料項構成的復合資料型別,所有表都習慣性的以”_info“結尾,
Class檔案的結構沒有分隔符,無論你是數量還是順序,都是嚴格規定的,哪個位元組代表什么含義、長度多少、先后順序如何,都不允許改變,
詳見.class檔案剖析
13、簡述zset實作原理
一兩句話說不清楚,就是一種資料結構,打個比方
1、2、3、4、5、6、7、8、9
要找到8,我得遍歷8次
如果我把1、3、5、7、9拎出來作為一個類似索引的東西
找8的時候,先在索引里面找,8在7和9中間,找到7以后再遍歷1次就找到了8,總共遍歷5次
而這個1、3、5、7、9的索引還可以拎個1、5、9出來,以此類推
回到zset,它維護了兩個元素,一個是 dict,用來維護資料到分數的關系,一個是zskiplist,用來維護分數所在鏈表的關系
14、簡述協程的優缺點
協程是一種輕量級執行緒
優點:
跨平臺
跨體系架構
無需執行緒背景關系切換的開銷
無需原子操作鎖定及同步的開銷
方便切換控制流,簡化編程模型
高并發+高擴展性+低成本:一個CPU支持上萬的協程都不是問題,所以很適合用于高并發處理,
缺點:
無法利用多核資源,協程的本質是個單執行緒,它不能同時將 單個CPU 的多個核用上,協程需要和行程配合才能運行在多CPU上.當然我們日常所撰寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用,
進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程式,這一點和事件驅動一樣,可以使用異步IO操作來解決
15、如何判斷一個hash函式好不好?
1、計算性能
2、離散性
hash演算法一定要把資料哈希均勻,不能全部擠在一坨,對hash演算法不太清楚的必須看看這篇文章,我以前花了整整一天時間寫的,舉了很多例,真正搞懂hashCode和hash演算法
16、http中get和post的區別?
GET在瀏覽器回退時是無害的,而POST會再次提交請求,
GET產生的URL地址可以被Bookmark,而POST不可以,
GET請求會被瀏覽器主動cache,而POST不會,除非手動設定,
GET請求只能進行url編碼,而POST支持多種編碼方式,
GET請求引數會被完整保留在瀏覽器歷史記錄里,而POST中的引數不會被保留,
GET請求在URL中傳送的引數是有長度限制的,而POST么有,
對引數的資料型別,GET只接受ASCII字符,而POST沒有限制,
GET比POST更不安全,因為引數直接暴露在URL上,所以不能用來傳遞敏感資訊,
GET引數通過URL傳遞,POST放在Request body中,
(本標準答案參考自w3schools)
然而實際上,它們的本質都是 TCP 連接,并無區別,上面的答案純粹是為了應付面試官,真正導致產生區別的原因是 HTTP 的規定以及瀏覽器/服務器的限制,這才導致它們在應用程序中可能會有所不同,
17、解決hash沖突的方式?
拉鏈法
代表作:hashMap
原理:把所有hash值相同的元素放到鏈表中
再哈希法
原理:產生沖突時,對結果再進行一次hash,直到沒有hash沖突,一般沒人用,太魯莽了,而且治標不治本,理論上永遠不能徹底解決hash沖突
開放地址法
1、線性探測法
2、線性補償探測法
3、偽隨機探測
18、簡述裝飾者模式和配接器模式
裝飾者模式
概括:在不改變現有物件結構的情況下,動態地給該物件增加一些職責(即增加其額外功能),它屬于結構型模式,mybatis-plus的QueryWrapper就是裝飾者模式的體現
配接器模式
概況:將一個介面轉換成用戶需要的另一個介面,使介面不兼容的那些類可以一起作業,也屬于結構型模式,SpringMvc中的HandlerAdapter就是配接器模式的體現
19、hashCode和equals方法的聯系
java規定:
如果兩個物件的hashCode()相等,那么他們的equals()不一定相等,
如果兩個物件的equals()相等,那么他們的hashCode()必定相等,
對hash演算法不太清楚的同學必須看看這篇文章,我以前花了整整一天時間寫的,舉了很多例,真正搞懂hashCode和hash演算法
20、什么是重寫和多載?
1、重寫是在子類存在方法與父類的方法的名字相同,而且引數的個數與型別一樣,回傳值也一樣的方法,就稱為重寫(Override)
2、多載是一個類中定義了多個方法名相同,而他們的引數的數量不同或數量相同而型別和次序不同,則稱為方法的多載(Overload)
3、多載是一個類的多型性表現,而重寫是子類與父類的一種多型性表現
21、Java有幾種基本資料型別?分別占用多少位元組?
| 資料型別 | 占用位元組 |
|---|---|
| byte | 1 |
| short | 2 |
| int | 4 |
| long | 8 |
| char | 2(c語言是1位元組,可以存盤一個漢字) |
| float | 4 |
| double | 8 |
| boolean | 1/8 |
22、Java例外有哪些型別?
Java所有的例外都繼承至Throwable,分為Error和Exception兩大類,其中:
Error是系統級錯誤,在代碼層無法處理,常見的有StackOverflowError、OutOfMemoryError等;
Exception是例外,通常可以在代碼層處理,常見的有NullpointerException、ClassCaseException、IndexOutOfBoundsException等
23、簡述jvm記憶體模型?
程式計數器:執行緒私有,各執行緒之間獨立儲存,互不影響,若當前執行的是Java方法,則記錄的就是當前執行指令的地址,若是native方法,則為空;
java虛擬機堆疊:執行緒私有,每個方法在執行時都會創建一個堆疊幀,方法執行程序就是堆疊幀在虛擬機堆疊中從入堆疊到出堆疊的程序,入堆疊表示方法開始被呼叫,出堆疊表示方法執行完畢,堆疊幀用于保存方法內部區域變數、運算元、方法回傳值、動態鏈接;我們平時說的堆疊其實一般就是指區域變數區:用于存放方法引數、方法內定義的區域變數,還有已知的八大基本資料型別、物件參考、回傳值地址;
本地方法堆疊:執行緒私有,和虛擬機堆疊相似,區別在于虛擬機堆疊的服務物件是java方法,而本地方法堆疊是本地方法;
堆:執行緒共享,在虛擬機啟動的時候創建,用于存放物件實體,堆是GC管理的主要區域;
方法區:也叫永久區,用于存放類資訊、常量(“abc”,"123"等)、靜態變數(static變數)、即時編譯器編譯后的代碼,其中常量存盤在運行時常量池中;(jdk1.8已經將方法區去掉了,將方法區移動到直接記憶體);
再細分的話還有運行時常量池:運行時常量池是方法區的一部分,用于存放編譯期生成的各種常量(“abc”,"123"等)和符號參考;
24、簡述GC機制,新生代和老年代的區別?
垃圾回收是java管理記憶體的一種機制,作用是清理無用物件避免記憶體泄漏,gc主要發生在java堆上,而堆可以細分為新生代和老年代(分代是為了提高gc效率,其實不分代也可以完成gc,只不過gc機制會對堆的所有區域進行掃描,浪費資源),新生代還可以細分為三個虛擬的區,Eden區、FromSurvivor區、ToSurvivor區,一開始物件都在Eden區,Eden區的物件經過一次新生代gc(復制演算法)后若還能存活,就會移動到survivor區(ToSurvivor區),在此次新生代gc時,在survivor發生的改變就是,From區中的物件會根據年齡來決定去留,達到閾值,會移動到老年代,沒達到就移動到To區,經過此次新生代gc,Eden和From區都已被清空,From區和To區會互換;
新生代gc(Minor GC)觸發時機:當Eden區沒有足夠空間進行分配時;
老年代gc(Major GC/Full GC)觸發時機:當老年代中沒有足夠的記憶體空間來存放物件時,虛擬機會發起一次Major GC/Full GC,只要老年代的連續空間大于新生代物件總大小或者歷次晉升的平均大小就會進行Minor GC,否則將進行Full CG,
25、如何優化JVM頻繁Minor GC?
適當擴大堆記憶體太小
26、簡述類加載機制,什么是雙親委派?
1、加載:將源檔案(代碼)編譯成.class檔案,傳遞給類加載器
2、驗證:類加載器拿到這些位元組碼檔案后開始執行檢查(通過本地類別庫),確保加載進來的位元組流格式符合java虛擬機要求
3、準備:對類變數(不是實體變數)分配記憶體,并且給一個默認初始化的值(0或者null)
4、決議:將常量池中的符號參考加載成直接參考(如string str = new string(“123”),str就是符號參考,123是直接參考)
5、初始化:對類的靜態變數初始化為指定的值,執行靜態代碼塊
雙親委派機制:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,
因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載,
27、synchronized底層實作原理?它與lock相比有什么優缺點?
首先那些說看過synchronized原始碼的基本都是大傻逼,synchronized根本點不進去,想弄懂它的實作原理,我們只能通過看編譯好的位元組碼檔案
原理:
基于物件的監視器(ObjectMonitor),我們在位元組碼檔案里面可以看到,在同步方法執行前后,有兩個指令,方法前monitorenter,方法后monitorexit;
關于位元組碼一兩句話說不清楚,我單獨寫了篇synchronized底層實作原理,保證你五分鐘看懂
與lock對比:
1、synchronized不需要手動釋放鎖,lock需要在鎖用完后進行onLock;
2、synchronized只能是默認的非公平鎖,lock可以指定使用公平鎖或者非公平鎖;
3、lock提供的Condition(條件)可以指定喚醒哪些執行緒,而synchronized只能隨機喚醒一個或者全部喚醒;
28、jvm指令重排原因?怎么避免?
原因:計算機記憶體操作速度遠慢于CPU運行速度,所以就造成CPU空置,為了將提高CPU利用率,虛擬機會按照自己的一些規則會跳過執行慢的代碼,去執行快的代碼(即對代碼重新排序),從而提升jvm的整體性能,
怎么避免:給關鍵的代碼加上volatile關鍵字,所謂關鍵,就是會被執行順序影響結果,
volatile關鍵字的三個特征是:執行緒可見、不具備原子性、禁止指令重排,volatile 的讀性能消耗與普通變數幾乎相同,但是寫操作稍慢,因為它需要在本地代碼中插入許多記憶體屏障指令來保證處理器不發生亂序執行,
29、ReentrantLock是什么?有什么用?怎么用?和synchronized的區別?
ReentrantLock是Lock的一個子類
作用:
用來給資源加鎖,避免高并發造成的資料例外問題;
使用:
public void lockTest() {
//創建lock實體,可傳引數true或者false,表示是否是公平鎖,默認是非公平鎖
ReentrantLock lock = new ReentrantLock();
//在需要保證同步的代碼前lock
lock.lock();
int i = 0;
i++;
//代碼后unlock
lock.unlock();
}
與synchronized主要區別,lock需要手動釋放鎖,可以指定公平鎖或者非公平鎖
30、volatile關鍵字解決了什么問題?實作原理是什么?
解決了什么問題:
1、保證了變數的可見性
2、禁止指令重排
比如i=i+1,單執行緒操作沒問題,如果使用多執行緒,比如兩個執行緒,執行這段代碼后(i初始值為0),i應該等于2,但是如果不用volatile修飾變數i,結果會等于1,初始時,兩個執行緒分別讀取i的值存入各自所在的CPU的高速快取當中,然后執行緒1進行加1操作,然后把i的最新值1寫入到記憶體,此時執行緒2的高速快取當中i的值還是0,進行加1操作之后,i的值為1,然后執行緒2把i的值寫入記憶體,
實作原理
基于記憶體屏障,關于記憶體屏障,搞java開發的同學在開發中不可能接觸到,所以不用關心太多,知道記憶體屏障有什么作用,面試官問到你能唬住他就行了,因為面試官自己也不懂
(1)它確保指令重排序時不會把其后面的指令排到記憶體屏障前面,也不會把前面的指令排到記憶體屏障后面,總之一句話,他能保證指令按照我們希望的順序執行;
(2)它會強制將對快取的修改操作立即寫入主存,使得其它執行緒能立馬發現;
31、ThreadLocal實作原理?
ThreadLocal中文名叫執行緒變數,它底層維護了一個map,key就是當前的ThreadLocal物件(可以理解為當前執行該段代碼的執行緒),value就是你set的值,這個map保證了各個執行緒的資料互不干擾;
想進一步了解ThreadLocal的同學請移步我憑ThreadLocal唬住了京東面試官
32、執行緒池實作原理?
要說實作原理,必須得把執行緒池的幾個引數徹底搞懂,不要死記硬背
1、corePoolSize(必填):核心執行緒數, 2、maximumPoolSize(必填):最大執行緒數,
3、keepAliveTime(必填):執行緒空閑時長,如果超過該時長,非核心執行緒就會被回收,
4、unit(必填):指定keepAliveTime的時間單位,常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分),
5、workQueue(必填):任務佇列,通過執行緒池的execute()方法提交的Runnable物件將存盤在該佇列中,
6、threadFactory(可選):執行緒工廠,一般就用默認的,
7、handler(可選):拒絕策略,當執行緒數達到最大執行緒數時就要執行飽和策略,
貼心的博主單獨給你們寫了篇講執行緒池的,五分鐘就能看懂的那種,請移步執行緒池實作原理吐血總結
33、java是如何實作執行緒安全的?哪些資料結構是執行緒安全的?
1、鎖機制:用synchronize、lock給共享資源加鎖;
2、使用java提供的安全類:java.util.concurrent包下的類自身就是執行緒安全的,在保證安全的同時還能保證性能;
執行緒安全的資料結構:
常見的有Atomicinteger、ConcurrentHashMap,太多了,截個圖吧,大家可以自己去java.util.concurrent包下看

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/256369.html
標籤:java
上一篇:Dubbo基礎理解



