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

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

 

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

Java基礎知識(*)

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

Spring Boot 啓動 流程(*)

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

Spring 一些面試題(*)

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

匿名內部類編譯class(*)

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

爲什麼集合類沒有實現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之間的依賴關係,爲上層應用提供準備就緒的運行環境。

enter image description here

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原理(*)

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

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% 的內存會被“浪費”。

enter image description here

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

爲什麼分代收集

  • 不同的對象的生命週期(存活情況)是不一樣的,而不同生命週期的對象位於堆中不同的區域,因此對堆內存不同區域採用不同的策略進行回收可以提高 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。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章