【BATJ面試必會】JAVA面試到底須要掌握什麼?【上】

[參考地址:https://mp.weixin.qq.com/s/y-kBBLDSOVH0VnAQ2XcQlw]html

秋招幾個月累積的知識點,東西太多,分兩篇發,儘可能用(*)和加粗標註出高頻知識點, 都是面試問過的或筆試考過的java

Java基礎知識(*)node

  • https://blog.csdn.net/qq_16633405/article/details/79211002git

Spring Boot 啓動 流程(*)github

  • https://juejin.im/post/5b679fbc5188251aad213110#heading-0面試

Spring 一些面試題(*)redis

  • https://www.ctolib.com/topics-35589.html算法

匿名內部類編譯class(*)spring

  • https://blog.csdn.net/lazyer_dog/article/details/50669473sql

爲何集合類沒有實現Cloneable和Serializable接口?

  • https://www.nowcoder.com/questionTerminal/2a4902f67d5b49b6b4c05f9d7e422caf

自動裝箱原理

  • https://www.jianshu.com/p/0ce2279c5691

final關鍵字

  • http://www.importnew.com/7553.html

基於Redis的分佈式鎖

  • https://segmentfault.com/a/1190000012919740

數據庫分佈式鎖

  • http://www.hollischuang.com/archives/1716

防備DDOS攻擊(*)

  • https://www.zhihu.com/question/19581905

何時Mysql調用行鎖?(*)

  • https://blog.csdn.net/songwei128/article/details/43418343

CMS,G1(*)

  • https://crowhawk.github.io/2017/08/15/jvm_3/

內部類,外部類互訪(*)

  • https://blog.csdn.net/jueblog/article/details/13434551

  • https://blog.csdn.net/Van_L_/article/details/54667365

設計模式(*)
熟背單例模式和工廠模式,會寫適配器和建造者也行

  • https://www.jianshu.com/p/8a293e4a888e

  • https://segmentfault.com/a/1190000004255439

深拷貝,淺拷貝(*)

  • https://segmentfault.com/a/1190000010648514

泛型擦除

  • https://blog.csdn.net/briblue/article/details/76736356

Java 8 Stream, 函數編程

  • https://www.jianshu.com/p/0c07597d8311

  • https://www.jianshu.com/p/9bd647bcf1e3

中斷線程

  • https://www.jianshu.com/p/264d4e1b76af

Lock,tryLock,lockInterruptibly區別

  • https://blog.csdn.net/u013851082/article/details/70140223

JUC

  • http://www.cnblogs.com/chenpi/p/5358579.html#_label2

  • http://www.cnblogs.com/chenpi/p/5614290.html

NIO

  • https://blog.csdn.net/u013063153/article/details/76473578

  • https://www.jianshu.com/p/052035037297

  • https://segmentfault.com/a/1190000006824196

Start和run區別(*)

  • https://blog.csdn.net/qq_36544760/article/details/79380963

jvm內存屏障

  • https://www.jianshu.com/p/2ab5e3d7e510

Java構造器能被重載,可是不能被重寫(*)

  • https://blog.csdn.net/weixin_36513603/article/details/54968094

HttpSession

  • https://blog.csdn.net/zy2317878/article/details/80275463

Thread類的方法

  • https://blog.csdn.net/gxx_csdn/article/details/79210192

String是值類型,仍是引用類型(*)

  • https://blog.csdn.net/a220315410/article/details/27743607

Redis 實現消息隊列

  • 消息/訂閱+List

  • https://segmentfault.com/a/1190000012244418

minor gc full gc 區別(*)

  • https://blog.csdn.net/u010796790/article/details/52213708

Java如何查看死鎖

  • https://blog.csdn.net/u014039577/article/details/52351626

  • https://juejin.im/post/5aaf6ee76fb9a028d3753534

c3p0,dbcp與druid

  • https://blog.csdn.net/qq_34359363/article/details/72763491

Spring Bean 生命週期(*)

  • https://www.jianshu.com/p/3944792a5fff

Spring的BeanFactory和ApplicationContext的區別?

  • ApplicationContext是實現類,繼承ListableBeanFactory(繼承BeanFactory),功能更多

  • ApplicationContext默認當即加載,BeanFactory懶加載

  • https://my.oschina.net/yao00jun/blog/215642

  • https://blog.csdn.net/qq_36748278/article/details/78264764

Java 如何有效地避免OOM:善於利用軟引用和弱引用

  • https://www.cnblogs.com/dolphin0520/p/3784171.html

分佈式數據庫主鍵生成策略(*)

  • https://www.jianshu.com/p/a0a3aa888a49

  • https://tech.meituan.com/MT_Leaf.html

String底層(*)

  • https://blog.csdn.net/yadicoco49/article/details/77627302

count(1)、count(*)與count(列名)的執行區別

  • https://blog.csdn.net/iFuMI/article/details/77920767

主鍵,惟一索引區別

  • 1)主鍵必定會建立一個惟一索引,可是有惟一索引的列不必定是主鍵;

  • 2)主鍵不容許爲空值,惟一索引列容許空值;

  • 3)一個表只能有一個主鍵,可是能夠有多個惟一索引;

  • 4)主鍵能夠被其餘表引用爲外鍵,惟一索引列不能夠;

  • 5)主鍵是一種約束,而惟一索引是一種索引,是表的冗餘數據結構,二者有本質的差異

死鎖
產生死鎖的四個必要條件:

  • 互斥條件:一個資源每次只能被一個進程使用。

  • 佔有且等待:一個進程因請求資源而阻塞時,對已得到的資源保持不放。

  • 不可強行佔有:進程已得到的資源,在末使用完以前,不能強行剝奪。

  • 循環等待條件:若干進程之間造成一種頭尾相接的循環等待資源關係。

避免死鎖:
https://segmentfault.com/a/1190000000378725

  • 確保全部的線程都是按照相同的順序得到鎖,那麼死鎖就不會發生.

  • 另一個能夠避免死鎖的方法是在嘗試獲取鎖的時候加一個超時時間,這也就意味着在嘗試獲取鎖的過程當中若超過了這個時限該線程則放棄對該鎖請求。若一個線程沒有在給定的時限內成功得到全部須要的鎖,則會進行回退並釋放全部已經得到的鎖,而後等待一段隨機的時間再重試。

  • 死鎖檢測是一個更好的死鎖預防機制,它主要是針對那些不可能實現按序加鎖而且鎖超時也不可行的場景。

樂觀鎖,悲觀鎖(*)

  • https://blog.csdn.net/lovejj1994/article/details/79116272

公平鎖、非公平鎖

  • 公平鎖(Fair):加鎖前檢查是否有排隊等待的線程,優先排隊等待的線程,先來先得
    非公平鎖(Nonfair):加鎖時不考慮排隊等待問題,直接嘗試獲取鎖,獲取不到自動到隊尾等待
    非公平鎖性能比公平鎖高5~10倍,由於公平鎖須要在多核的狀況下維護一個隊列
    Java中的ReentrantLock 默認的lock()方法採用的是非公平鎖。

OOM分析

  • https://blog.csdn.net/zheng12tian/article/details/40617369

JVM調優參數
知道-Xms,-Xmx,-XX:NewRatio=n,會算就行,筆試題考過

  • https://www.jianshu.com/p/a2a6a0995fee

堆設置

  • -Xms:初始堆大小
    -Xmx:最大堆大小
    -XX:NewSize=n:設置年輕代大小
    -XX:NewRatio=n:設置年輕代和年老代的比值。如:爲3,表示年輕代與年老代比值爲1:3,年輕代佔整個年輕代年老代和的1/4
    -XX:SurvivorRatio=n:年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:3,表示Eden:Survivor=3:2,一個Survivor區佔整個年輕代的1/5
    -XX:MaxPermSize=n:設置持久代大小

收集器設置

  • -XX:+UseSerialGC:設置串行收集器
    -XX:+UseParallelGC:設置並行收集器
    -XX:+UseParalledlOldGC:設置並行年老代收集器
    -XX:+UseConcMarkSweepGC:設置併發收集器

垃圾回收統計信息

  • -XX:+PrintGC
    -XX:+PrintGCDetails
    -XX:+PrintGCTimeStamps
    -Xloggc:filename

並行收集器設置

  • -XX:ParallelGCThreads=n:設置並行收集器收集時使用的CPU數。並行收集線程數。
    -XX:MaxGCPauseMillis=n:設置並行收集最大暫停時間
    -XX:GCTimeRatio=n:設置垃圾回收時間佔程序運行時間的百分比。公式爲1/(1+n)

併發收集器設置

  • -XX:+CMSIncrementalMode:設置爲增量模式。適用於單CPU狀況。
    -XX:ParallelGCThreads=n:設置併發收集器年輕代收集方式爲並行收集時,使用的CPU數。並行收集線程數。

調優總結
年輕代大小選擇

  • 響應時間優先的應用:儘量設大,直到接近系統的最低響應時間限制(根據實際狀況選擇)。在此種狀況下,年輕代收集發生的頻率也是最小的。同時,減小到達年老代的對象。
    吞吐量優先的應用:儘量的設置大,可能到達Gbit的程度。由於對響應時間沒有要求,垃圾收集能夠並行進行,通常適合8CPU以上的應用。

年老代大小選擇

  • 響應時間優先的應用:年老代使用併發收集器,因此其大小須要當心設置,通常要考慮併發會話率和會話持續時間等一些參數。若是堆設置小了,能夠會形成內存碎片、高回收頻率以及應用暫停而使用傳統的標記清除方式;若是堆大了,則須要較長的收集時間。最優化的方案,通常須要參考如下數據得到:
    併發垃圾收集信息 持久代併發收集次數 傳統GC信息 花在年輕代和年老代回收上的時間比例 減小年輕代和年老代花費的時間,通常會提升應用的效率
    吞吐量優先的應用:通常吞吐量優先的應用都有一個很大的年輕代和一個較小的年老代。緣由是,這樣能夠儘量回收掉大部分短時間對象,減小中期的對象,而年老代盡存放長期存活對象。

較小堆引發的碎片問題

  • 由於年老代的併發收集器使用標記、清除算法,因此不會對堆進行壓縮。當收集器回收時,他會把相鄰的空間進行合併,這樣能夠分配給較大的對象。可是,當堆空間較小時,運行一段時間之後,就會出現「碎片」,若是併發收集器找不到足夠的空間,那麼併發收集器將會中止,而後使用傳統的標記、清除方式進行回收。若是出現「碎片」,可能須要進行以下配置:
    -XX:+UseCMSCompactAtFullCollection:使用併發收集器時,開啓對年老代的壓縮。
    -XX:CMSFullGCsBeforeCompaction=0:上面配置開啓的狀況下,這裏設置多少次Full GC後,對年老代進行壓縮

synchronized實現原理(*)

  • https://blog.csdn.net/javazejian/article/details/72828483

  • 內存對象頭, Mark Word保存鎖信息

  • JVM層:Monitor對象,字節碼中的monitorenter 和 monitorexit 指令

  • 無鎖,偏向鎖,輕量級鎖(自選),重量級鎖

  • 可重入

  • notify/notifyAll和wait方法,在使用這3個方法時,必須處於synchronized代碼塊或者synchronized方法中,不然就會拋出IllegalMonitorStateException異常,這是由於調用這幾個方法前必須拿到當前對象的監視器monitor對象,也就是說notify/notifyAll和wait方法依賴於monitor對象,在前面的分析中,咱們知道monitor 存在於對象頭的Mark Word 中(存儲monitor引用指針),而synchronized關鍵字能夠獲取 monitor ,這也就是爲何notify/notifyAll和wait方法必須在synchronized代碼塊或者synchronized方法調用的緣由.

synchronized, lock區別(*)

  • https://blog.csdn.net/u012403290/article/details/64910926

Spring容器中Bean的做用域(*)
當經過Spring容器建立一個Bean實例時,不只能夠完成Bean實例的實例化,還能夠爲Bean指定特定的做用域。Spring支持以下5種做用域:

  • singleton:單例模式,在整個Spring IoC容器中,使用singleton定義的Bean將只有一個實例

  • prototype:原型模式,每次經過容器的getBean方法獲取prototype定義的Bean時,都將產生一個新的Bean實例

  • request:對於每次HTTP請求,使用request定義的Bean都將產生一個新實例,即每次HTTP請求將會產生不一樣的Bean實例。只有在Web應用中使用Spring時,該做用域纔有效

  • session:對於每次HTTP
    Session,使用session定義的Bean產生一個新實例。一樣只有在Web應用中使用Spring時,該做用域纔有效

  • globalsession:每一個全局的HTTP
    Session,使用session定義的Bean都將產生一個新實例。典型狀況下,僅在使用portlet
    context的時候有效。一樣只有在Web應用中使用Spring時,該做用域纔有效

      其中比較經常使用的是singleton和prototype兩種做用域。對於singleton做用域的Bean,每次請求該Bean都將得到相同的實例。容器負責跟蹤Bean實例的狀態,負責維護Bean實例的生命週期行爲;若是一個Bean被設置成prototype做用域,程序每次請求該id的Bean,Spring都會新建一個Bean實例,而後返回給程序。在這種狀況下,Spring容器僅僅使用new 關鍵字建立Bean實例,一旦建立成功,容器不在跟蹤實例,也不會維護Bean實例的狀態。

      若是不指定Bean的做用域,Spring默認使用singleton做用域。Java在建立Java實例時,須要進行內存申請;銷燬實例時,須要完成垃圾回收,這些工做都會致使系統開銷的增長。所以,prototype做用域Bean的建立、銷燬代價比較大。而singleton做用域的Bean實例一旦建立成功,能夠重複使用。所以,除非必要,不然儘可能避免將Bean被設置成prototype做用域。

Spring IOC實現原理, 相關知識(*)

Spring 啓動時讀取應用程序提供的Bean配置信息,並在Spring容器中生成一份相應的Bean配置註冊表,而後根據這張註冊表實例化Bean,裝配好Bean之間的依賴關係,爲上層應用提供準備就緒的運行環境。



Bean緩存池:HashMap實現

Spring 經過一個配置文件描述 Bean 及 Bean 之間的依賴關係,利用 Java 語言的反射功能實例化 Bean 並創建 Bean 之間的依賴關係。 Spring 的 IoC 容器在完成這些底層工做的基礎上,還提供了 Bean 實例緩存、生命週期管理、 Bean 實例代理、事件發佈、資源裝載等高級服務。

BeanFactory 是 Spring 框架的基礎設施,面向 Spring 自己;

ApplicationContext 面向使用 Spring 框架的開發者,幾乎全部的應用場合咱們都直接使用 ApplicationContext 而非底層的 BeanFactory。


enter image description here

BeanDefinitionRegistry: Spring 配置文件中每個節點元素在 Spring 容器裏都經過一個 BeanDefinition 對象表示,它描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器手工註冊 BeanDefinition 對象的方法。

BeanFactory 接口位於類結構樹的頂端 ,它最主要的方法就是 getBean(String beanName),該方法從容器中返回特定名稱的 Bean,BeanFactory 的功能經過其餘的接口獲得不斷擴展:

ListableBeanFactory:該接口定義了訪問容器中 Bean 基本信息的若干方法,如查看Bean 的個數、獲取某一類型 Bean 的配置名、查看容器中是否包括某一 Bean 等方法;

HierarchicalBeanFactory:父子級聯 IoC 容器的接口,子容器能夠經過接口方法訪問父容器; 經過 HierarchicalBeanFactory 接口, Spring 的 IoC 容器能夠創建父子層級關聯的容器體系,子容器能夠訪問父容器中的 Bean,但父容器不能訪問子容器的 Bean。Spring 使用父子容器實現了不少功能,好比在 Spring MVC 中,展示層 Bean 位於一個子容器中,而業務層和持久層的 Bean 位於父容器中。這樣,展示層 Bean 就能夠引用業務層和持久層的 Bean,而業務層和持久層的 Bean 則看不到展示層的 Bean。

ConfigurableBeanFactory:是一個重要的接口,加強了 IoC 容器的可定製性,它定義了設置類裝載器、屬性編輯器、容器初始化後置處理器等方法;

AutowireCapableBeanFactory:定義了將容器中的 Bean 按某種規則(如按名字匹配、按類型匹配等)進行自動裝配的方法;

SingletonBeanRegistry:定義了容許在運行期間向容器註冊單實例 Bean 的方法;

@Bean, @Component 區別

  • Componet 通常放在類上面,Bean放在方法上面,本身可控制是否生成bean.
    bean 通常會放在classpath scanning路徑下面,會自動生成bean.
    有Componet /bean生成的bean都提供給autowire使用.

  • 在@Component中(@Component標註的類,包括@Service,@Repository, @Controller)使用@Bean註解和在@Configuration中使用是不一樣的。在@Component類中使用方法或字段時不會使用CGLIB加強(及不使用代理類:調用任何方法,使用任何變量,拿到的是原始對象,後面會有例子解釋)。而在@Configuration類中使用方法或字段時則使用CGLIB創造協做對象(及使用代理:拿到的是代理對象);當調用@Bean註解的方法時它不是普通的Java語義,而是從容器中拿到的由Spring生命週期管理、被Spring代理甚至依賴於其餘Bean的對象引用。在@Component中調用@Bean註解的方法和字段則是普通的Java語義,不通過CGLIB處理。

如何中止線程?

  • 主線程提供volatile boolean flag, 線程內while判斷flag

  • 線程內while(!this.isInterrupted), 主線程裏調用interrupt

  • if(this.isInterrupted) throw new InterruptedException() 或return,主線程裏調用interrupt

  • 將一個線程設置爲守護線程後,當進程中沒有非守護線程後,守護線程自動結束

多線程實現方式?(*)

  • extends Thread

  • implements Runnable

  • implements Callable, 重寫call, 返回future (主線程能夠用線程池submit)

線程池(*)
線程池處理過程:

  • 若是當前運行的線程少於corePoolSize,則建立新線程來執行任務(注意,執行這一步驟須要獲取全局鎖)。

  • 若是運行的線程等於或多於corePoolSize,則將任務加入BlockingQueue。

  • 若是沒法將任務加入BlockingQueue(隊列已滿),則建立新的線程來處理任務(注意,執行這一步驟須要獲取全局鎖)。

  • 若是建立新線程將使當前運行的線程超出maximumPoolSize,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()方法。

四種線程池:

  • CachedThreadPool

  • FixedThreadPool

  • ScheduledThreadPool

  • SingleThreadExecutor

建立線程池的參數:

  • corePoolSize(線程池的基本大小):當提交一個任務到線程池時,線程池會建立一個線程來執行任務,即便其餘空閒的基本線程可以執行新任務也會建立線程,等到須要執行的任務數大於線程池基本大小時就再也不建立。若是調用了線程池的prestartAllCoreThreads()方法,線程池會提早建立並啓動全部基本線程。

  • runnableTaskQueue(任務隊列):用於保存等待執行的任務的阻塞隊列。能夠選擇如下幾個阻塞隊列。
    ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按FIFO(先進先出)原則對元素進行排序。
    LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO排序元素,吞吐量一般要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。
    SynchronousQueue:一個不存儲元素的阻塞隊列。每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於Linked-BlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列。
    PriorityBlockingQueue:一個具備優先級的無限阻塞隊列。

  • maximumPoolSize(線程池最大數量):線程池容許建立的最大線程數。若是隊列滿了,而且已建立的線程數小於最大線程數,則線程池會再建立新的線程執行任務。值得注意的是,若是使用了無界的任務隊列這個參數就沒什麼效果。

  • ThreadFactory:用於設置建立線程的工廠

  • RejectedExecutionHandler(飽和策略):當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採起一種策略處理提交的新任務。這個策略默認狀況下是AbortPolicy,表示沒法處理新任務時拋出異常。在JDK
    1.5中Java線程池框架提供瞭如下4種策略。 AbortPolicy:直接拋出異常。 CallerRunsPolicy:只用調用者所在線程來運行任務。
    DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。 DiscardPolicy:不處理,丟棄掉。

**ArrayList,LinkedList **

  • ArrayList初始化能夠指定大小,知道大小的建議指定
    arraylist添加元素的時候,須要判斷存放元素的數組是否須要擴容(擴容大小是原來大小的1/2+1)

  • LinkedList添加、刪除元素經過移動指針 LinkedList遍歷比arraylist慢,建議用迭代器

  • ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。
    對於隨機訪問get和set,ArrayList優於LinkedList,由於ArrayList能夠隨機定位,而LinkedList要移動指針一步一步的移動到節點處。(參考數組與鏈表來思考)

  • 對於新增和刪除操做add和remove,LinedList比較佔優點,只須要對指針進行修改便可,而ArrayList要移動數據來填補被刪除的對象的空間。

HashMap原理(*)

  • HashMap最多隻容許一條Entry的鍵爲Null(多條會覆蓋),但容許多條Entry的值爲Null

  • HashSet 自己就是在 HashMap 的基礎上實現的.

  • 若負載因子越大,那麼對空間的利用更充分,但查找效率的也就越低;若負載因子越小,那麼哈希表的數據將越稀疏,對空間形成的浪費也就越嚴重。系統默認負載因子0.75

  • 調用put方法存值時,HashMap首先會調用Key的hashCode方法,而後基於此獲取Key哈希碼,經過哈希碼快速找到某個桶,這個位置能夠被稱之爲bucketIndex.若是兩個對象的hashCode不一樣,那麼equals必定爲false;不然,若是其hashCode相同,equals也不必定爲 true。因此,理論上,hashCode可能存在碰撞的狀況,當碰撞發生時,這時會取出bucketIndex桶內已存儲的元素,並經過hashCode() 和 equals()來逐個比較以判斷Key是否已存在。若是已存在,則使用新Value值替換舊Value值,並返回舊Value值;若是不存在,則存放新的鍵值對

    到桶中。所以,在 HashMap中,equals() 方法只有在哈希碼碰撞時纔會被用到。
  • 首先,判斷key是否爲null,若爲null,則直接調用putForNullKey方法;若不爲空,則先計算key的hash值,而後根據hash值搜索在table數組中的索引位置,若是table數組在該位置處有元素,則查找是否存在相同的key,若存在則覆蓋原來key的value,不然將該元素保存在鏈頭(最早保存的元素放在鏈尾)。此外,若table在該處沒有元素,則直接保存。

  • HashMap 永遠都是在鏈表的表頭添加新元素。

hash()和indexFor()

static int hash(int h) {
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
    return h & (length-1);  // 做用等價於取模運算,但這種方式效率更高
}
複製代碼
  • hash() 方法用於對Key的hashCode進行從新計算,而 indexFor()方法用於生成這個Entry對象的插入位置。當計算出來的hash值與hashMap的(length-1)作了&運算後,會獲得位於區間[0,length-1]的一個值。特別地,這個值分佈的越均勻,HashMap 的空間利用率也就越高,存取效率也就越好,保證元素均勻分佈到table的每一個桶中以便充分利用空間。

  • hash():使用hash()方法對一個對象的hashCode進行從新計算是爲了防止質量低下的hashCode()函數實現。因爲hashMap的支撐數組長度老是2 的冪次,經過右移可使低位的數據儘可能的不一樣,從而使hash值的分佈儘可能均勻。

  • indexFor():保證元素均勻分佈到table的每一個桶中; 當length爲2的n次方時,h&(length -1)就至關於對length取模,並且速度比直接取模要快得多,這是HashMap在速度上的一個優化.

擴容resize()和重哈希transfer()

void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;

    // 若 oldCapacity 已達到最大值,直接將 threshold 設爲 Integer.MAX_VALUE
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;             // 直接返回
    }

    // 不然,建立一個更大的數組
    Entry[] newTable = new Entry[newCapacity];

    //將每條Entry從新哈希到新的數組中
    transfer(newTable);

    table = newTable;
    threshold = (int) (newCapacity * loadFactor);  // 從新設定 threshold
}

void transfer(Entry[] newTable) {

    // 將原數組 table 賦給數組 src
    Entry[] src = table;
    int newCapacity = newTable.length;

    // 將數組 src 中的每條鏈從新添加到 newTable 中
    for (int j = 0; j < src.length; j++) {
        Entry<K,V> e = src[j];
        if (e != null) {
            src[j] = null;   // src 回收

            // 將每條鏈的每一個元素依次添加到 newTable 中相應的桶中
            do {
                Entry<K,V> next = e.next;

                // e.hash指的是 hash(key.hashCode())的返回值;
                // 計算在newTable中的位置,注意原來在同一條子鏈上的元素可能被分配到不一樣的子鏈
                int i = indexFor(e.hash, newCapacity);   
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            } while (e != null);
        }
    }
}
複製代碼
  • 爲了保證HashMap的效率,系統必需要在某個臨界點進行擴容處理,該臨界點就是HashMap中元素的數量在數值上等於threshold(table數組長度*加載因子)

  • 重哈希的主要是一個從新計算原HashMap中的元素在新table數組中的位置並進行復制處理的過程

HashMap 的底層數組長度爲什麼老是2的n次方

  • 當底層數組的length爲2的n次方時, h&(length - 1) 就至關於對length取模,並且速度比直接取模得多,這是HashMap在速度上的一個優化

  • 不一樣的hash值發生碰撞的機率比較小,這樣就會使得數據在table數組中分佈較均勻,空間利用率較高,查詢速度也較快

ConcurrenytHashMap原理(*)


enter image description here
  • 經過鎖分段技術保證併發環境下的寫操做;
    經過 HashEntry的不變性、Volatile變量的內存可見性和加鎖重讀機制保證高效、安全的讀操做;
    經過不加鎖和加鎖兩種方案控制跨段操做的的安全性。

HashMap線程不安全

  • https://blog.csdn.net/justloveyou_/article/details/72783008

  • 在HashMap進行擴容重哈希時致使Entry鏈造成環。一旦Entry鏈中有環,勢必會致使在同一個桶中進行插入、查詢、刪除等操做時陷入死循環

Segment數組

static final class Segment<K,V> extends ReentrantLock 
implements Serializable {
    transient volatile int count;    // Segment中元素的數量,可見的
    transient int modCount;  //對count的大小形成影響的操做的次數(好比put或者remove操做)
    transient int threshold;      // 閾值,段中元素的數量超過這個值就會對Segment進行擴容
    transient volatile HashEntry<K,V>[] table;  // 鏈表數組
    final float loadFactor;  // 段的負載因子,其值等同於ConcurrentHashMap的負載因子
    ...
}
複製代碼
  • Segment 類繼承於 ReentrantLock 類,從而使得 Segment 對象能充當鎖的角色

  • 在Segment類中,count 變量是一個計數器,它表示每一個 Segment 對象管理的 table 數組包含的 HashEntry 對象的個數,也就是 Segment 中包含的 HashEntry 對象的總數。特別須要注意的是,之因此在每一個 Segment 對象中包含一個計數器,而不是在 ConcurrentHashMap 中使用全局的計數器,是對 ConcurrentHashMap 併發性的考慮:由於這樣當須要更新計數器時,不用鎖定整個ConcurrentHashMap。事實上,每次對段進行結構上的改變,如在段中進行增長/刪除節點(修改節點的值不算結構上的改變),都要更新count的值,此外,在JDK的實現中每次讀取操做開始都要先讀取count的值。特別須要注意的是,count是volatile的,這使得對count的任何更新對其它線程都是當即可見的。modCount用於統計段結構改變的次數,主要是爲了檢測對多個段進行遍歷過程當中某個段是否發生改變.table是一個典型的鏈表數組,並且也是volatile的,這使得對table的任何更新對其它線程也都是當即可見的。

HashEntry

static final class HashEntry<K,V> {
   final K key;                       // 聲明 key 爲 final 的
   final int hash;                   // 聲明 hash 值爲 final 的
   volatile V value;                // 聲明 value 被volatile所修飾
   final HashEntry<K,V> next;      // 聲明 next 爲 final 的

    HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
        this.key = key;
        this.hash = hash;
        this.next = next;
        this.value = value;
    }

    @SuppressWarnings("unchecked")
    static final <K,V> HashEntry<K,V>[] newArray(int i) {
    return new HashEntry[i];
    }
}
複製代碼
  • 在HashEntry類中,key,hash和next域都被聲明爲final的,value域被volatile所修飾,所以HashEntry對象幾乎是不可變的,這是ConcurrentHashmap讀操做並不須要加鎖的一個重要緣由

  • 因爲value域被volatile修飾,因此其能夠確保被讀線程讀到最新的值,這是ConcurrentHashmap讀操做並不須要加鎖的另外一個重要緣由

put(), get()

  • 不容許key值爲null,也不容許value值爲null

  • HashTable 和由同步包裝器包裝的HashMap每次只能有一個線程執行讀或寫操做,ConcurrentHashMap 在併發訪問性能上有了質的提升。在理想狀態下,ConcurrentHashMap 能夠支持 16 個線程執行併發寫操做(若是併發級別設置爲 16),及任意數量線程的讀操做

    重哈希rehash()

  • ConcurrentHashMap的重哈希其實是對ConcurrentHashMap的某個段的重哈希,所以ConcurrentHashMap的每一個段所包含的桶位天然也就不盡相同

存在key-value爲null的特殊狀況

V get(Object key, int hash) {
        if (count != 0) {            // read-volatile,首先讀 count 變量
            HashEntry<K,V> e = getFirst(hash);   // 獲取桶中鏈表頭結點
            while (e != null) {
                if (e.hash == hash && key.equals(e.key)) {    // 查找鏈中是否存在指定Key的鍵值對
                    V v = e.value;
                    if (v != null)  // 若是讀到value域不爲 null,直接返回
                        return v;   
                    // 若是讀到value域爲null,說明發生了重排序,加鎖後從新讀取
                    return readValueUnderLock(e); // recheck
                }
                e = e.next;
            }
        }
        return null;  // 若是不存在,直接返回null
    }
複製代碼
  • 初始化HashEntry時發生的指令重排序致使的,也就是在HashEntry初始化完成以前便返回了它的引用

  • 加鎖重讀

讀操做不須要加鎖

  • 用HashEntery對象的不變性來下降讀操做對加鎖的需求;

  • 用Volatile變量協調讀寫線程間的內存可見性;

  • 若讀時發生指令重排序現象,則加鎖重讀;

結構性操做的併發安全

remove(Object key, int hash, Object value) {
        lock();     // 加鎖
        try {
            int c = count - 1;      
            HashEntry<K,V>[] tab = table;
            int index = hash & (tab.length - 1);        // 定位桶
            HashEntry<K,V> first = tab[index];
            HashEntry<K,V> e = first;
            while (e != null && (e.hash != hash || !key.equals(e.key)))  // 查找待刪除的鍵值對
                e = e.next;

            V oldValue = null;
            if (e != null) {    // 找到
                V v = e.value;
                if (value == null || value.equals(v)) {
                    oldValue = v;
                    // All entries following removed node can stay
                    // in list, but all preceding ones need to be
                    // cloned.
                    ++modCount;
                    // 全部處於待刪除節點以後的節點原樣保留在鏈表中
                    HashEntry<K,V> newFirst = e.next;
                    // 全部處於待刪除節點以前的節點被克隆到新鏈表中
                    for (HashEntry<K,V> p = first; p != e; p = p.next)
                        newFirst = new HashEntry<K,V>(p.key, p.hash,newFirst, p.value); 

                    tab[index] = newFirst;   // 將刪除指定節點並重組後的鏈從新放到桶中
                    count = c;      // write-volatile,更新Volatile變量count
                }
            }
            return oldValue;
        } finally {
            unlock();          // finally子句解鎖
        }
    }
複製代碼
  • clear操做只是把ConcurrentHashMap中全部的桶置空,每一個桶以前引用的鏈表依然存在,只是桶再也不引用這些鏈表而已,而鏈表自己的結構並無發生任何修改。

  • put操做若是須要插入一個新節點到鏈表中時會在鏈表頭部插入這個新節點,此時鏈表中的原有節點的連接並無被修改

  • 在執行remove操做時,原始鏈表並無被修改

  • 只要以前對鏈表作結構性修改操做的寫線程M在退出寫方法前寫volatile變量count(segment中的,segment中元素的個數),讀線程N就能讀取到這個volatile變量count的最新值

跨segment操做

  • size(): JDK只須要在統計size先後比較modCount(Segment中的)是否發生變化就能夠得知容器的大小是否發生變化

  • size方法主要思路是先在沒有鎖的狀況下對全部段大小求和,這種求和策略最多執行RETRIES_BEFORE_LOCK次(默認是兩次):在沒有達到RETRIES_BEFORE_LOCK以前,求和操做會不斷嘗試執行(這是由於遍歷過程當中可能有其它線程正在對已經遍歷過的段進行結構性更新);在超過RETRIES_BEFORE_LOCK以後,若是還不成功就在持有全部段鎖的狀況下再對全部段大小求和。

JVM內存模型(*)
必考,熟背


enter image description here
  • 線程私有的數據區 包括 程序計數器、 虛擬機棧 和 本地方法棧

  • 線程共享的數據區 具體包括 Java堆 和 方法區

線程計數器

  • 在多線程狀況下,當線程數超過CPU數量或CPU內核數量時,線程之間就要根據 時間片輪詢搶奪CPU時間資源。也就是說,在任何一個肯定的時刻,一個處理器都只會執行一條線程中的指令。所以,爲了線程切換後可以恢復到正確的執行位置,每條線程都須要一個獨立的程序計數器去記錄其正在執行的字節碼指令地址。

虛擬機棧

  • 每一個方法從調用直至完成的過程,對應一個棧幀在虛擬機棧中入棧到出棧的過程

本地方法棧

  • 本地方法棧與Java虛擬機棧很是類似,也是線程私有的,區別是虛擬機棧爲虛擬機執行 Java 方法服務,而本地方法棧爲虛擬機執行 Native 方法服務。與虛擬機棧同樣,本地方法棧區域也會拋出 StackOverflowError 和 OutOfMemoryError 異常

Java堆

  • Java 堆的惟一目的就是存放對象實例,幾乎全部的對象實例(和數組)都在這裏分配內存

  • Java堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可。並且,Java堆在實現時,既能夠是固定大小的,也能夠是可拓展的,而且主流虛擬機都是按可擴展來實現的(經過-Xmx(最大堆容量) 和 -Xms(最小堆容量)控制)。若是在堆中沒有內存完成實例分配,而且堆也沒法再拓展時,將會拋出 OutOfMemoryError 異常。

  • TLAB (線程私有分配緩衝區) : 虛擬機爲新生對象分配內存時,須要考慮修改指針 (該指針用於劃份內存使用空間和空閒空間) 時的線程安全問題,由於存在可能出現正在給對象A分配內存,指針還未修改,對象B又同時使用原來的指針分配內存的狀況。TLAB 的存在就是爲了解決這個問題:每一個線程在Java堆中預先分配一小塊內存 TLAB,哪一個線程須要分配內存就在本身的TLAB上進行分配,若TLAB用完並分配新的TLAB時,再加同步鎖定,這樣就大大提高了對象內存分配的效率。

方法區

  • 方法區與Java堆同樣,也是線程共享的而且不須要連續的內存,其用於存儲已被虛擬機加載的 類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據

  • 運行時常量池:是方法區的一部分,用於存放編譯期生成的各類 字面量 和 符號引用. 字面量比較接近Java語言層次的常量概念,如文本字符串、被聲明爲final的常量值. 符號引用:包括如下三類常量:類和接口的全限定名、字段的名稱和描述符 和 方法的名稱和描述符.

方法區的回收

  • 主要是針對 常量池的回收 (判斷引用) 和 對類型的卸載

  • 回收類: 1) 該類全部的實例都已經被回收,也就是Java堆中不存在該類的任何實例加載 2) 該類的ClassLoader已經被回收 3) 該類對應的 java.lang.Class 對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法。

垃圾回收機制(*)
必考,熟背

引用計數法

  • 循環引用

可達性分析算法

  • 經過一系列的名爲 「GC Roots」 的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain)。當一個對象到 GC Roots 沒有任何引用鏈相連(用圖論的話來講就是從 GC Roots 到這個對象不可達)時,則證實此對象是不可用的

  • 虛擬機棧(棧幀中的局部變量表)中引用的對象

  • 方法區中類靜態屬性引用的對象

  • 方法區中常量引用的對象

  • 本地方法棧中Native方法引用的對象

標記清除算法

  • 標記-清除算法分爲標記和清除兩個階段。該算法首先從根集合進行掃描,對存活的對象對象標記,標記完畢後,再掃描整個空間中未被標記的對象並進行回收

  • 效率問題:標記和清除兩個過程的效率都不高;

  • 空間問題:標記-清除算法不須要進行對象的移動,而且僅對不存活的對象進行處理,所以標記清除以後會產生大量不連續的內存碎片,空間碎片太多可能會致使之後在程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做

複製算法

  •  複製算法將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。這種算法適用於對象存活率低的場景,好比新生代。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜狀況,只要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。

  • 實踐中會將新生代內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間 (以下圖所示),每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活着的對象一次地複製到另一塊Survivor空間上,最後清理掉Eden和剛纔用過的Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是 8:1,也就是每次新生代中可用內存空間爲整個新生代容量的90% ( 80%+10% ),只有10% 的內存會被「浪費」。


  • 如今商用的虛擬機都採用這種算法來回收新生代

爲何分代收集

  • 不一樣的對象的生命週期(存活狀況)是不同的,而不一樣生命週期的對象位於堆中不一樣的區域,所以對堆內存不一樣區域採用不一樣的策略進行回收能夠提升 JVM 的執行效率.

新生代進入老生代的狀況

  • 對象優先在Eden分配,當Eden區沒有足夠空間進行分配時,虛擬機將發起一次MinorGC。如今的商業虛擬機通常都採用複製算法來回收新生代,將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。 當進行垃圾回收時,將Eden和Survivor中還存活的對象一次性地複製到另一塊Survivor空間上,最後處理掉Eden和剛纔的Survivor空間。(HotSpot虛擬機默認Eden和Survivor的大小比例是8:1)當Survivor空間不夠用時,須要依賴老年代進行分配擔保。

  • 大對象直接進入老年代。所謂的大對象是指,須要大量連續內存空間的Java對象,最典型的大對象就是那種很長的字符串以及數組。

  • 長期存活的對象(-XX:MaxTenuringThreshold)將進入老年代。當對象在新生代中經歷過必定次數(默認爲15)的Minor GC後,就會被晉升到老年代中。

  • 動態對象年齡斷定。爲了更好地適應不一樣程序的內存情況,虛擬機並非永遠地要求對象年齡必須達到了MaxTenuringThreshold才能晉升老年代,若是在Survivor空間中相同年齡全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。

內存分配擔保機制

  • 咱們知道若是對象在複製到Survivor區時若Survivor空間不足,則會出發擔保機制,將對象轉入老年代;但老年代的能力也不是無限的,所以須要在minor GC時作一個是否須要Major GC 的判斷:

  • 若是老年代的剩餘空間 < 以前轉入老年代的對象的平均大小,則觸發Major GC

  • 若是老年代的剩餘空間 > 以前轉入老年代的對象的平均大小,而且容許擔保失敗,則直接Minor GC,不須要作Full GC

  • 若是老年代的剩餘空間 > 以前轉入老年代的對象的平均大小,而且不容許擔保失敗,則觸發Major GC

    出發點仍是儘可能爲對象分配內存。可是通常會配置容許擔保失敗,避免頻繁的去作Full GC。

標記整理算法

  • 標記整理算法的標記過程相似標記清除算法,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存,相似於磁盤整理的過程,該垃圾回收算法適用於對象存活率高的場景(老年代)

  • 無內存碎片

新生代、老年代、永久代

  • 新生代的目標就是儘量快速的收集掉那些生命週期短的對象,通常狀況下,全部新生成的對象首先都是放在新生代的. 若是老年代也滿了,就會觸發一次FullGC,也就是新生代、老年代都進行回收。注意,新生代發生的GC也叫作MinorGC,MinorGC發生頻率比較高,不必定等 Eden區滿了才觸發。

  • 老年代存放的都是一些生命週期較長的對象,就像上面所敘述的那樣,在新生代中經歷了N次垃圾回收後仍然存活的對象就會被放到老年代中

  • 永久代主要用於存放靜態文件,如Java類、方法等

垃圾收集器


enter image description here
  • Serial收集器(複製算法): 新生代單線程收集器,標記和清理都是單線程,優勢是簡單高效;

  • Serial Old收集器 (標記-整理算法): 老年代單線程收集器,Serial收集器的老年代版本;

  • ParNew收集器 (複製算法):新生代收並行集器,其實是Serial收集器的多線程版本,在多核CPU環境下有着比Serial更好的表現;

  • Parallel Scavenge收集器 (複製算法): 新生代並行收集器,追求高吞吐量,高效利用 CPU。吞吐量 =用戶線程時間/(用戶線程時間+GC線程時間),高吞吐量能夠高效率的利用CPU時間,儘快完成程序的運算任務,適合後臺應用等對交互相應要求不高的場景;

  • Parallel Old收集器 (標記-整理算法): 老年代並行收集器,吞吐量優先,Parallel Scavenge收集器的老年代版本;

  • CMS(Concurrent Mark Sweep)收集器(標記-清除算法):老年代並行收集器,以獲取最短回收停頓時間爲目標的收集器,具備高併發、低停頓的特色,追求最短GC回收停頓時間。

  • G1(Garbage First)收集器 (標記-整理算法):Java堆並行收集器,G1收集器是JDK1.7提供的一個新收集器,G1收集器基於「標記-整理」算法實現,也就是說不會產生內存碎片。此外,G1收集器不一樣於以前的收集器的一個重要特色是:G1回收的範圍是整個Java堆(包括新生代,老年代),而前六種收集器回收的範圍僅限於新生代或老年代。

CMS,G1

  • https://blog.csdn.net/huanbia/article/details/75581423

內存泄露問題

  • 靜態集合類: 如 HashMap、Vector 等集合類的靜態使用最容易出現內存泄露,由於這些靜態變量的生命週期和應用程序一致,全部的對象Object也不能被釋放

  • 各類資源鏈接包括數據庫鏈接、網絡鏈接、IO鏈接等沒有顯式調用close關閉

  • 監聽器的使用,在釋放對象的同時沒有相應刪除監聽器的時候也可能致使內存泄露。

MYSQL索引(*)

創建索引

  • 表的主鍵、外鍵必須有索引;

  • 數據量超過300的表應該有索引;

  • 常常與其餘表進行鏈接的表,在鏈接字段上應該創建索引;

  • 常常出如今Where子句中的字段,特別是大表的字段,應該創建索引;

  • 索引應該建在選擇性高的字段上;

  • 索引應該建在小字段上,對於大的文本字段甚至超長字段,不要建索引;

  • 頻繁進行數據操做的表,不要創建太多的索引;

索引失效

  • 字符串不加單引號

  • 將要使用的索引列不是複合索引列表中的第一部分,則不會使用索引

  • 應儘可能避免在 where 子句中對字段進行 null 值判斷,不然將致使引擎放棄使用索引而進行全表掃描,如:
    select id from t where num is null

  • 能夠在num上設置默認值0,確保表中num列沒有null值,而後這樣查詢:
    select id from t where num=0

  • 應儘可能避免在 where 子句中使用!=或<>操做符,不然將引擎放棄使用索引而進行全表掃描。優化器將沒法經過索引來肯定將要命中的行數,所以須要搜索該表的全部行。

  • 應儘可能避免在 where 子句中使用 or 來鏈接條件 (用or分割開的條件,若是or前的條件中的列有索引,然後面的列中沒有索引,那麼涉及的索引都不會被用到),不然將致使引擎放棄使用索引而進行全表掃描,如:
    select id from t where num=10 or num=20

  • 能夠這樣查詢:
    select id from t where num=10
    union all
    select id from t where num=20

  • in 和 not in 也要慎用,由於IN會使系統沒法使用索引,而只能直接搜索表中的數據。如:
    select id from t where num in(1,2,3)

  • 對於連續的數值,能用 between 就不要用 in 了:
    select id from t where num between 1 and 3

  • 儘可能避免在索引過的字符數據中,使用非打頭字母%搜索。這也使得引擎沒法利用索引。
    見以下例子:
    SELECT * FROM T1 WHERE NAME LIKE ‘%L%’
    SELECT * FROM T1 WHERE SUBSTING(NAME,2,1)=’L’
    SELECT * FROM T1 WHERE NAME LIKE ‘L%’

  • 即便NAME字段建有索引,前兩個查詢依然沒法利用索引完成加快操做,引擎不得不對全表全部數據逐條操做來完成任務。而第三個查詢可以使用索引來加快操做

  • 應儘可能避免在 where 子句中對字段進行表達式操做,這將致使引擎放棄使用索引而進行全表掃描

  • 應儘可能避免在where子句中對字段進行函數操做,這將致使引擎放棄使用索引而進行全表掃描

  • 不要在 where 子句中的「=」左邊進行函數、算術運算或其餘表達式運算,不然系統將可能沒法正確使用索引

共享鎖,排他鎖

  • InnoDB普通 select 語句默認不加鎖(快照讀,MYISAM會加鎖),而CUD操做默認加排他鎖

  • MySQL InnoDB存儲引擎,實現的是基於多版本的併發控制協議——MVCC (Multi-Version Concurrency Control) (注:與MVCC相對的,是基於鎖的併發控制,Lock-Based Concurrency Control)。MVCC最大的好處,相信也是耳熟能詳:讀不加鎖讀寫不衝突。在讀多寫少的OLTP應用中,讀寫不衝突是很是重要的,極大的增長了系統的併發性能,這也是爲何現階段,幾乎全部的RDBMS,都支持了MVCC。

  • 多版本併發控制(MVCC)是一種用來解決讀-寫衝突的無鎖併發控制,也就是爲事務分配單向增加的時間戳,爲每一個修改保存一個版本,版本與事務時間戳關聯,讀操做只讀該事務開始前的數據庫的快照。 這樣在讀操做不用阻塞寫操做,寫操做不用阻塞讀操做的同時,避免了髒讀和不可重複讀.MVCC 在語境中傾向於 「對多行數據打快照造平行宇宙」,然而 CAS 通常只是保護單行數據而已

  • 在MVCC併發控制中,讀操做能夠分紅兩類:快照讀 (snapshot read)與當前讀 (current read)。快照讀,讀取的是記錄的可見版本 (有多是歷史版本),不用加鎖。當前讀,讀取的是記錄的最新版本,而且,當前讀返回的記錄,都會加上鎖,保證其餘事務不會再併發修改這條記錄。

  • SELECT … LOCK IN SHARE MODE :共享鎖(S鎖, share locks)。其餘事務能夠讀取數據,但不能對該數據進行修改,直到全部的共享鎖被釋放。

  • SELECT … FOR UPDATE:排他鎖(X鎖, exclusive locks)。若是事務對數據加上排他鎖以後,則其餘事務不能對該數據加任何的鎖。獲取排他鎖的事務既能讀取數據,也能修改數據。

  • InnoDB默認隔離級別 可重複讀(Repeated Read)

  • 查詢字段未加索引(主鍵索引、普通索引等)時,使用表鎖

  • InnoDB行級鎖基於索引實現

  • 索引數據重複率過高會致使全表掃描:當表中索引字段數據重複率過高,則MySQL可能會忽略索引,進行全表掃描,此時使用表鎖。可以使用 force index 強制使用索引。

隔離級別

  • Read Uncommitted(讀取未提交內容): 在該隔離級別,全部事務均可以看到其餘未提交事務的執行結果。本隔離級別不多用於實際應用,由於它的性能也不比其餘級別好多少。讀取未提交的數據,也被稱之爲髒讀(Dirty Read)。

  • Read Committed(讀取提交內容): 這是大多數數據庫系統的默認隔離級別(但不是MySQL默認的)。它知足了隔離的簡單定義:一個事務只能看見已經提交事務所作的改變。這種隔離級別 也支持所謂的不可重複讀(Nonrepeatable Read),由於同一事務的其餘實例在該實例處理其間可能會有新的commit,因此同一select可能返回不一樣結果。

  • Repeatable Read(可重讀): 這是MySQL的默認事務隔離級別,它確保同一事務的多個實例在併發讀取數據時,會看到一樣的數據行。不過理論上,這會致使另外一個棘手的問題:幻讀 (Phantom Read)。簡單的說,幻讀指當用戶讀取某一範圍的數據行時,另外一個事務又在該範圍內插入了新行,當用戶再讀取該範圍的數據行時,會發現有新的「幻影」 行。InnoDB和Falcon存儲引擎經過多版本併發控制(MVCC,Multiversion Concurrency Control)機制解決了該問題。

  • Serializable(可串行化): 這是最高的隔離級別,它經過強制事務排序,使之不可能相互衝突,從而解決幻讀問題。簡言之,它是在每一個讀的數據行上加上共享鎖。在這個級別,可能致使大量的超時現象和鎖競爭.

Spring IOC 怎麼注入類,怎麼實例化對象
實例化

  • Spring IoC容器則須要根據Bean定義裏的配置元數據使用反射機制來建立Bean

  • 使用構造器實例化Bean 有參/無參;使用靜態工廠實例化Bean;使用實例工廠實例化Bean.

  • 使用@Autowire註解注入的時機則是容器剛啓動的時候就開始注入;注入以前要先初始化bean;ApplicationContext 的初始化和BeanFactory 有一個重大的區別:BeanFactory在初始化容器時,並未實例化Bean,直到第一次訪問某個Bean 時才實例目標Bean;而ApplicationContext 則在初始化應用上下文時就實例化全部單實例的Bean。

注入

  • 接口、setter、構造器

AOP(*)
動態代理

@Aspect
public class Audience
{
    @Before("execution(** concert.Performance.perform(..))")        // 表演以前
    public void silenceCellPhones()
    {
        System.out.println("Silencing cell phones");
    }
    @Before("execution(** concert.Performance.perform(..))")        // 表演以前
    public void takeSeats()
    {
        System.out.println("Taking seats");
    }        
    @AfterReturning("execution(** concert.Performance.perform(..))")        // 表演以後
    public void applause()
    {
        System.out.println("CLAP CLAP CLAP!!!");
    }
    @AfterThrowing("execution(** concert.Performance.perform(..))")        // 表演失敗以後
    public void demandRefound()
    {
        System.out.println("Demanding a refund");
    }
}   
複製代碼
  • JDK動態代理,接口,用Proxy.newProxyInstance生成代理對象,InvocationHandler

  • CGLIB,類,用enhancer生成代理對象,MethodInteceptor

  • 若是目標對象實現了接口,默認狀況下會採用JDK的動態代理實現AOP ; 若是目標對象實現了接口,能夠強制使用CGLIB實現AOP ; 若是目標對象沒有實現了接口,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換;

  • 切點+註解

  • AspectJ是一個比較牛逼的AOP框架,他能夠對類的成員變量,方法進行攔截。因爲 AspectJ 是 Java 語言語法和語義的擴展,因此它提供了本身的一套處理方面的關鍵字。除了包含字段和方法以外,AspectJ 的方面聲明還包含切入點和通知成員。
    Spring AOP依賴的是 Spring 框架方便的、最小化的運行時配置,因此不須要獨立的啓動器。可是,使用這個技術,只能通知從 Spring 框架檢索出的對象。Spring的AOP技術只能是對方法進行攔截。
    在spring AOP中咱們一樣也可使用相似AspectJ的註解來實現AOP功能,可是這裏要注意一下,使AspectJ的註解時,AOP的實現方式仍是Spring AOP。Spring缺省使用J2SE動態代理來做爲AOP的代理,這樣任何接口均可以被代理,Spring也可使用CGLIB代理,對於須要代理類而不是代理接口的時候CGLIB是頗有必要的。若是一個業務對象沒有實現接口,默認就會使用CGLIB代理。
    Spring AOP和AscpectJ之間的關係:Spring使用了和aspectj同樣的註解,並使用Aspectj來作切入點解析和匹配。可是spring AOP運行時仍舊是純的spring AOP,並不依賴於Aspectj的編譯器或者織入器

volatile和內存模型(*)

happens-before

  • 什麼是happens-before
    令A和B表示兩組操做,若是A happens-before B,那麼由A操做引發的內存變化,在B開始執行以前,都應該是可見的。
    A happens-before B,不表明A在B以前執行.

  • 如何確保happen-before
    鎖(互斥鎖、讀寫鎖等)、內存屏障

內存屏障

  • 內存屏障是一個指令,這個指令能夠保證屏障先後的指令遵照必定的順序,而且保證必定的可見性

  • 爲了實現volatile的內存語義,編譯器在生成字節碼時,會在指令序列中插入

    內存屏障
    來禁止特定類型的處理器重排序。

Java內存模型

  • 屏蔽各個硬件平臺和操做系統的內存訪問差別,以實現讓 Java 程序在各類平臺下都能達到一致的內存訪問效果

  • Java內存模型 規定全部的變量都是存在主存當中(相似於前面說的物理內存),每一個線程都有本身的工做內存(相似於前面的高速緩存)。線程對變量的全部操做都必須在工做內存中進行,而不能直接對主存進行操做,而且每一個線程不能訪問其餘線程的工做內存。

原子性

  • 只有簡單的讀取、賦值(並且必須是將數字賦值給某個變量,變量之間的相互賦值不是原子操做)纔是原子操做

  • Java內存模型只保證了基本讀取和賦值是原子性操做,若是要實現更大範圍操做的原子性,能夠經過 synchronized 和 Lock 來實現

可見性

  • 當一個共享變量被 volatile 修飾時,它會保證修改的值會當即被更新到主存,當有其餘線程須要讀取時,它會去內存中讀取新值. 經過 synchronized 和 Lock 也可以保證可見性,synchronized 和 Lock 能保證同一時刻只有一個線程獲取鎖而後執行同步代碼,而且 在釋放鎖以前會將對變量的修改刷新到主存當中,所以能夠保證可見性

有序性

  • 指令重排序

  • 不能因爲 synchronized 和 Lock 可讓線程串行執行同步代碼,就說它們能夠保證指令不會發生重排序

volatile

  • 保證了不一樣線程對共享變量進行操做時的可見性,即一個線程修改了某個變量的值,這個新值對其餘線程來講是 當即可見

  • 禁止進行指令重排序 (雙重檢查鎖單例模式)

  • synchronized 也能夠保證可見性,由於每次運行synchronized塊 或者 synchronized方法都會致使線程工做內存與主存的同步,使得其餘線程能夠取得共享變量的最新值。也就是說,synchronized 語義範圍不但包括 volatile 具備的可見性,也包括原子性,但不能禁止指令重排序,這是兩者一個功能上的差別

i被volatile修飾,若是多線程來運行i++,那麼是否能夠達到理想的效果?

  • 不能,volatile不能保證操做的原子性

Sleep()和wait()的區別,使用wait()方法後,怎麼喚醒線程(*)

筆試題常常考

  • sleep方法只讓出了CPU,而並不會釋放同步資源鎖

  • wait()方法則是指當前線程讓本身暫時退讓出同步資源鎖,以便其餘正在等待該資源的線程獲得該資源進而運行

  • sleep()方法能夠在任何地方使用;wait()方法則只能在同步方法或同步塊中使用

  • sleep()是線程線程類(Thread)的方法,調用會暫停此線程指定的時間,但監控依然保持,不會釋放對象鎖,到時間自動恢復;wait()是Object的方法,調用會放棄對象鎖,進入等待隊列,待調用notify()/notifyAll()喚醒指定的線程或者全部線程,纔會進入鎖池,再也不次得到對象鎖纔會進入運行狀態

  • notify讓以前調用wait的線程有權利從新參與線程的調度

Mybatis緩存(*)

  • 一級緩存的做用域是同一個SqlSession,在同一個sqlSession中兩次執行相同的sql語句,第一次執行完畢會將數據庫中查詢的數據寫到緩存(內存),第二次會從緩存中獲取數據將再也不從數據庫查詢,從而提升查詢效率。當一個sqlSession結束後該sqlSession中的一級緩存也就不存在了。Mybatis默認開啓一級緩存

  • 二級緩存是mapper級別的緩存,多個SqlSession去操做同一個Mapper的sql語句,多個SqlSession去操做數據庫獲得數據會存在二級緩存區域,多個SqlSession能夠共用二級緩存,二級緩存是跨SqlSession的。不一樣的sqlSession兩次執行相同namespace下的sql語句且向sql中傳遞參數也相同即最終執行相同的sql語句,第一次執行完畢會將數據庫中查詢的數據寫到緩存(內存),第二次會從緩存中獲取數據將再也不從數據庫查詢,從而提升查詢效率。Mybatis默認沒有開啓二級緩存須要在setting全局參數中配置開啓二級緩存

  • https://segmentfault.com/a/1190000013678579

Redis的數據結構(*)

  • String, Hash, List, Set, ZSet

Hash底層結構

  • redis的哈希對象的底層存儲可使用ziplist(壓縮列表)和hashtable

Redis緩存怎麼運行的?

  • 使用ANSI C編寫的開源、支持網絡、基於內存、可選持久性的鍵值對存儲數據庫

  • 主從複製

  • 哨兵模式

持久化

  • 快照文件

  • AOF語句追加

過時策略

  • https://blog.csdn.net/xiangnan129/article/details/54928672

反向代理是什麼?

  • 反向代理(Reverse Proxy)方式是指以代理服務器來接受internet上的鏈接請求,而後將請求轉發給內部網絡上的服務器,並將從服務器上獲得的結果返回給internet上請求鏈接的客戶端,此時代理服務器對外就表現爲一個反向代理服務器。客戶端只會得知反向代理的IP地址,而不知道在代理服務器後面的服務器簇的存在.

負載均衡是什麼?

  • 負載平衡(Load balancing)是一種計算機技術,用來在多個計算機(計算機集羣)、網絡鏈接、CPU、磁盤驅動器或其餘資源中分配負載,以達到最優化資源使用、最大化吞吐率、最小化響應時間、同時避免過載的目的。 使用帶有負載平衡的多個服務器組件,取代單一的組件,能夠經過冗餘提升可靠性。負載平衡服務一般是由專用軟件和硬件來完成。 主要做用是將大量做業合理地分攤到多個操做單元上進行執行,用於解決互聯網架構中的高併發和高可用的問題。

單例模式(*)

必考,靜態內部類,雙重檢查鎖至少會寫一個

  • 私有的構造方法;
    指向本身實例的私有靜態引用;
    以本身實例爲返回值的靜態的公有方法。

雙重檢查鎖

public class Singleton2 {

private volatile static Singleton2 singleton2;

private Singleton2() {
}

public static Singleton2 getSingleton2() {

    if (singleton2 == null) {
        synchronized (Singleton2.class) {
            if (singleton2 == null) {
                singleton2 = new Singleton2();
            }
        }
    }
    return singleton2;
}
}
複製代碼
  • 第一個if (instance == null),只有instance爲null的時候,才進入synchronized.
    第二個if (instance == null),是爲了防止可能出現多個實例的狀況。

  • volatile: 主要在於singleton = new Singleton()這句,這並不是是一個原子操做,事實上在 JVM 中這句話大概作了下面 3 件事情。
      1. 給 singleton 分配內存
      2. 調用 Singleton 的構造函數來初始化成員變量,造成實例
      3. 將singleton對象指向分配的內存空間(執行完這步 singleton纔是非 null 了)可是在 JVM 的即時編譯器中存在指令重排序的優化。
      也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序多是 1-2-3 也多是 1-3-2。若是是後者,則在 3 執行完畢、2 未執行以前,被線程二搶佔了
    ,這時 instance 已是非 null 了(但卻沒有初始化),因此線程二會直接返回 instance,而後使用,而後瓜熟蒂落地報錯。

靜態內部類

public class Singleton1 {

private Singleton1() {
}

public static final Singleton1 getSingleton1() {
    return Singleton1Holder.singleton1;
}

private static class Singleton1Holder {
    private static final Singleton1 singleton1 = new Singleton1();
}
}
複製代碼

ThreadLocal內存泄露?

static class ThreadLocalMap {
    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    }
    ...
}
複製代碼
  • ThreadLocalMap裏面對Key的引用是弱引用。那麼,就存在這樣的狀況:當釋放掉對threadlocal對象的強引用後,map裏面的value沒有被回收,但卻永遠不會被訪問到了,所以ThreadLocal存在着內存泄露問題

  • Java爲了最小化減小內存泄露的可能性和影響,在ThreadLocal進行get、set操做時會清除線程Map裏全部key爲null的value。因此最怕的狀況就是,ThreadLocal對象設null了,開始發生「內存泄露」,而後使用線程池,線程結束後被放回線程池中而不銷燬,那麼若是這個線程一直不被使用或者分配使用了又再也不調用get/set方法,那麼這個期間就會發生真正的內存泄露。所以,最好的作法是:在不使用該ThreadLocal對象時,及時調用該對象的remove方法去移除ThreadLocal.ThreadLocalMap中的對應Entry.

線程死鎖檢測工具?

  • Jconsole, Jstack, visualVM

線程池調優?

  • 設置最大線程數,防止線程資源耗盡;

  • 使用有界隊列,從而增長系統的穩定性和預警能力(飽和策略);

  • 根據任務的性質設置線程池大小:CPU密集型任務(CPU個數個線程),IO密集型任務(CPU個數兩倍的線程),混合型任務(拆分)。

幾種鎖?

  • 無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態,它會隨着競爭狀況逐漸升級。鎖能夠升級但不能降級,意味着偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是爲了提升得到鎖和釋放鎖的效率

偏向鎖

  • 偏向鎖的目的是在某個線程得到鎖以後,消除這個線程鎖重入(CAS)的開銷,看起來讓這個線程獲得了偏護

  • 偏向鎖使用了一種等到競爭出現才釋放鎖的機制,因此當其餘線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖

自旋鎖

  • 線程的阻塞和喚醒須要CPU從用戶態轉爲核心態,頻繁的阻塞和喚醒對CPU來講是一件負擔很重的工做. 所謂「自旋」,就是讓線程去執行一個無心義的循環,循環結束後再去從新競爭鎖,若是競爭不到繼續循環,循環過程當中線程會一直處於running狀態,可是基於JVM的線程調度,會出讓時間片,因此其餘線程依舊有申請鎖和釋放鎖的機會。

  • 自旋鎖省去了阻塞鎖的時間空間(隊列的維護等)開銷,可是長時間自旋就變成了「忙式等待」,忙式等待顯然還不如阻塞鎖。因此自旋的次數通常控制在一個範圍內,例如10,100等,在超出這個範圍後,自旋鎖會升級爲阻塞鎖。

輕量級鎖

  • 線程嘗試使用CAS將對象頭中的Mark Word替換爲指向鎖記錄的指針。若是成功,當前線程得到鎖,若是失敗,則自旋獲取鎖,當自旋獲取鎖仍然失敗時,表示存在其餘線程競爭鎖(兩條或兩條以上的線程競爭同一個鎖),則輕量級鎖會膨脹成重量級鎖。

重量級鎖

  • 重量鎖在JVM中又叫對象監視器(Monitor),它很像C中的Mutex,除了具有Mutex(0|1)互斥的功能,它還負責實現了Semaphore(信號量)的功能,也就是說它至少包含一個競爭鎖的隊列,和一個信號阻塞隊列(wait隊列),前者負責作互斥,後一個用於作線程同步。


enter image description here

innoDB和MyISAM的區別? (*)

  • https://www.jianshu.com/p/a957b18ba40d

  • InnoDB支持事務,MyISAM不支持,對於InnoDB每一條SQL語言都默認封裝成事務,自動提交,這樣會影響速度,因此最好把多條SQL語言放在begin和commit之間,組成一個事務;

  • InnoDB支持外鍵,而MyISAM不支持。對一個包含外鍵的InnoDB錶轉爲MYISAM會失敗;

  • InnoDB是彙集索引,數據文件是和索引綁在一塊兒的,必需要有主鍵,經過主鍵索引效率很高。可是輔助索引須要兩次查詢,先查詢到主鍵,而後再經過主鍵查詢到數據。所以,主鍵不該該過大,由於主鍵太大,其餘索引也都會很大。而MyISAM是非彙集索引,數據文件是分離的,索引保存的是數據文件的指針。主鍵索引和輔助索引是獨立的。

  • InnoDB不保存表的具體行數,執行select count(*) from table時須要全表掃描。而MyISAM用一個變量保存了整個表的行數,執行上述語句時只須要讀出該變量便可,速度很快;

  • Innodb不支持全文索引,而MyISAM支持全文索引,查詢效率上MyISAM要高;

索引失效 (*)

  • https://blog.csdn.net/qq_32331073/article/details/79041232

MySQL 索引實現原理+幾種索引 (*)

普通索引

  • B+ree

  • MyISAM的B+Tree的葉子節點上的data,並非數據自己,而是數據存放的地址。主索引和輔助索引沒啥區別,只是主索引中的key必定得是惟一的。這裏的索引都是非聚簇索引.

InnoDB

  • InnoDB 的數據文件自己就是索引文件,B+Tree的葉子節點上的data就是數據自己,key爲主鍵,這是聚簇索引。

  • 由於InnoDB的數據文件自己要按主鍵彙集,因此InnoDB要求表必須有主鍵(MyISAM能夠沒有),若是沒有顯式指定,則MySQL系統會自動選擇一個能夠 惟一 標識數據記錄的列做爲主鍵,若是不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段做爲主鍵,這個字段長度爲6個字節,類型爲長整形。

  • 彙集索引這種實現方式使得按主鍵的搜索十分高效,可是輔助索引(普通索引)搜索須要 檢索兩遍索引:首先檢索輔助索引得到主鍵,而後用主鍵到主索引中檢索得到記錄.

幾種索引

  • 主鍵索引;

  • 惟一索引;

  • 普通索引;

  • 聯合索引;

  • 全文索引。

輔助索引

  • https://www.cnblogs.com/xiangyangzhu/p/index.html

爲何用B+樹

  • https://blog.csdn.net/xlgen157387/article/details/79450295

  • 在MySQL中的數據通常是放在磁盤中的,讀取數據的時候確定會有訪問磁盤的操做,磁盤中有兩個機械運動的部分,分別是盤片旋轉和磁臂移動。盤片旋轉就是咱們市面上所提到的多少轉每分鐘,而磁盤移動則是在盤片旋轉到指定位置之後,移動磁臂後開始進行數據的讀寫。那麼這就存在一個定位到磁盤中的塊的過程,而定位是磁盤的存取中花費時間比較大的一塊,畢竟機械運動花費的時候要遠遠大於電子運動的時間。當大規模數據存儲到磁盤中的時候,顯然定位是一個很是花費時間的過程,可是咱們能夠經過B樹進行優化,提升磁盤讀取時定位的效率。

  • 爲何B類樹能夠進行優化呢?咱們能夠根據B類樹的特色,構造一個多階的B類樹,而後在儘可能多的在結點上存儲相關的信息,保證層數儘可能的少,以便後面咱們能夠更快的找到信息,磁盤的I/O操做也少一些,並且B類樹是平衡樹,每一個結點到葉子結點的高度都是相同,這也保證了每一個查詢是穩定的。

  • 總的來講,B/B+樹是爲了磁盤或其它存儲設備而設計的一種平衡多路查找樹(相對於二叉,B樹每一個內節點有多個分支),與紅黑樹相比,在相同的的節點的狀況下,一顆B/B+樹的高度遠遠小於紅黑樹的高度(在下面B/B+樹的性能分析中會提到)。B/B+樹上操做的時間一般由存取磁盤的時間和CPU計算時間這兩部分構成,而CPU的速度很是快,因此B樹的操做效率取決於訪問磁盤的次數,關鍵字總數相同的狀況下B樹的高度越小,磁盤I/O所花的時間越少。

B+樹的插入刪除

  • https://www.cnblogs.com/nullzx/p/8729425.html

爲何說B+樹比B樹更適合數據庫索引

  • B+樹的磁盤讀寫代價更低:B+樹的內部節點並無指向關鍵字具體信息的指針,所以其內部節點相對B樹更小,若是把全部同一內部節點的關鍵字存放在同一盤塊中,那麼盤塊所能容納的關鍵字數量也越多,一次性讀入內存的須要查找的關鍵字也就越多,相對IO讀寫次數就下降了。

  • B+樹的查詢效率更加穩定:因爲非終結點並非最終指向文件內容的結點,而只是葉子結點中關鍵字的索引。因此任何關鍵字的查找必須走一條從根結點到葉子結點的路。全部關鍵字查詢的路徑長度相同,致使每個數據的查詢效率至關。

  • 因爲B+樹的數據都存儲在葉子結點中,分支結點均爲索引,方便掃庫,只須要掃一遍葉子結點便可,可是B樹由於其分支結點一樣存儲着數據,咱們要找到具體的數據,須要進行一次中序遍歷按序來掃,因此B+樹更加適合在區間查詢的狀況,因此一般B+樹用於數據庫索引。

JVM內存配置參數

  • -Xmx Java Heap最大值,默認值爲物理內存的1/4,最佳設值應該視物理內存大小及計算機內其餘內存開銷而定;

  • -Xms Java Heap初始值,Server端JVM最好將-Xms和-Xmx設爲相同值,開發測試機JVM能夠保留默認值;

  • -Xmn Java Heap Young區大小,不熟悉最好保留默認值;

  • -Xss 每一個線程的Stack大小,不熟悉最好保留默認值;

extends 抽象類和 interface 區別 (*)

  • 接口(interface)能夠說成是抽象類的一種特例,接口中的全部方法都必須是抽象的。

  • 接口中的方法定義默認爲 public abstract 類型,接口中的成員變量類型默認爲 public static final

  • 抽象類能夠有構造方法,接口中不能有構造方法。

  • 抽象類中能夠有普通成員變量,接口中沒有普通成員變量。

  • 抽象類中能夠包含非抽象的普通方法,接口中的全部方法必須都是抽象的,不能有非抽象的普通方法。

  • 抽象類中的抽象方法的訪問類型能夠是public,protected,但接口中的抽象方法只能是public類型的,而且默認即爲public abstract類型。

  • 抽象類中能夠包含靜態(static)方法,接口中不能包含靜態(static)方法。

  • 抽象類和接口中均可以包含靜態成員變量(static),抽象類中的靜態成員變量的訪問類型能夠任意,但接口中定義的變量只能是public static final類型,而且默認即爲public static final類型。

  • 一個類只能繼承一個抽象類,可是能夠實現多個接口。

  • 一個接口能夠繼承多個接口。

  • 抽象類所體現的是一種繼承關係,要想使得繼承關係合理,父類和派生類之間必須存在」is-a」關係關係,即父類和派生類在概念本質上應該是相同的。對於接口則否則,並不要求接口的實現者和接口定義在概念本質上是一致的,僅僅是實現了接口定義的契約而已,是」like-a」的關係。

Servlet生命週期

  • 調用 init() 方法初始化

  • 調用 service() 方法來處理客戶端的請求

  • 調用 destroy() 方法釋放資源,標記自身爲可回收

  • 被垃圾回收器回收

Cookie, Session區別

  • cookie數據存放在客戶的瀏覽器上,session數據放在服務器上

  • cookie不是很安全,別人能夠分析存放在本地的COOKIE並進行COOKIE欺騙,考慮到安全應當使用session

  • session會在必定時間內保存在服務器上。當訪問增多,會比較佔用你服務器的性能,考慮到減輕服務器性能方面,應當使用COOKIE

  • 單個cookie在客戶端的限制是3K,就是說一個站點在客戶端存放的COOKIE不能3K。

相關文章
相關標籤/搜索