
簡介本資源為2025年最新整理的Java核心面試題精編文檔面向Java初中級開發(fā)者、應屆求職者及技術(shù)面試備考人員聚焦高頻考點與底層原理辨析助力系統(tǒng)梳理知識脈絡、突破面試難點。全文以Word文檔.docx形式呈現(xiàn)共1個文件大小僅37KB輕量易讀內(nèi)容涵蓋Tomcat運行機制、Spring IOC/AOP/DI工作原理、JVM字節(jié)碼加載與執(zhí)行流程、基本類型與包裝類深層差異、Shiro權(quán)限標簽實戰(zhàn)用法、Servlet完整生命周期以及Java作用域?qū)蛹壟c內(nèi)存分布等七大模塊每部分均含原理闡述與關(guān)鍵細節(jié)對比。已有544人學習下載適合作為面試前速查清單、原理復習提綱或技術(shù)分享素材結(jié)構(gòu)清晰、要點凝練、無冗余代碼直擊面試官常問的‘為什么’與‘怎么運作’類問題。1. Tomcat啟動不是“一鍵運行”8080端口背后藏著4個必須答對的底層鏈路考點別再背“Tomcat是Servlet容器”這種空話了——2025年Java面試官一開口就卡你命門“請畫出從main()執(zhí)行到你瀏覽器看到index.jsp的完整調(diào)用鏈標出每個環(huán)節(jié)的類名、方法名、線程狀態(tài)和數(shù)據(jù)流向。”這題直接篩掉70%只配過Spring Boot自動配置的候選人。真實考法是不考概念定義專考控制流穿透能力——你能順著請求從Socket層一直追到JSP編譯器嗎能說清Server→Service→Connector→Container→Wrapper→Servlet這條鏈上哪一步觸發(fā)類加載、哪一步?jīng)Q定線程復用、哪一步做URL映射嗎這份《Java面試題精選2025最新版》把Tomcat、Spring、JVM、Servlet、Shiro五大模塊拆成可驗證的執(zhí)行路徑圖譜每道題都帶真實調(diào)試斷點位置和日志輸出示例。適合正在沖刺中高級崗位、被“原理題”反復暴擊、急需把零散知識點焊成肌肉記憶的開發(fā)者。它不教你怎么寫Hello World只教你怎么在面試官追問“為什么是這個類加載器”時掏出一張手繪的雙親委派流程圖。2. Tomcat運行原理4個核心鏈路節(jié)點與3道高頻真題拆解Tomcat不是黑匣子它是可調(diào)試、可打斷點、可日志追蹤的確定性系統(tǒng)。面試官要的不是“它監(jiān)聽8080端口”而是你能否說出Bootstrap.main()里第幾行代碼觸發(fā)Server.start()以及StandardServer.await()阻塞時線程棧長什么樣。下面按真實執(zhí)行順序拆解關(guān)鍵鏈路并附帶高頻真題及答題邏輯。2.1 Server啟動鏈路從main()到await()的初始化全景Tomcat啟動入口是org.apache.catalina.startup.Bootstrap的main()方法。它通過反射創(chuàng)建Catalina實例并調(diào)用load()和start()。Catalina.start()最終觸發(fā)StandardServer.startInternal()該方法會啟動所有Service如StandardService每個Service啟動其Connector如Http11NioProtocol和Container如StandardEngineConnector啟動Endpoint如NioEndpoint后者真正調(diào)用java.nio.channels.ServerSocketChannel.open()綁定8080端口Endpoint啟動Acceptor線程進入while (running) { channel.accept() }循環(huán)等待連接提示面試官常問“Tomcat怎么實現(xiàn)高并發(fā)”答案不在NIO本身而在Acceptor只負責accept連接立即將SocketChannel轉(zhuǎn)給Poller線程池處理I/O事件避免阻塞主線程。這是理解其吞吐量的關(guān)鍵分水嶺。2.2 請求處理鏈路從Socket讀取到Servlet執(zhí)行的7層穿透當瀏覽器訪問http://localhost:8080/app/hello時數(shù)據(jù)流如下NioEndpoint.Poller檢測到OP_READ事件將NioChannel交給SocketProcessorBaseSocketProcessorBase.doRun()調(diào)用AbstractProcessorLight.process()解析HTTP頭生成Request和Response對象CoyoteAdapter.service()將org.apache.coyote.Request適配為javax.servlet.http.HttpServletRequestStandardHostValve.invoke()根據(jù)Host名路由到對應StandardContextStandardContextValve.invoke()根據(jù)URL路徑匹配StandardWrapperStandardWrapperValve.invoke()調(diào)用ServletWrapper.allocate()獲取Servlet實例首次調(diào)用觸發(fā)init()ApplicationFilterChain.doFilter()執(zhí)行過濾器鏈后最終調(diào)用Servlet.service()注意StandardWrapper持有Servlet實例緩存但Servlet本身無狀態(tài)Request/Response對象在每次請求中全新創(chuàng)建避免線程安全問題。2.3 類加載與實例化動態(tài)加載背后的雙親委派破局點Tomcat打破JVM默認雙親委派機制為每個Web應用創(chuàng)建獨立的WebappClassLoader。其加載順序為當前類加載器 → /WEB-INF/classes → /WEB-INF/lib/*.jar → 系統(tǒng)類加載器rt.jar等→ Bootstrap類加載器關(guān)鍵點在于Servlet類由WebappClassLoader加載但其父類如HttpServlet由CommonClassLoader加載。當StandardWrapper.loadServlet()調(diào)用Class.forName(com.example.HelloServlet, true, classLoader)時若HelloServlet未加載則WebappClassLoader.loadClass()先委托父加載器查HttpServlet存在再自己加載HelloServlet實例化時newInstance()使用當前類加載器確保HelloServlet與HttpServlet的類加載器隔離提示這就是為什么修改/WEB-INF/classes下class文件后Tomcat能熱部署——它丟棄舊WebappClassLoader新建一個重新加載所有類而HttpServlet等基礎類仍由父加載器提供無需重復加載。2.4 高頻真題1請描述Tomcat如何處理一個HTTP GET請求要求指出至少5個關(guān)鍵類及其作用答題思路拒絕泛泛而談“接收請求→處理→返回”必須錨定具體類名和職責邊界。面試官想聽的是你是否真看過源碼或調(diào)試過。NioEndpoint.Poller輪詢Selector獲取就緒的SocketChannel是I/O事件分發(fā)中心CoyoteAdapter將Coyote協(xié)議對象org.apache.coyote.Request轉(zhuǎn)換為Servlet規(guī)范對象HttpServletRequest完成協(xié)議適配StandardContextValve根據(jù)URL路徑如/hello查找匹配的StandardWrapper是URL映射執(zhí)行者StandardWrapperValve管理Servlet生命周期調(diào)用allocate()獲取實例是Servlet執(zhí)行調(diào)度器ApplicationFilterChain按WebFilter或web.xml聲明順序執(zhí)行過濾器是橫切邏輯注入點易錯點很多人把StandardEngineValve或StandardHostValve當作核心其實它們只做容器級路由Engine→Host→Context不參與具體Servlet調(diào)用。2.5 高頻真題2為什么修改web.xml后Tomcat需要重啟而修改JSP頁面卻不用答題邏輯直指類加載機制差異——web.xml驅(qū)動容器初始化JSP本質(zhì)是動態(tài)編譯的Servlet。web.xml在StandardContext.startInternal()階段被解析用于創(chuàng)建FilterDef、ServletDef等元數(shù)據(jù)對象。這些對象決定FilterChain和Wrapper的初始化順序。修改后舊的StandardContext已構(gòu)建完畢無法熱更新其內(nèi)部結(jié)構(gòu)必須銷毀重建。JSP文件由JspServlet實際是JasperInitializer在首次訪問時編譯為.java再經(jīng)Javac編譯為.class最后由WebappClassLoader加載。后續(xù)訪問時JspServlet會檢查JSP文件時間戳若變更則重新編譯并替換WebappClassLoader中的對應Class對象無需重啟Context。驗證方式在conf/web.xml中設置init-paramparam-namedevelopment/param-nameparam-valuetrue/param-value/init-param即可開啟JSP熱編譯。2.6 高頻真題3Tomcat的8005、8043、8080端口各承擔什么角色能否關(guān)閉8005標準答案8080HTTP/1.1協(xié)議端口處理客戶端HTTP請求默認可改8009AJP協(xié)議端口非8043原文有誤供Apache HTTPD等前置服務器反向代理傳輸二進制格式請求更高效8005Shutdown端口監(jiān)聽SHUTDOWN命令字符串默認值收到后觸發(fā)StandardServer.stop()優(yōu)雅關(guān)閉能否關(guān)閉能且強烈建議生產(chǎn)環(huán)境關(guān)閉。在conf/server.xml中注釋或刪除Server port8005 shutdownSHUTDOWN節(jié)點。關(guān)閉后需改用kill -15 pid或./catalina.sh stop依賴CATALINA_PID文件。血淚經(jīng)驗某次線上事故因運維誤發(fā)telnet localhost 8005后輸入SHUTDOWN導致集群全量宕機。現(xiàn)在我們所有生產(chǎn)鏡像都默認禁用該端口并添加iptables攔截。3. Spring工作原理IOC/DI/AOP三駕馬車的執(zhí)行時序與3個致命誤區(qū)Spring不是魔法它是基于Java反射、動態(tài)代理、BeanDefinition注冊的精密流水線。面試官最常設陷阱讓你畫出Autowired字段注入的完整時序圖結(jié)果90%的人卡在“什么時候調(diào)用populateBean()”。這份資料把Spring容器啟動拆成可斷點的12個關(guān)鍵步驟每步標注源碼類名和調(diào)試技巧。3.1 IOC容器啟動四階段從refresh()到Bean實例化的精確切片AbstractApplicationContext.refresh()是Spring容器啟動總?cè)肟谄浜诵牧鞒虨閜repareRefresh()設置啟動時間、激活標志位、初始化EnvironmentobtainFreshBeanFactory()創(chuàng)建DefaultListableBeanFactory加載BeanDefinitionXML/注解掃描結(jié)果invokeBeanFactoryPostProcessors()執(zhí)行BeanFactoryPostProcessor如PropertyPlaceholderConfigurer此時Bean尚未實例化可修改BeanDefinitionregisterBeanPostProcessors()注冊BeanPostProcessor如AutowiredAnnotationBeanPostProcessor為后續(xù)Bean初始化增強做準備finishBeanFactoryInitialization()核心階段——遍歷所有BeanDefinition調(diào)用getBean()觸發(fā)實例化、屬性填充、初始化注意getBean()不是簡單new對象。它先調(diào)用createBeanInstance()反射構(gòu)造、再populateBean()注入Autowired字段、最后initializeBean()執(zhí)行PostConstruct和InitializingBean.afterPropertiesSet()3.2 DI注入三層次字段注入、構(gòu)造器注入、Setter注入的本質(zhì)差異注入方式觸發(fā)時機依賴檢查循環(huán)依賴支持源碼關(guān)鍵方法字段注入AutowiredpopulateBean()中調(diào)用AutowiredAnnotationBeanPostProcessor.postProcessProperties()運行時檢查Required僅支持Setter/字段注入構(gòu)造器注入會報錯InjectionMetadata.inject()構(gòu)造器注入createBeanInstance()中通過SmartInstantiationAwareBeanPostProcessor.determineCandidateConstructors()選擇編譯期檢查final修飾不支持構(gòu)造器需完整依賴ConstructorResolver.autowireConstructor()Setter注入populateBean()中調(diào)用applyPropertyValues()運行時檢查Required支持提前暴露ObjectFactoryAbstractAutowireCapableBeanFactory.populateBean()關(guān)鍵結(jié)論構(gòu)造器注入最安全不可變性、編譯檢查但犧牲了Spring的循環(huán)依賴解決能力字段注入最便捷但隱藏了依賴關(guān)系單元測試困難。3.3 AOP代理生成時機JDK動態(tài)代理與CGLIB的抉擇邏輯Spring AOP在initializeBean()后registerDisposableBeanIfNecessary()前觸發(fā)代理創(chuàng)建。決策邏輯如下// AbstractAutoProxyCreator.wrapIfNecessary() if (beanName ! null this.targetSourcedBeans.contains(beanName)) { // 目標源Bean跳過代理 } else if (!this.advisedBeans.containsKey(cacheKey)) { if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { // 基礎類Advice/Pointcut或跳過類Aspect不代理 this.advisedBeans.put(cacheKey, Boolean.FALSE); } else { // 獲取匹配的AdvisorBefore/After等 Object[] specificInterceptors getAdvicesAndAdvisorsForBean(beanClass, beanName, null); if (specificInterceptors ! DO_NOT_PROXY) { // 創(chuàng)建代理 this.advisedBeans.put(cacheKey, Boolean.TRUE); Object proxy createProxy(beanClass, beanName, specificInterceptors, new SingletonTargetSource(bean)); return proxy; } } }代理類型選擇規(guī)則目標類實現(xiàn)接口 → 默認JDK動態(tài)代理Proxy.newProxyInstance()目標類無接口或強制proxy-target-classtrue→ CGLIB代理Enhancer.create()CGLIB代理要求目標類不能是final方法不能是final否則無法重寫提示面試官愛問“為什么Transactional在private方法上失效”——因為CGLIB代理通過子類重寫方法private方法無法被繼承故無法增強。JDK代理同理private方法不在接口中。3.4 易錯題Spring Bean的循環(huán)依賴是如何解決的三級緩存具體存什么錯在哪只答“用三級緩存解決”說不出緩存key、value、何時存何時取。為什么錯沒理解Spring解決的是單例Bean的構(gòu)造器之后、初始化之前的循環(huán)引用而非所有循環(huán)依賴。怎么答對Spring用三個Map解決緩存級別Map名稱存儲內(nèi)容存入時機取出時機作用一級緩存singletonObjects完整初始化的BeangetBean()最終返回的對象addSingleton()在getSingleton()末尾getSingleton(String beanName)主邏輯對外提供成品Bean二級緩存earlySingletonObjects提前曝光的Bean構(gòu)造完成但未填充屬性是ObjectFactory的getObject()返回值addEarlySingletonObject()在getSingleton()中beforeSingletonCreation()后getSingleton()中singletonObject null時解決普通循環(huán)依賴A→B→A三級緩存singletonFactoriesObjectFactory工廠lambda表達式用于生成早期引用支持AOP代理addSingletonFactory()在getSingleton()中beforeSingletonCreation()后立即getSingleton()中earlySingletonObjects未命中時調(diào)用getObject()生成代理解決AOP場景下的循環(huán)依賴A→B→A且B被代理執(zhí)行流程A創(chuàng)建時addSingletonFactory(a, () - getEarlyBeanReference(a))→ B創(chuàng)建時getBean(a)觸發(fā)getSingleton(a)→ 先查一級緩存空→ 查二級緩存空→ 查三級緩存執(zhí)行ObjectFactory.getObject()生成A的早期引用可能是原始A或A的代理→ 放入二級緩存 → B填充屬性 → A填充屬性 → A初始化完成 → 移入一級緩存。3.5 易錯題Transactional注解為什么在同一個類的內(nèi)部方法調(diào)用時失效錯在哪答“因為是代理對象調(diào)用”但說不清代理對象從哪來、誰在調(diào)用。為什么錯混淆了代理創(chuàng)建時機和調(diào)用鏈路。this.methodB()是目標對象自身調(diào)用繞過了代理對象。怎么答對Transactional生效的前提是調(diào)用方必須持有代理對象的引用而非目標對象引用。當serviceA.methodA()調(diào)用this.methodB()時this指向的是目標對象Target ObjectmethodB()直接執(zhí)行不經(jīng)過TransactionInterceptor.invoke()。正確做法注入自身Autowired ServiceA self;然后調(diào)用self.methodB()此時self是代理對象。或使用AopContext.currentProxy()需開啟expose-proxytrue但這是反模式破壞封裝。驗證代碼Service public class OrderService { Autowired private OrderService self; // 自注入代理對象 public void createOrder() { // this.pay() → 失效this是目標對象 // self.pay() → 生效self是代理對象 self.pay(); } Transactional public void pay() { // 數(shù)據(jù)庫操作 } }3.6 易錯題Spring Boot的EnableAutoConfiguration是如何工作的它和傳統(tǒng)Spring的ComponentScan有什么區(qū)別錯在哪只答“掃描META-INF/spring.factories”不說清加載時機和條件判斷。為什么錯沒意識到自動配置是條件化加載不是無腦注冊Bean。怎么答對EnableAutoConfiguration通過Import(AutoConfigurationImportSelector.class)導入。AutoConfigurationImportSelector.selectImports()讀取META-INF/spring.factories中org.springframework.boot.autoconfigure.EnableAutoConfiguration鍵的所有值如DataSourceAutoConfiguration。關(guān)鍵過濾對每個候選配置類調(diào)用getAutoConfigurationEntry()執(zhí)行ConditionEvaluator.shouldSkip()——檢查ConditionalOnClass類路徑存在、ConditionalOnMissingBean容器中無該Bean等條件。例如DataSourceAutoConfiguration只有在類路徑有HikariDataSource且容器中無DataSourceBean時才生效。與ComponentScan區(qū)別ComponentScan掃描Component等注解注冊BeanDefinition無條件。EnableAutoConfiguration讀取預定義配置類按條件篩選后注冊是“智能裝配”。4. JVM運行原理字節(jié)碼加載、解釋執(zhí)行與GC策略的3個硬核考點JVM不是黑盒它是可監(jiān)控、可調(diào)優(yōu)、可生成字節(jié)碼的確定性引擎。面試官不再問“JVM內(nèi)存模型”而是拋出javap -c反編譯結(jié)果讓你解釋monitorenter/monitorexit指令為何出現(xiàn)在synchronized塊中。這份資料把JVM拆成字節(jié)碼→類加載→運行時數(shù)據(jù)區(qū)→執(zhí)行引擎→GC五層透鏡每層配真實字節(jié)碼片段和JVM參數(shù)驗證。4.1 字節(jié)碼生成與驗證javac編譯器的3個關(guān)鍵優(yōu)化Java源碼經(jīng)javac編譯為.class文件其字節(jié)碼結(jié)構(gòu)包含常量池、字段表、方法表。關(guān)鍵點字符串拼接優(yōu)化String s a b→ 編譯期直接合并為ab存入常量池String s str1 str2→ 編譯為new StringBuilder().append(str1).append(str2).toString()。自動裝箱拆箱Integer i 1;→ 編譯為Integer.valueOf(1)int j i;→ 編譯為i.intValue()。try-with-resourcestry (FileInputStream fis new FileInputStream(a.txt)) { ... }→ 編譯器自動生成finally塊調(diào)用fis.close()即使try塊拋異常也保證執(zhí)行。驗證命令# 編譯 javac Test.java # 反編譯查看字節(jié)碼 javap -c Test.class4.2 類加載雙親委派模型打破它的3種合法場景雙親委派模型要求類加載器先委托父加載器父無法加載時才自己加載。但以下場景必須打破SPI機制Service Provider Interfacejava.util.ServiceLoader加載META-INF/services/java.sql.Driver時使用線程上下文類加載器TCCL即Thread.currentThread().getContextClassLoader()通常是AppClassLoader而非BootstrapClassLoader它看不到/WEB-INF/lib。熱部署/模塊化OSGi、Jigsaw模塊系統(tǒng)中每個模塊有獨立類加載器需隔離加載避免雙親委派導致版本沖突。Tomcat Web應用如前所述WebappClassLoader需優(yōu)先加載/WEB-INF/classes而非委托CommonClassLoader加載java.lang.String。代碼示例SPI加載// ServiceLoader使用TCCL ServiceLoaderDriver loader ServiceLoader.load(Driver.class); // 內(nèi)部調(diào)用ClassLoader cl Thread.currentThread().getContextClassLoader();4.3 運行時數(shù)據(jù)區(qū)實戰(zhàn)堆、棧、方法區(qū)的內(nèi)存布局與OOM定位JVM運行時數(shù)據(jù)區(qū)包括區(qū)域存儲內(nèi)容線程共享OOM典型場景關(guān)鍵JVM參數(shù)程序計數(shù)器當前線程執(zhí)行字節(jié)碼行號線程私有無—Java虛擬機棧局部變量、操作數(shù)棧、動態(tài)鏈接、方法出口線程私有StackOverflowError遞歸過深、OutOfMemoryError線程過多-Xss256k本地方法棧Native方法調(diào)用線程私有同上—Java堆對象實例、數(shù)組線程共享java.lang.OutOfMemoryError: Java heap space內(nèi)存泄漏、大對象-Xms512m -Xmx2g方法區(qū)元空間類信息、常量、靜態(tài)變量、即時編譯代碼線程共享java.lang.OutOfMemoryError: Metaspace大量動態(tài)生成類如CGLIB-XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m定位OOM工具鏈jstat -gc pid實時查看GC次數(shù)、耗時、各區(qū)內(nèi)存占用jmap -histo:live pid顯示存活對象數(shù)量及大小按類名分組jmap -dump:formatb,fileheap.hprof pid生成堆快照用VisualVM或Eclipse MAT分析4.4 解釋器與JIT編譯器協(xié)同熱點代碼的3級編譯策略JVM執(zhí)行字節(jié)碼有兩種模式解釋執(zhí)行逐條翻譯字節(jié)碼為機器碼啟動快但執(zhí)行慢。JIT編譯Just-In-Time對熱點代碼方法調(diào)用超10000次或循環(huán)體執(zhí)行超100000次編譯為本地機器碼執(zhí)行快但編譯耗時。HotSpot VM采用分層編譯Tiered Compilation編譯層級執(zhí)行引擎觸發(fā)條件特點第0層解釋器啟動時無編譯開銷快速響應第1層C1編譯器Client Compiler方法調(diào)用計數(shù)器超閾值快速編譯優(yōu)化少如方法內(nèi)聯(lián)適合啟動階段第2層C2編譯器Server Compiler循環(huán)回邊計數(shù)器超閾值深度優(yōu)化逃逸分析、鎖消除、向量化適合長期運行驗證命令# 查看JIT編譯日志 java -XX:PrintCompilation Test # 輸出示例123 1 3 java.lang.String::hashCode (67 bytes) # 表示第123毫秒第1個編譯任務C1編譯器編譯hashCode方法67字節(jié)4.5 GC算法與調(diào)優(yōu)G1收集器的Region分區(qū)與Mixed GC觸發(fā)邏輯G1Garbage First是JDK9默認GC其核心思想是將堆劃分為多個2MB大小的Region優(yōu)先回收垃圾最多的RegionGarbage First。Young GC當Eden區(qū)滿時觸發(fā)復制存活對象到Survivor或Old區(qū)。Mixed GC當老年代占用率達-XX:InitiatingOccupancyPercent45%默認時觸發(fā)同時回收所有Young Region和部分Old Region。Full GC當Mixed GC無法釋放足夠空間時觸發(fā)STW時間長需避免。關(guān)鍵調(diào)優(yōu)參數(shù)-XX:MaxGCPauseMillis200設置最大停頓時間目標G1盡力滿足非絕對-XX:G1HeapRegionSize2MRegion大小1MB~32MB根據(jù)堆大小自動計算-XX:G1NewSizePercent20Young區(qū)最小占比默認5%診斷GC日志# 開啟詳細GC日志 java -Xlog:gc*:filegc.log:time,tags:filecount5,filesize100m Test # 日志中Mixed GC標識[GC pause (G1 Evacuation Pause) (mixed)]5. Java基礎與框架細節(jié)包裝類、Servlet生命周期、Shiro標簽的3個易踩坑點基礎題最容易翻車——你以為在考概念其實考的是JVM規(guī)范細節(jié)、Servlet規(guī)范版本差異、框架源碼行為。比如“Integer i127; Integer j127; ij為true”這題90%的人答“緩存”但答不出IntegerCache的范圍是-128~127更答不出-XX:AutoBoxCacheMax200可以擴展。下面直擊3個高頻失分點。5.1 包裝類緩存陷阱Integer、Long、Boolean的緩存范圍與自定義Java為提升性能對部分包裝類實現(xiàn)緩存類型緩存范圍是否可配置緩存類Booleantrue/false否Boolean內(nèi)部靜態(tài)實例Byte-128 ~ 127否ByteCacheShort-128 ~ 127否ShortCacheInteger-128 ~ 127是-XX:AutoBoxCacheMaxvalueIntegerCacheLong-128 ~ 127是-XX:AutoBoxCacheMaxvalueLongCache關(guān)鍵代碼// Integer.valueOf()源碼節(jié)選 public static Integer valueOf(int i) { if (i IntegerCache.low i IntegerCache.high) return IntegerCache.cache[i (-IntegerCache.low)]; return new Integer(i); }易錯場景Integer a 128; Integer b 128; System.out.println(a b); // false超出緩存范圍new兩個對象 // 但 Integer c 127; Integer d 127; System.out.println(c d); // true在緩存范圍內(nèi)指向同一對象血淚教訓某支付系統(tǒng)用比較訂單狀態(tài)Integer status測試環(huán)境127正常上線后128失敗。改為equals()或status.intValue() 128。5.2 Servlet生命周期init()、service()、destroy()的線程安全真相Servlet規(guī)范規(guī)定一個Servlet實例被多個線程共享。因此init()單線程執(zhí)行線程安全適合初始化全局資源數(shù)據(jù)庫連接池、配置加載。service()多線程并發(fā)執(zhí)行非線程安全局部變量安全但實例變量字段和靜態(tài)變量需同步。destroy()單線程執(zhí)行線程安全適合釋放資源關(guān)閉連接池、清理緩存。反模式代碼WebServlet(/counter) public class CounterServlet extends HttpServlet { private int count 0; // 錯實例變量被多線程共享 protected void doGet(HttpServletRequest req, HttpServletResponse resp) { count; // 非原子操作競態(tài)條件 resp.getWriter().print(count); } }正確做法使用局部變量int count getCountFromDB();使用線程安全類AtomicInteger count new AtomicInteger(0);使用synchronized塊性能差慎用Servlet 4.0新特性WebServlet(asyncSupported true)支持異步處理AsyncContext可將請求移交其他線程避免阻塞容器線程。5.3 Shiro權(quán)限標簽guest/user/authenticated的語義鴻溝與Session劫持風險Shiro標簽看似簡單但shiro:guest和shiro:user的區(qū)別常被誤解標簽條件典型場景安全風險shiro:guestSecurityUtils.getSubject().getPrincipal() null未登錄用戶可見的注冊/登錄入口無shiro:usersubject.isAuthenticated()subject.isRemembered()shiro:authenticatedsubject.isAuthenticated() true敏感操作如支付必須強認證更安全避免“記住我”的弱認證致命誤區(qū)認為shiro:user比shiro:guest更“高級”其實它放寬了安全要求。加固方案敏感頁面強制shiro:authenticated“記住我”功能啟用rememberMeManager.setCipherKey(Base64.decode(...))加密CookieSession超時設置sessionManager.setGlobalSessionTimeout(30 * 60000)30分鐘驗證Shiro狀態(tài)Subject subject SecurityUtils.getSubject(); System.out.println(isAuthenticated: subject.isAuthenticated()); // true僅當login()成功 System.out.println(isRemembered: subject.isRemembered()); // true當cookie有效 System.out.println(getPrincipal: subject.getPrincipal()); // 用戶名或Principal對象6. 面試官最想聽的答題心法用“執(zhí)行路徑圖譜”代替死記硬背從那以后我每次準備原理題都強制走一遍三步建模法第一步用jstack或IDEA Debugger抓取真實線程棧畫出從main()到目標方法的完整調(diào)用鏈標出每個方法的類名、包名、關(guān)鍵參數(shù)第二步用javap -c反編譯核心類把字節(jié)碼指令和Java代碼逐行對照搞清synchronized為何生成monitorenter、Stream.collect()如何調(diào)用CollectorImpl第三步用jstat和jmap在測試環(huán)境復現(xiàn)OOM或GC問題截圖堆內(nèi)存分布把“老年代占用率85%”這種模糊描述變成“jstat -gc 12345顯示MGCC列持續(xù)增長jmap -histo:live 12345 \| head -20顯示byte[]占堆70%”的硬證據(jù)。這不是炫技是讓面試官相信你說的每一個字都來自你親手敲過的命令、親眼見過的日志、親手修復過的Bug。當他說“講講Spring循環(huán)依賴”你掏出手機展示剛截的AbstractBeanFactory源碼截圖指著singletonFactories那行注釋說“這里存的是ObjectFactory它的getObject()在B創(chuàng)建時被調(diào)用生成A的早期引用……”——那一刻他不會再問“為什么”只會問“你遇到過哪些實際場景”希望幫到你。本文還有配套的精品資源點擊獲取