[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"\u002Fresource\u002Fdocument\u002Flist?undefined":3,"\u002Fresource\u002Fdocument\u002Fquery\u002Fg96k66kczovvbm1i?undefined":462,"\u002Fresource\u002Fadvertise\u002Flist?type=all?undefined":467},{"data":4,"status":460,"success":461},[5,148,202,291,332,370,420],{"books":6,"desc":145,"id":8,"image":146,"title":147},[7,40,63,78,93,105,117],{"cateId":8,"chapters":9,"desc":36,"id":11,"time":37,"title":38,"video":39},1,[10,15,18,21,24,27,30,33],{"bookId":11,"id":12,"indexOrder":13,"name":14},24,"8egfulw98v3h680j",0,"JavaSE 笔记（一）走进Java语言",{"bookId":11,"id":16,"indexOrder":13,"name":17},"pew6po6wrou23pk3","JavaSE 笔记（二）面向过程编程",{"bookId":11,"id":19,"indexOrder":13,"name":20},"eldst1fgrbdkmfs7","JavaSE 笔记（三）面向对象基础",{"bookId":11,"id":22,"indexOrder":13,"name":23},"48zphgkpjto8cath","JavaSE 笔记（四）面向对象高级篇",{"bookId":11,"id":25,"indexOrder":13,"name":26},"6r4llai92yc15j98","JavaSE 笔记（五）泛型程序设计",{"bookId":11,"id":28,"indexOrder":13,"name":29},"k6fmxd6qabgkwm9i","JavaSE 笔记（六）集合类与IO",{"bookId":11,"id":31,"indexOrder":13,"name":32},"qrd0xfttsz32gpqg","JavaSE 笔记（七）多线程与反射",{"bookId":11,"id":34,"indexOrder":13,"name":35},"td5tgn04nqmkrryt","JavaSE 笔记（八）GUI程序开发","基于Java25全新录制的SE课程",2025,"JavaSE 核心内容","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV163GGz2E8c",{"cateId":8,"chapters":41,"desc":59,"id":8,"time":60,"title":61,"video":62},[42,44,46,49,51,53,55,57],{"bookId":8,"id":43,"indexOrder":13,"name":14},"ibeeuwsbbi00undq",{"bookId":8,"id":45,"indexOrder":13,"name":17},"dncxjecdv4wciqcp",{"bookId":8,"id":47,"indexOrder":13,"name":48},"jviyz2hsht9ete5k","JavaSE 笔记（三）面向对象基础篇",{"bookId":8,"id":50,"indexOrder":13,"name":23},"qb9i6q9fap7bg1cc",{"bookId":8,"id":52,"indexOrder":13,"name":26},"hnkrjrkm3hjzeq6s",{"bookId":8,"id":54,"indexOrder":13,"name":29},"erpm32wduoaaqmrx",{"bookId":8,"id":56,"indexOrder":13,"name":32},"lfqtvxr7azumcwja",{"bookId":8,"id":58,"indexOrder":13,"name":35},"qs7gqok56gzc6idr","2022年制作的JavaSE版本",2022,"JavaSE 22年旧版","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1YP4y1o75f\u002F",{"cateId":8,"chapters":64,"desc":75,"id":66,"time":60,"title":76,"video":77},[65,69,72],{"bookId":66,"id":67,"indexOrder":13,"name":68},2,"g96k66kczovvbm1i","JVM 笔记（一）走进JVM",{"bookId":66,"id":70,"indexOrder":13,"name":71},"ydd7n3jg8unc3clg","JVM 笔记（二）内存管理",{"bookId":66,"id":73,"indexOrder":13,"name":74},"r9dq37de0kaeauoi","JVM 笔记（三）类与类加载","了解Java的底层运作机制","Java JVM 虚拟机","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Er4y1r7as\u002F",{"cateId":8,"chapters":79,"desc":90,"id":81,"time":60,"title":91,"video":92},[80,84,87],{"bookId":81,"id":82,"indexOrder":13,"name":83},3,"asncyye9ya18gfar","JUC 笔记（一）再谈多线程",{"bookId":81,"id":85,"indexOrder":13,"name":86},"5tr1sm4ho6ygpt9q","JUC 笔记（二）并发编程核心",{"bookId":81,"id":88,"indexOrder":13,"name":89},"1scf51z5300mzxkh","JUC 笔记（三）并发编程进阶","你也可以成为多线程的主宰者","Java JUC 并发编程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1JT4y1S7K8\u002F",{"cateId":8,"chapters":94,"desc":102,"id":96,"time":60,"title":103,"video":104},[95,99],{"bookId":96,"id":97,"indexOrder":13,"name":98},4,"eedesc445ygiqhil","NIO 笔记（一）基础内容",{"bookId":96,"id":100,"indexOrder":13,"name":101},"ndz9t0uunrmfmv4n","NIO 笔记（二）Netty框架专题","编写畅快的高性能网络服务器","Java NIO 网络编程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1ar4y1J7mC\u002F",{"cateId":8,"chapters":106,"desc":114,"id":108,"time":60,"title":115,"video":116},[107,111],{"bookId":108,"id":109,"indexOrder":13,"name":110},5,"9890i8ofuadpwy2b","[扩展篇] Java 9-17新特性介绍",{"bookId":108,"id":112,"indexOrder":13,"name":113},"tsrkqvb6zpmtwh0n","[扩展篇] JavaSE关键字总结 笔记","精彩仍在继续，不要停止脚步","其他内容","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1tU4y1y7Fg\u002F",{"cateId":8,"chapters":118,"desc":141,"id":120,"time":142,"title":143,"video":144},[119,123,126,129,132,135,138],{"bookId":120,"id":121,"indexOrder":13,"name":122},6,"4db9h32opv7imszh","JavaSE 笔记（一）面向过程编程",{"bookId":120,"id":124,"indexOrder":13,"name":125},"c93u3v37br7hgn1q","JavaSE 笔记（二）面向对象基础篇",{"bookId":120,"id":127,"indexOrder":13,"name":128},"yglsjde9gi1jxkcb","JavaSE 笔记（三）泛型与集合类",{"bookId":120,"id":130,"indexOrder":13,"name":131},"ilhi987n986rmvo3","JavaSE 笔记（四）异常机制",{"bookId":120,"id":133,"indexOrder":13,"name":134},"pqv38vexmenglk4k","JavaSE 笔记（五）IO",{"bookId":120,"id":136,"indexOrder":13,"name":137},"jiq41n87i9ia7ilw","JavaSE 笔记（六）多线程",{"bookId":120,"id":139,"indexOrder":13,"name":140},"wn7x2mge9ws79zps","JavaSE 笔记（七）反射","此版本为早期录制的旧版本",2021,"JavaSE 21年旧版","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Gv411T7pi\u002F","包含JavaSE基础路线全部教程笔记，打下坚实的基础","https:\u002F\u002Fpic2.zhimg.com\u002F80\u002Fv2-bf1a927f037a79f4d57d9ae543430a0d_1440w.webp","JavaSE 系列笔记 ☕️",{"books":149,"desc":199,"id":66,"image":200,"title":201},[150,166,178],{"cateId":66,"chapters":151,"desc":162,"id":153,"time":163,"title":164,"video":165},[152,156,159],{"bookId":153,"id":154,"indexOrder":13,"name":155},21,"iqbc2haub31bwqtz","Lombok 极速上手",{"bookId":153,"id":157,"indexOrder":13,"name":158},"ijay2hay19kn1k031","Mybatis 快速上手",{"bookId":153,"id":160,"indexOrder":13,"name":161},"ru4ogh2waocpn4jo","Maven 快速上手","JavaWeb阶段必须扩展知识点",2024,"常用知识讲解","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1gb421J7ok\u002F",{"cateId":66,"chapters":167,"desc":175,"id":169,"time":163,"title":176,"video":177},[168,172],{"bookId":169,"id":170,"indexOrder":13,"name":171},22,"ek20yvb6huhxizx7","JavaWeb 笔记（一）计算机网络基础",{"bookId":169,"id":173,"indexOrder":13,"name":174},"pgevws6w2krkffa4","JavaWeb笔记（二）Java与数据库","全面升级的JavaWeb课程","JavaWeb 网站开发","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1kS421X7rq\u002F",{"cateId":66,"chapters":179,"desc":196,"id":181,"time":142,"title":197,"video":198},[180,184,187,190,193],{"bookId":181,"id":182,"indexOrder":13,"name":183},7,"ggwwj09j2vkfftvd","JavaWeb 笔记（一）Java网络编程",{"bookId":181,"id":185,"indexOrder":13,"name":186},"sauvq105istskjaz","JavaWeb 笔记（二）数据库基础",{"bookId":181,"id":188,"indexOrder":13,"name":189},"xgbeasmvrhxx9tn4","JavaWeb 笔记（三）Java与数据库",{"bookId":181,"id":191,"indexOrder":13,"name":192},"k7dfwua3bsezvw9q","JavaWeb 笔记（四）前端基础",{"bookId":181,"id":194,"indexOrder":13,"name":195},"ycpagby2v7j4p728","JavaWeb 笔记（五）后端开发","搭建属于自己的Web网站","JavaWeb 旧版","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1CL4y1i7qR\u002F","包含JavaWeb路线全套笔记，从零开始搭建自己的网站！","https:\u002F\u002Fpic3.zhimg.com\u002F80\u002Fv2-df3b38e3012258ed70c23b586309e3f6_1440w.webp","JavaWeb 系列笔记 🚛",{"books":203,"desc":288,"id":81,"image":289,"title":290},[204,220,235,255,273],{"cateId":81,"chapters":205,"desc":216,"id":207,"time":217,"title":218,"video":219},[206,210,213],{"bookId":207,"id":208,"indexOrder":13,"name":209},8,"h7sjo5oy0l03607e","SSM笔记（一）Spring基础",{"bookId":207,"id":211,"indexOrder":13,"name":212},"eve8gq72qmdb46sg","SSM笔记（二）SpringMvc基础",{"bookId":207,"id":214,"indexOrder":13,"name":215},"63v73g0zh1qlr6fk","SSM笔记（三）SpringSecurity基础","Spring的探索之路从这里开始",2023,"JavaSSM 基础部分","[\"https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Kv4y1x7is\u002F\", \"https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Lh4y1M7kx\u002F\", \"https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1fV411M7aS\u002F\"]",{"cateId":81,"chapters":221,"desc":232,"id":223,"time":217,"title":233,"video":234},[222,226,229],{"bookId":223,"id":224,"indexOrder":13,"name":225},16,"0k66v5r6slsfuog4","SpringBoot笔记（一）核心内容",{"bookId":223,"id":227,"indexOrder":13,"name":228},"bqlrnc2yvkaxo8s1","SpringBoot笔记（二）数据交互",{"bookId":223,"id":230,"indexOrder":13,"name":231},"wci9lb9tgea866jt","SpringBoot笔记（三）前后端分离","SpringBoot全新重制版","SpringBoot 新版","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1xu4y1m7UP\u002F",{"cateId":81,"chapters":236,"desc":252,"id":238,"time":60,"title":253,"video":254},[237,240,243,246,249],{"bookId":238,"id":239,"indexOrder":13,"name":225},9,"e43gl1ilygps032v",{"bookId":238,"id":241,"indexOrder":13,"name":242},"emnmd8nzfdb3hr50","SpringBoot笔记（二）Git版本控制",{"bookId":238,"id":244,"indexOrder":13,"name":245},"jjlolj5igvttvyhv","SpringBoot笔记（三）Redis数据库",{"bookId":238,"id":247,"indexOrder":13,"name":248},"skgr4ivb5curdoux","SpringBoot笔记（四）其他框架介绍",{"bookId":238,"id":250,"indexOrder":13,"name":251},"le91fqhu4dqui1k4","SpringBoot笔记（五）Linux系统","逐步走向企业级开发","SpringBoot 旧版","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1UL411V7f3\u002F",{"cateId":81,"chapters":256,"desc":270,"id":258,"time":60,"title":271,"video":272},[257,261,264,267],{"bookId":258,"id":259,"indexOrder":13,"name":260},10,"oejzo0l77zeb6a7e","SpringCloud笔记（一）微服务基础",{"bookId":258,"id":262,"indexOrder":13,"name":263},"f6eya9taaelsl35p","SpringCloud笔记（二）微服务进阶",{"bookId":258,"id":265,"indexOrder":13,"name":266},"35v1hbsfcdgagdnw","SpringCloud笔记（三）微服务应用",{"bookId":258,"id":268,"indexOrder":13,"name":269},"a782u84512tyuo1m","SpringCloud笔记（四）消息队列","体验微服务架构带来的魅力","SpringCloud 进阶","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1AL4y1j7RY\u002F",{"cateId":81,"chapters":274,"desc":285,"id":276,"time":142,"title":286,"video":287},[275,278,280,282],{"bookId":276,"id":277,"indexOrder":13,"name":209},11,"efjw75u8a251qxk5",{"bookId":276,"id":279,"indexOrder":13,"name":212},"guc134xb7sl78vju",{"bookId":276,"id":281,"indexOrder":13,"name":215},"u8ekxxucowr2b1tm",{"bookId":276,"id":283,"indexOrder":13,"name":284},"vkpmw9wbej21nei6","SSM笔记（四）MySQL进阶","此教程为2021年旧版教程","JavaSSM 旧版","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1xL4y1H7Tq\u002F","包含Spring全套框架笔记，从开始到Spring Boot，以及众多运维小知识。","https:\u002F\u002Fpic4.zhimg.com\u002F80\u002Fv2-28c3144421220d7c048703281bc34f63_1440w.webp","Spring 系列笔记 🍏",{"books":292,"desc":329,"id":96,"image":330,"title":331},[293,308],{"cateId":96,"chapters":294,"desc":305,"id":296,"time":60,"title":306,"video":307},[295,299,302],{"bookId":296,"id":297,"indexOrder":13,"name":298},12,"jd3e8u5cmvx5gco6","C语言（一）计算机思维导论",{"bookId":296,"id":300,"indexOrder":13,"name":301},"lqv77apvx82nkkio","C语言（二）基础语法",{"bookId":296,"id":303,"indexOrder":13,"name":304},"xb0b9t37gyv96xns","C语言（三）高级特性","包含高等院校需要教授的全部内容","C语言程序设计","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Cr4y137os\u002F",{"cateId":96,"chapters":309,"desc":326,"id":311,"time":60,"title":327,"video":328},[310,314,317,320,323],{"bookId":311,"id":312,"indexOrder":13,"name":313},13,"8a046ps2e4w6k4py","数据结构与算法（一）线性结构篇",{"bookId":311,"id":315,"indexOrder":13,"name":316},"3ma8db91f9zrnkja","数据结构与算法（二）树形结构篇",{"bookId":311,"id":318,"indexOrder":13,"name":319},"0lsjm59k7cgu4tpr","数据结构与算法（三）散列表篇",{"bookId":311,"id":321,"indexOrder":13,"name":322},"0qzy7bogo0g2pusa","数据结构与算法（四）图结构篇",{"bookId":311,"id":324,"indexOrder":13,"name":325},"6gmcxcikcilyxblj","数据结构与算法（五）排序算法篇","虽然很难，但是它是考研必学科目","数据结构与算法","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV13W4y127Ey\u002F","你的内心一直有一个坚定的声音在告诉你，一定要考上一名研究生，向着未来前进吧！","https:\u002F\u002Fpic2.zhimg.com\u002F80\u002Fv2-ac128404efb29ce1c9d1ccc61024f1d1_1440w.webp","C语言 系列笔记 🥬",{"books":333,"desc":367,"id":108,"image":368,"title":369},[334,349,358],{"cateId":108,"chapters":335,"desc":346,"id":337,"time":163,"title":347,"video":348},[336,340,343],{"bookId":337,"id":338,"indexOrder":13,"name":339},17,"urw2e6gg1lprv65w","Kotlin（一）基础语法",{"bookId":337,"id":341,"indexOrder":13,"name":342},"t7lnl87f74f3v1ju","Kotlin（二）类与对象",{"bookId":337,"id":344,"indexOrder":13,"name":345},"v1zzvki0knb1xvml","Kotlin（三）高级特性","包含Kotlin语言完整基础部分","Kotlin程序设计基础","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1P94y1c7tV\u002F",{"cateId":108,"chapters":350,"desc":355,"id":352,"time":163,"title":356,"video":357},[351],{"bookId":352,"id":353,"indexOrder":13,"name":354},18,"ovbzpe7065bye1st","Kotlin扩展（一）","包含Kotlin额外扩展知识","Kotlin扩展篇","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Hg4y1m7Ca\u002F",{"cateId":108,"chapters":359,"desc":364,"id":361,"time":163,"title":365,"video":366},[360],{"bookId":361,"id":362,"indexOrder":13,"name":363},19,"3at7ybv04dmjc0wp","Gradle基础教程","Gradle配置教程（Kotlin）","Gradle教程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1Fc411x7xF\u002F","Kotlin让JVM平台焕发新的生机，让语言的表达更加优美","https:\u002F\u002Fpic2.zhimg.com\u002F80\u002Fv2-be815568f7c79c64cdaa171b0409786d_1440w.webp","Kotlin 系列笔记 ☘️",{"books":371,"desc":418,"id":120,"title":419},[372,391,403],{"cateId":120,"chapters":373,"desc":387,"id":375,"time":388,"title":389,"video":390},[374,378,381,384],{"bookId":375,"id":376,"indexOrder":13,"name":377},26,"zjf5qapwqtqiohcn","JavaScript笔记（一）基础语法",{"bookId":375,"id":379,"indexOrder":13,"name":380},"95jc6sjyjwcp9pvp","JavaScript笔记（二）核心知识",{"bookId":375,"id":382,"indexOrder":13,"name":383},"j35cdc1qz8dzq7pn","JavaScript笔记（三）进阶知识",{"bookId":375,"id":385,"indexOrder":13,"name":386},"sdhodlihphnpcg37","JavaScript笔记（四）前端基础","包含JavaScript最新语法规范讲解",2026,"JavaScript教程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1xq6gBgESU",{"cateId":120,"chapters":392,"desc":400,"id":394,"time":37,"title":401,"video":402},[393,397],{"bookId":394,"id":395,"indexOrder":13,"name":396},23,"bsisgazdftiz3o9c","HTML5笔记（一）基础内容",{"bookId":394,"id":398,"indexOrder":13,"name":399},"njol93fs34gfwuzf","HTML5笔记（二）高级内容","包含HTML基础内容和相关知识点","HTML5核心教程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1BrBiYNEWg",{"cateId":120,"chapters":404,"desc":415,"id":406,"time":37,"title":416,"video":417},[405,409,412],{"bookId":406,"id":407,"indexOrder":13,"name":408},25,"jo74ciirtg8wh90y","CSS笔记（一）基础入门",{"bookId":406,"id":410,"indexOrder":13,"name":411},"ap5ixyomoejuw4ue","CSS笔记（二）盒模型和布局",{"bookId":406,"id":413,"indexOrder":13,"name":414},"4djgk5xy1lzpiuf2","CSS笔记（三）变换和过渡","包含CSS3基础内容和相关知识点","CSS3核心教程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1sQeEzFEKi","包含Web前端学习路径全部教程笔记，打下坚实的基础","Web前端 系列笔记",{"books":421,"desc":458,"id":423,"image":368,"title":459},[422,432,450],{"cateId":423,"chapters":424,"desc":429,"id":426,"time":163,"title":430,"video":431},100,[425],{"bookId":426,"id":427,"indexOrder":13,"name":428},20,"o0ab271mkdsas87","Markdown基础语法","编写简洁而又优美的文档","Markdown教程","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1eJ4m157kC",{"cateId":423,"chapters":433,"desc":447,"id":435,"time":60,"title":448,"video":449},[434,438,441,444],{"bookId":435,"id":436,"indexOrder":13,"name":437},14,"6386mh7anqt4tzyv","设计模式（一）面向对象设计原则",{"bookId":435,"id":439,"indexOrder":13,"name":440},"8ftkb38wfn6ox0ug","设计模式（二）创建型",{"bookId":435,"id":442,"indexOrder":13,"name":443},"i1msql1k8y70etey","设计模式（三）结构型",{"bookId":435,"id":445,"indexOrder":13,"name":446},"5434a3cyyjvwhs8s","设计模式（四）行为型","使你的编码水平得到质的飞跃","设计模式系列","https:\u002F\u002Fwww.bilibili.com\u002Fvideo\u002FBV1u3411P7Na\u002F",{"cateId":423,"chapters":451,"desc":456,"id":453,"time":60,"title":457},[452],{"bookId":453,"id":454,"indexOrder":13,"name":455},15,"zj9uvg0sp3b0sok8","Docker 容器技术 笔记","这里包含其他中间件课程笔记","其他中间件笔记","我们对知识的探索从未停止，只有不断地学习，才能走向美好的未来！","其他笔记分类 🌽",200,true,{"data":463,"status":460,"success":461},{"bookId":66,"content":464,"id":67,"indexOrder":8,"introduction":465,"lastUpdate":466,"name":68},"# 走进JVM\n\nJVM相对于Java应用层的学习难度更大，**开篇推荐掌握的预备知识：** C\u002FC++(关键)、微机原理与接口技术、计算机组成原理、操作系统、数据结构与算法、编译原理（不推荐刚学完JavaSE的同学学习），如果没有掌握推荐的一半以上的预备知识，可能学习起来会比较吃力。\n\n**本套课程中需要用到的开发工具：** CLion、IDEA、Jetbrains Gateway\n\n此阶段，我们需要深入探讨Java的底层执行原理，了解Java程序运行的本质。开始之前，推荐各位都入手一本《深入理解Java虚拟机 第三版》这本书对于JVM的讲述非常地详细：\n\n![点击查看图片来源](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002Fb8TSFRoZayuUOlQ.jpg)\n\n我们在JavaSE阶段的开篇就进行介绍了，我们的Java程序之所以能够实现跨平台，本质就是因为它是运行在虚拟机之上的，而不同平台只需要安装对应平台的Java虚拟机即可运行（在JRE中包含），所有的Java程序都采用统一的标准，在任何平台编译出来的字节码文件(.class)也是同样的，最后实际上是将编译后的字节码交给JVM处理执行。\n\n![点击查看图片来源](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FSgBybA8xC3MrD6k.jpg)\n\n也正是得益于这种统一规范，除了Java以外，还有多种JVM语言，比如Kotlin、Groovy等，它们的语法虽然和Java不一样，但是最终编译得到的字节码文件，和Java是同样的规范，同样可以交给JVM处理。\n\n![点击查看图片来源](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FvNoWyDGsuwY23OE.jpg)\n\n所以，JVM是我们需要去关注的一个部分，通过了解Java的底层运作机制，我们的技术会得到质的提升。\n\n## 技术概述\n\n首先我们要了解虚拟机的具体定义，我们所接触过的虚拟机有安装操作系统的虚拟机，也有我们的Java虚拟机，而它们所面向的对象不同，Java虚拟机只是面向单一应用程序的虚拟机，但是它和我们接触的系统级虚拟机一样，我们也可以为其分配实际的硬件资源，比如最大内存大小等。\n\n并且Java虚拟机并没有采用传统的PC架构，比如现在的HotSpot虚拟机，实际上采用的是`基于栈的指令集架构`，而我们的传统程序设计一般都是`基于寄存器的指令集架构`，这里我们需要回顾一下`计算机组成原理`中的CPU结构：\n\n![image-20230306164318560](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FFuCI49TPSRpaitE.png)\n\n其中，**AX，BX，CX，DX 称作为数据寄存器：** \n\n* AX (Accumulator)：累加寄存器，也称之为累加器；\n* BX (Base)：基地址寄存器；\n* CX (Count)：计数器寄存器；\n* DX (Data)：数据寄存器；\n\n这些寄存器可以用来传送数据和暂存数据，并且它们还可以细分为一个8位的高位寄存器和一个8位的低位寄存器，除了这些通用功能，它们各自也有自己的一些专属职责，比如AX就是一个专用于累加的寄存器，用的也比较多。\n\n**SP 和 BP 又称作为指针寄存器：** \n\n* SP (Stack Pointer)：堆栈指针寄存器，与SS配合使用，用于访问栈顶；\n* BP (Base Pointer)：基指针寄存器，可用作SS的一个相对基址位置，用它可直接存取堆栈中的数据；\n\n**SI 和 DI 又称作为变址寄存器：** \n\n* SI (Source Index)：源变址寄存器；\n* DI (Destination Index)：目的变址寄存器；\n\n主要用于存放存储单元在段内的偏移量，用它们可实现多种存储器操作数的寻址方式，为以不同的地址形式访问存储单元提供方便。\n\n**控制寄存器：** \n\n* IP (Instruction Pointer)：指令指针寄存器；\n* FLAG：标志寄存器；\n\n**段寄存器：** \n\n* CS (Code Segment)：代码段寄存器；\n* DS (Data Segment)：数据段寄存器；\n* SS (Stack Segment)：堆栈段寄存器；\n* ES (Extra Segment)：附加段寄存器；\n\n这里我们分别比较一下在x86架构下C语言和arm架构下编译之后的汇编指令不同之处：\n\n```c\nint main() {     \u002F\u002F实现一个最简的a+b功能，并存入变量c\n    int a = 10;\n    int b = 20;\n    int c = a + b;\n    return c;\n}\n```\n\n```sh\ngcc -S main.c\n```\n\n```assembly\n\t.file\t\"main.c\"\n\t.text\n\t.globl\tmain\n\t.type\tmain, @function\nmain:\n.LFB0:\n\t.cfi_startproc  ;rbp寄存器是64位CPU下的基址寄存器，和8086CPU的16位bp一样\n\tpushq\t%rbp     ;该函数中需要用到rbp寄存器，所以需要先把他原来的值压栈保护起来\n\t.cfi_def_cfa_offset 16\n\t.cfi_offset 6, -16\n\tmovq\t%rsp, %rbp    ;rsp是64位下的栈指针寄存器，这里是将rsp的值丢给rbp，因为局部变量是存放在栈中的，之后会使用rbp来访问局部变量\n\t.cfi_def_cfa_register 6\n\tmovl\t$10, -12(%rbp)    ;将10存入rbp所指向位置-12的位置 ->  int a = 10;\n\tmovl\t$20, -8(%rbp)     ;将20存入rbp所指向位置-8的位置  -> int b = 20;\n\tmovl\t-12(%rbp), %edx   ;将变量a的值交给DX寄存器（32位下叫edx，因为是int，这里只使用了32位）\n\tmovl\t-8(%rbp), %eax    ;同上，变量b的值丢给AX寄存器\n\taddl\t%edx, %eax        ;将DX和AX寄存器中的值相加，并将结果存在AX中  ->  tmp = a + b\n\tmovl\t%eax, -4(%rbp)    ;将20存入rbp所指向位置-4的位置  -> int c = tmp;与上面合在一起就是int c = a + b;\n\tmovl\t-4(%rbp), %eax    ;根据约定，将函数返回值放在AX   -> return c;\n\tpopq\t%rbp     ;函数执行完毕，出栈\n\t.cfi_def_cfa 7, 8\n\tret      ;函数返回\n\t.cfi_endproc\n.LFE0:\n\t.size\tmain, .-main\n\t.ident\t\"GCC: (Ubuntu 7.5.0-6ubuntu2) 7.5.0\"\n\t.section\t.note.GNU-stack,\"\",@progbits\n```\n\n在arm架构下（Apple M1 Pro芯片）编译的结果为：\n\n```assembly\n    .section   __TEXT,__text,regular,pure_instructions\n   .build_version macos, 12, 0    sdk_version 12, 1\n   .globl _main                           ; -- Begin function main\n   .p2align   2\n_main:                                  ; @main\n   .cfi_startproc\n; %bb.0:\n   sub    sp, sp, #16                     ; =16\n   .cfi_def_cfa_offset 16\n   str    wzr, [sp, #12]\n   mov    w8, #10\n   str    w8, [sp, #8]\n   mov    w8, #20\n   str    w8, [sp, #4]\n   ldr    w8, [sp, #8]\n   ldr    w9, [sp, #4]\n   add    w8, w8, w9\n   str    w8, [sp]\n   ldr    w0, [sp]\n   add    sp, sp, #16                     ; =16\n   ret\n   .cfi_endproc\n                                        ; -- End function\n.subsections_via_symbols\n```\n\n我们发现，在不同的CPU架构下，实际上得到的汇编代码也不一样，并且在arm架构下并没有和x86架构一样的寄存器结构，因此只能使用不同的汇编指令操作来实现。所以这也是为什么C语言不支持跨平台的原因，我们只能将同样的代码在不同的平台上编译之后才能在对应的平台上运行我们的程序。而Java利用了JVM，它提供了很好的平台无关性（当然，JVM本身是不跨平台的），我们的Java程序编译之后，并不是可以由平台直接运行的程序，而是由JVM运行，同时，我们前面说了，JVM（如HotSpot虚拟机），实际上采用的是`基于栈的指令集架构`，它并没有依赖于寄存器，而是更多的利用操作栈来完成，这样不仅设计和实现起来更简单，并且也能够更加方便地实现跨平台，不太依赖于硬件的支持。\n\n这里我们对一个类进行反编译查看：\n\n```java\npublic class Main {\n    public int test(){    \u002F\u002F和上面的例子一样\n        int a = 10;\n        int b = 20;\n        int c = a + b;\n        return c;\n    }\n}\n```\n\n```sh\njavap -v target\u002Fclasses\u002Fcom\u002Ftest\u002FMain.class #使用javap命令对class文件进行反编译\n```\n\n得到如下结果：\n\n```\n...\npublic int test();\n    descriptor: ()I\n    flags: ACC_PUBLIC\n    Code:\n      stack=2, locals=4, args_size=1\n         0: bipush        10\n         2: istore_1\n         3: bipush        20\n         5: istore_2\n         6: iload_1\n         7: iload_2\n         8: iadd\n         9: istore_3\n        10: iload_3\n        11: ireturn\n      LineNumberTable:\n        line 5: 0\n        line 6: 3\n        line 7: 6\n        line 8: 10\n      LocalVariableTable:\n        Start  Length  Slot  Name   Signature\n            0      12     0  this   Lcom\u002Ftest\u002FMain;\n            3       9     1     a   I\n            6       6     2     b   I\n           10       2     3     c   I\n```\n\n我们可以看到，java文件编译之后，也会生成类似于C语言那样的汇编指令，但是这些命令都是交给JVM去执行的命令（实际上虚拟机提供了一个类似于物理机的运行环境，也有程序计数器之类的东西），最下方存放的是本地变量（局部变量）表，表示此方法中出现的本地变量，实际上this也在其中，所以我们才能在非静态方法中使用`this`关键字，在最上方标记了方法的返回值类型、访问权限等。首先介绍一下例子中出现的命令代表什么意思：\n\n* bipush   将单字节的常量值推到栈顶\n* istore_1   将栈顶的int类型数值存入到第二个本地变量\n* istore_2   将栈顶的int类型数值存入到第三个本地变量\n* istore_3   将栈顶的int类型数值存入到第四个本地变量\n* iload_1    将第二个本地变量推向栈顶\n* iload_2    将第三个本地变量推向栈顶\n* iload_3   将第四个本地变量推向栈顶\n* iadd   将栈顶的两个int类型变量相加，并将结果压入栈顶\n* ireturn   方法的返回操作\n\n有关详细的指令介绍列表可以参考《深入理解Java虚拟机 第三版》附录C。\n\nJVM运行字节码时，所有的操作基本都是围绕两种数据结构，一种是堆栈（本质是栈结构），还有一种是队列，如果JVM执行某条指令时，该指令需要对数据进行操作，那么被操作的数据在指令执行前，必须要压到堆栈上，JVM会自动将栈顶数据作为操作数。如果堆栈上的数据需要暂时保存起来时，那么它就会被存储到局部变量队列上。\n\n我们从第一条指令来依次向下解读，显示方法相关属性：\n\n    descriptor: ()I     \u002F\u002F参数以及返回值类型，()I就表示没有形式参数，返回值为基本类型int\n    flags: ACC_PUBLIC   \u002F\u002Fpublic访问权限\n    Code:\n      stack=2, locals=4, args_size=1    \u002F\u002Fstack表示要用到的最大栈深度，本地变量数，堆栈上最大对象数量（这里指的是this）\n\n有关descriptor的详细属性介绍，我们会放在之后的类结构中进行讲解。\n\n接着我们来看指令：\n\n```\n0: bipush        10     \u002F\u002F0是程序偏移地址，然后是指令，最后是操作数\n2: istore_1\n```\n\n这一步操作实际上就是使用`bipush`将10推向栈顶，接着使用`istore_1`将当前栈顶数据存放到第二个局部变量中，也就是a，所以这一步执行的是`int a = 10`操作。\n\n```\n3: bipush        20\n5: istore_2\n```\n\n同上，这里执行的是`int b = 20`操作。\n\n```\n6: iload_1\n7: iload_2\n8: iadd\n```\n\n这里是将第二和第三个局部变量放到栈中，也就是取a和b的值到栈中，最后`iadd`操作将栈中的两个值相加，结果依然放在栈顶。\n\n```\n9: istore_3\n10: iload_3\n11: ireturn\n```\n\n将栈顶数据存放到第四个局部变量中，也就是c，执行的是`int c = 30`，最后取出c的值放入栈顶，使用`ireturn`返回栈顶值，也就是方法的返回值。\n\n至此，方法执行完毕。\n\n实际上我们发现，JVM执行的命令基本都是入栈出栈等，而且大部分指令都是没有操作数的，传统的汇编指令有一操作数、二操作数甚至三操作数的指令，Java相比C编译出来的汇编指令，执行起来会更加复杂，实现某个功能的指令条数也会更多，所以Java的执行效率实际上是不如C\u002FC++的，虽然能够很方便地实现跨平台，但是性能上大打折扣，所以在性能要求比较苛刻的Android上，采用的是定制版的JVM，并且是基于寄存器的指令集架构。此外，在某些情况下，我们还可以使用JNI机制来通过Java调用C\u002FC++编写的程序以提升性能（也就是本地方法，使用到native关键字）\n\n***\n\n## 现在与未来\n\n随着时代的变迁，JVM的实现多种多样，而我们还要从最初的虚拟机说起。\n\n### 虚拟机的发展历程\n\n在1996，Java1.0面世时，第一款商用虚拟机Sun Classic VM开始了它的使命，这款虚拟机提供了一个Java解释器，也就是将我们的class文件进行读取，最后像上面一样得到一条一条的命令，JVM再将指令依次执行。虽然这样的运行方式非常的简单易懂，但是它的效率实际上是很低的，就像你耳机里一边在放六级听力，你必须同时记在脑海里面然后等着问问题，再去选择问题的答案一样，更重要的是同样的代码每次都需要重新翻译再执行。\n\n这个时候我们就需要更加高效的方式来运行Java程序，随着后面的发展，现在大多数的主流的JVM都包含即时**编译器**。JVM会根据当前代码的进行判断，当虚拟机发现某个方法或代码块的运行特别频繁时，就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率，在运行时，虚拟机将会把这些代码编译成与本地平台相关的机器码，并进行各种层次的优化，完成这个任务的编译器称为即时编译器（Just In Time Compiler）\n\n![img](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FJcfyIZm85AXECoh.jpg)\n\n在JDK1.4时，Sun Classic VM完全退出了历史舞台，取而代之的是至今都在使用的HotSpot VM，它是目前使用最广泛的虚拟机，拥有上面所说的热点代码探测技术、准确式内存管理（虚拟机可以知道内存中某个位置的数据具体是什么类型）等技术，而我们之后的章节都是基于HotSpot虚拟机进行讲解。\n\n### 虚拟机发展的未来\n\n2018年4月，Oracle Labs公开了最新的GraalVM，它是一种全新的虚拟机，它能够实现所有的语言统一运行在虚拟机中。\n\n![img](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FBKqTOH8RrjoAWMw.jpg)\n\nGraal VM被官方称为“Universal VM”和“Polyglot VM”，这是一个在HotSpot虚拟机基础上增强而成的跨语言全栈虚拟机，可以作为“任何语言”的运行平台使用，这里“任何语言”包括了Java、Scala、Groovy、Kotlin等基于Java虚拟机之上的语言，还包括了C、C++、Rust等基于LLVM的语言，同时支持其他像JavaScript、Ruby、Python和R语言等等。Graal VM可以无额外开销地混合使用这些编程语言，支持不同语言中混用对方的接口和对象，也能够支持这些语言使用已经编写好的本地库文件。\n\nGraal VM的基本工作原理是将这些语言的源代码（例如JavaScript）或源代码编译后的中间格式（例如LLVM字节码）通过解释器转换为能被Graal VM接受的中间表示（Intermediate Representation，IR），譬如设计一个解释器专门对LLVM输出的字节码进行转换来支持C和C++语言，这个过程称为“程序特化”（Specialized，也常称为Partial Evaluation）。Graal VM提供了Truffle工具集来快速构建面向一种新语言的解释器，并用它构建了一个称为Sulong的高性能LLVM字节码解释器。\n\n目前最新的SpringBoot已经提供了本地运行方案：https:\u002F\u002Fdocs.spring.io\u002Fspring-native\u002Fdocs\u002Fcurrent\u002Freference\u002Fhtmlsingle\u002F\n\n> Spring Native支持使用[GraalVM](https:\u002F\u002Fwww.graalvm.org\u002F)[原生镜像](https:\u002F\u002Fwww.graalvm.org\u002Freference-manual\u002Fnative-image\u002F)编译器将Spring应用程序编译为本机可执行文件。\n>\n> 与Java虚拟机相比，原生映像可以为许多类型的工作负载实现更简单、更加持续的托管。包括微服务、非常适合容器的功能工作负载和[Kubernetes](https:\u002F\u002Fkubernetes.io\u002F)\n>\n> 使用本机映像提供了关键优势，如即时启动、即时峰值性能和减少内存消耗。\n>\n> GraalVM原生项目预计随着时间的推移会改进一些缺点和权衡。构建本机映像是一个比常规应用程序慢的繁重过程。热身后的本机映像运行时优化较少。最后，它不如JVM成熟，行为各不相同。\n>\n> 常规JVM和此原生映像平台的主要区别是：\n>\n> - 从主入口点对应用程序进行静态分析，在构建时进行。\n> - 未使用的部件将在构建时删除。\n> - 反射、资源和动态代理需要配置。\n> - Classpath在构建时是固定的。\n> - 没有类惰性加载：可执行文件中运送的所有内容将在启动时加载到内存中。\n> - 一些代码将在构建时运行。\n> - Java应用程序的某些方面有一些不受完全支持[的限制](https:\u002F\u002Fwww.graalvm.org\u002Freference-manual\u002Fnative-image\u002FLimitations\u002F)。\n>\n> 该项目的目标是孵化对Spring Native的支持，Spring Native是Spring JVM的替代品，并提供旨在打包在轻量级容器中的原生部署选项。在实践中，目标是在这个新平台上支持您的Spring应用程序，几乎未经修改。\n\n优点：\n\n1. 立即启动，一般启动时间小于100ms\n2. 更低的内存消耗\n3. 独立部署，不再需要JVM\n4. 同样的峰值性能要比JVM消耗的内存小\n\n缺点：\n\n1. 构建时间长\n2. 只支持新的Springboot版本（2.4.4+）\n\n***\n\n## 手动编译JDK8\n\n学习JVM最关键的是研究底层C\u002FC++源码，我们首先需要搭建一个测试环境，方便我们之后对底层源码进行调试。但是编译这一步的坑特别多，请务必保证跟教程中的环境一致，尤其是编译环境，版本不能太高，因为JDK8属于比较早期的版本了，否则会遇到各种各样奇奇怪怪的问题。\n\n### 环境配置\n\n* 操作系统：Ubuntu 20.04 Server\n* 硬件配置：i7-4790 4C8T\u002F 16G内存 \u002F 128G硬盘 （不能用树莓派或是arm芯片Mac的虚拟机，配置越高越好，不然卡爆）\n* 调试工具：Jetbrains Gateway（服务器运行CLion Backend程序，界面在Mac上显示）\n* OpenJDK源码：https:\u002F\u002Fcodeload.github.com\u002Fopenjdk\u002Fjdk\u002Fzip\u002Frefs\u002Ftags\u002Fjdk8-b120\n* 编译环境：\n  * gcc-4.8\n  * g++-4.8\n  * make-3.81\n  * openjdk-8\n\n### 开始折腾\n\n首选需要在我们的测试服务器上安装Ubuntu 20.04 Server系统，并通过ssh登录到服务器：\n\n```sh\nWelcome to Ubuntu 20.04.3 LTS (GNU\u002FLinux 5.4.0-96-generic x86_64)\n\n * Documentation:  https:\u002F\u002Fhelp.ubuntu.com\n * Management:     https:\u002F\u002Flandscape.canonical.com\n * Support:        https:\u002F\u002Fubuntu.com\u002Fadvantage\n\n  System information as of Sat 29 Jan 2022 10:33:03 AM UTC\n\n  System load:  0.08               Processes:               156\n  Usage of \u002F:   5.5% of 108.05GB   Users logged in:         0\n  Memory usage: 5%                 IPv4 address for enp2s0: 192.168.10.66\n  Swap usage:   0%                 IPv4 address for enp2s0: 192.168.10.75\n  Temperature:  32.0 C\n\n\n37 updates can be applied immediately.\nTo see these additional updates run: apt list --upgradable\n\n\nLast login: Sat Jan 29 10:27:06 2022\nnagocoler@ubuntu-server:~$ \n```\n\n先安装一些基本的依赖：\n\n```sh\nsudo apt install build-essential libxrender-dev xorg-dev libasound2-dev libcups2-dev gawk zip libxtst-dev libxi-dev libxt-dev gobjc\n```\n\n接着我们先将JDK的编译环境配置好，首先是安装gcc和g++的4.8版本，但是最新的源没有这个版本了，我们先导入旧版软件源：\n\n```sh\nsudo vim \u002Fetc\u002Fapt\u002Fsources.list\n```\n\n在最下方添加旧版源地址并保存：\n\n```\ndeb http:\u002F\u002Farchive.ubuntu.com\u002Fubuntu xenial main\ndeb http:\u002F\u002Farchive.ubuntu.com\u002Fubuntu xenial universe\n```\n\n接着更新一下apt源信息，并安装gcc和g++：\n\n```sh\nsudo apt update\nsudo apt install gcc-4.8 g++-4.8\n```\n\n接着配置：\n\n```sh\nsudo update-alternatives --install \u002Fusr\u002Fbin\u002Fgcc gcc \u002Fusr\u002Fbin\u002Fgcc-4.8 100\nsudo update-alternatives --install \u002Fusr\u002Fbin\u002Fg++ g++ \u002Fusr\u002Fbin\u002Fg++-4.8 100\n```\n\n最后查看版本是否为4.8版本：\n\n```sh\nnagocoler@ubuntu-server:~$ gcc --version\ngcc (Ubuntu 4.8.5-4ubuntu2) 4.8.5\nCopyright (C) 2015 Free Software Foundation, Inc.\nThis is free software; see the source for copying conditions.  There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\nnagocoler@ubuntu-server:~$ g++ --version\ng++ (Ubuntu 4.8.5-4ubuntu2) 4.8.5\nCopyright (C) 2015 Free Software Foundation, Inc.\nThis is free software; see the source for copying conditions.  There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n```\n\n接着安装make 3.81版本，需要从官方下载：\n\n```sh\nwget https:\u002F\u002Fftp.gnu.org\u002Fgnu\u002Fmake\u002Fmake-3.81.tar.gz\n```\n\n下载好之后进行解压，并进入目录：\n\n```sh\ntar -zxvf make-3.81.tar.gz \ncd make-3.81\u002F\n```\n\n接着我们修改一下代码，打开`glob\u002Fglob.c`文件：\n\n```c\n...\n#ifdef  HAVE_CONFIG_H\n# include \u003Cconfig.h>\n#endif\n\n#define __alloca alloca   \u003C- 添加这一句\n\u002F* Enable GNU extensions \n...\n```\n\n接着进行配置并完成编译和安装：\n\n```sh\nbash configure\nsudo make install\n```\n\n安装完成后，将make已经变成3.81版本了：\n\n```sh\nnagocoler@ubuntu-server:~\u002Fmake-3.81$ make -verison\nGNU Make 3.81\nCopyright (C) 2006  Free Software Foundation, Inc.\nThis is free software; see the source for copying conditions.\nThere is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\nPARTICULAR PURPOSE.\n```\n\n由于JDK中某些代码是Java编写的，所以我们还需要安装一个启动JDK，启动JDK可以是当前版本或低一版本，比如我们要编译JDK8的源码，那么就可以使用JDK7、JDK8作为启动JDK，对源码中的一些java文件进行编译。这里我们选择安装OpenJDK8作为启动JDK：\n\n```sh\nsudo apt install openjdk-8-jdk\n```\n\n这样，我们的系统环境就准备完成了，接着我们需要下载OpenJDK8的源码（已经放在网盘了）解压：\n\n```sh\nunzip jdk-jdk8-b120.zip\n```\n\n接着我们需要安装JetBrains Gateway在我们的服务器上导入项目，这里我们使用CLion后端，等待下载远程后端，这样我们的Linux服务器上虽然没有图形化界面，但是依然可以使用IDEA、CLion等工具，只是服务器上只有后端程序，而界面由我们电脑上的前端程序提供（目前此功能还在Beta阶段，暂不支持arm架构的Linux服务器）整个过程根据服务器配置决定可能需要5-20分钟。\n\n完成之后，我们操作起来就很方便了，界面和IDEA其实差不多，我们打开终端，开始进行配置：\n\n```sh\nbash configure --with-debug-level=slowdebug --enable-debug-symbols ZIP_DEBUGINFO_FIELS=0\n```\n\n配置完成后，再次确认是否和教程中的配置信息一致：\n\n```\nConfiguration summary:\n* Debug level:    slowdebug\n* JDK variant:    normal\n* JVM variants:   server\n* OpenJDK target: OS: linux, CPU architecture: x86, address length: 64\n\nTools summary:\n* Boot JDK:       openjdk version \"1.8.0_312\" OpenJDK Runtime Environment (build 1.8.0_312-8u312-b07-0ubuntu1~20.04-b07) OpenJDK 64-Bit Server VM (build 25.312-b07, mixed mode)  (at \u002Fusr\u002Flib\u002Fjvm\u002Fjava-8-openjdk-amd64)\n* C Compiler:     gcc-4.8 (Ubuntu 4.8.5-4ubuntu2) version 4.8.5 (at \u002Fusr\u002Fbin\u002Fgcc-4.8)\n* C++ Compiler:   g++-4.8 (Ubuntu 4.8.5-4ubuntu2) version 4.8.5 (at \u002Fusr\u002Fbin\u002Fg++-4.8)\n\nBuild performance summary:\n* Cores to use:   3\n* Memory limit:   3824 MB\n* ccache status:  not installed (consider installing)\n\nWARNING: The result of this configuration has overridden an older\nconfiguration. You *should* run 'make clean' to make sure you get a\nproper build. Failure to do so might result in strange build problems.\n```\n\n接着我们需要修改几个文件，不然一会会编译失败，首先是`hotspot\u002Fmake\u002Flinux\u002FMakefile`文件：\n\n```\n原有的 SUPPORTED_OS_VERSION = 2.4% 2.5% 2.6% 3%\n修改为 SUPPORTED_OS_VERSION = 2.4% 2.5% 2.6% 3% 4% 5%\n```\n\n接着是`hotspot\u002Fmake\u002Flinux\u002Fmakefiles\u002Fgcc.make`文件：\n\n```\n原有的 WARNINGS_ARE_ERRORS = -Werror\n修改为 #WARNINGS_ARE_ERRORS = -Werror\n```\n\n接着是`nashorn\u002Fmake\u002FBuildNashorn.gmk`文件：\n\n```\n  $(CP) -R -p $(NASHORN_OUTPUTDIR)\u002Fnashorn_classes\u002F* $(@D)\u002F\n  $(FIXPATH) $(JAVA) \\\n原有的 -cp \"$(NASHORN_OUTPUTDIR)\u002Fnasgen_classes$(PATH_SEP)$(NASHORN_OUTPUTDIR)\u002Fnashorn_classes\" \\\n修改为  -Xbootclasspath\u002Fp:\"$(NASHORN_OUTPUTDIR)\u002Fnasgen_classes$(PATH_SEP)$(NASHORN_OUTPUTDIR)\u002Fnashorn_classes\" \\\n   jdk.nashorn.internal.tools.nasgen.Main $(@D) jdk.nashorn.internal.objects $(@D)\n```\n\nOK，修改完成，接着我们就可以开始编译了：\n\n```\nmake all\n```\n\n整个编译过程大概需要持续10-20分钟，请耐心等待。构建完成后提示：\n\n```\n----- Build times -------\nStart 2022-01-29 11:36:35\nEnd   2022-01-29 11:48:20\n00:00:30 corba\n00:00:25 demos\n00:02:39 docs\n00:03:05 hotspot\n00:00:27 images\n00:00:17 jaxp\n00:00:31 jaxws\n00:03:02 jdk\n00:00:38 langtools\n00:00:11 nashorn\n00:11:45 TOTAL\n-------------------------\nFinished building OpenJDK for target 'all'\n```\n\n只要按照我们的教程一步步走，别漏了，应该是直接可以完成的，当然难免可能有的同学出现了奇奇怪怪的问题，加油，慢慢折腾，总会成功的~\n\n接着我们就可以创建一个测试配置了，首先打开设置页面，找到`自定义构建目标`：\n\n![image-20230306164504510](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FTAcqg1Sx3KwOQZz.png)\n\n点击`应用`即可，接着打开运行配置，添加一个新的自定义配置：\n\n![image-20230306164521873](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FFbEYsV1zvIf9TWl.png)\n\n选择我们编译完成的java程序，然后测试-version查看版本信息，去掉下方的构建。\n\n接着直接运行即可：\n\n```\n\u002Fhome\u002Fnagocoler\u002Fjdk-jdk8-b120\u002Fbuild\u002Flinux-x86_64-normal-server-slowdebug\u002Fjdk\u002Fbin\u002Fjava -version\nopenjdk version \"1.8.0-internal-debug\"\nOpenJDK Runtime Environment (build 1.8.0-internal-debug-nagocoler_2022_01_29_11_36-b00)\nOpenJDK 64-Bit Server VM (build 25.0-b62-debug, mixed mode)\n\nProcess finished with exit code 0\n```\n\n我们可以将工作目录修改到其他地方，接着我们创建一个Java文件并完成编译，然后测试能否使用我们编译的JDK运行：\n\n![image-20230306164535518](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FYZcxklCK7hvnapV.png)\n\n在此目录下编写一个Java程序，然后编译：\n\n```java\npublic class Main{\n        public static void main(String[] args){\n                System.out.println(\"Hello World!\");\n        }       \n}       \n```\n\n```sh\nnagocoler@ubuntu-server:~$ cd JavaHelloWorld\u002F\nnagocoler@ubuntu-server:~\u002FJavaHelloWorld$ vim Main.java\nnagocoler@ubuntu-server:~\u002FJavaHelloWorld$ javac Main.java \nnagocoler@ubuntu-server:~\u002FJavaHelloWorld$ ls\nMain.class  Main.java\n```\n\n点击运行，成功得到结果：\n\n```\n\u002Fhome\u002Fnagocoler\u002Fjdk-jdk8-b120\u002Fbuild\u002Flinux-x86_64-normal-server-slowdebug\u002Fjdk\u002Fbin\u002Fjava Main\nHello World!\n\nProcess finished with exit code 0\n```\n\n我们还可以在CLion前端页面中进行断点调试，比如我们测试一个入口点JavaMain，在`jdk\u002Fsrc\u002Fshare\u002Fbin\u002Fjava.c`中的JavaMain方法：\n\n![image-20230306164549328](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FAcdjJWy8QnxlTa4.png)\n\n点击右上角调试按钮，可以成功进行调试：\n\n![image-20230306164602205](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FtZzqg2GD3LSbn9o.png)\n\n至此，在Ubuntu系统上手动编译OpenJDK8完成。\n\n***\n\n## JVM启动流程探究\n\n前面我们完成了JDK8的编译，也了解了如何进行断点调试，现在我们就可以来研究一下JVM的启动流程了，首先我们要明确，虚拟机的启动入口位于`jdk\u002Fsrc\u002Fshare\u002Fbin\u002Fjava.c`的`JLI_Launch`函数，整个流程分为如下几个步骤：\n\n1. 配置JVM装载环境\n2. 解析虚拟机参数\n3. 设置线程栈大小\n4. 执行JavaMain方法\n\n首先我们来看看`JLI_Launch`函数是如何定义的：\n\n```c\nint\nJLI_Launch(int argc, char ** argv,              \u002F* main argc, argc *\u002F\n        int jargc, const char** jargv,          \u002F* java args *\u002F\n        int appclassc, const char** appclassv,  \u002F* app classpath *\u002F\n        const char* fullversion,                \u002F* full version defined *\u002F\n        const char* dotversion,                 \u002F* dot version defined *\u002F\n        const char* pname,                      \u002F* program name *\u002F\n        const char* lname,                      \u002F* launcher name *\u002F\n        jboolean javaargs,                      \u002F* JAVA_ARGS *\u002F\n        jboolean cpwildcard,                    \u002F* classpath wildcard *\u002F\n        jboolean javaw,                         \u002F* windows-only javaw *\u002F\n        jint     ergo_class                     \u002F* ergnomics policy *\u002F\n);\n```\n\n可以看到在入口点的参数有很多个，其中包括当前的完整版本名称、简短版本名称、运行参数、程序名称、启动器名称等。\n\n首先会进行一些初始化操作以及Debug信息打印配置等：\n\n```c\nInitLauncher(javaw);\nDumpState();\nif (JLI_IsTraceLauncher()) {\n    int i;\n    printf(\"Command line args:\\n\");\n    for (i = 0; i \u003C argc ; i++) {\n        printf(\"argv[%d] = %s\\n\", i, argv[i]);\n    }\n    AddOption(\"-Dsun.java.launcher.diag=true\", NULL);\n}\n```\n\n接着就是选择一个合适的JRE版本：\n\n```c\n\u002F*\n * Make sure the specified version of the JRE is running.\n *\n * There are three things to note about the SelectVersion() routine:\n *  1) If the version running isn't correct, this routine doesn't\n *     return (either the correct version has been exec'd or an error\n *     was issued).\n *  2) Argc and Argv in this scope are *not* altered by this routine.\n *     It is the responsibility of subsequent code to ignore the\n *     arguments handled by this routine.\n *  3) As a side-effect, the variable \"main_class\" is guaranteed to\n *     be set (if it should ever be set).  This isn't exactly the\n *     poster child for structured programming, but it is a small\n *     price to pay for not processing a jar file operand twice.\n *     (Note: This side effect has been disabled.  See comment on\n *     bugid 5030265 below.)\n *\u002F\nSelectVersion(argc, argv, &main_class);\n```\n\n接着是创建JVM执行环境，例如需要确定数据模型，是32位还是64位，以及jvm本身的一些配置在jvm.cfg文件中读取和解析：\n\n```c\nCreateExecutionEnvironment(&argc, &argv,\n                               jrepath, sizeof(jrepath),\n                               jvmpath, sizeof(jvmpath),\n                               jvmcfg,  sizeof(jvmcfg));\n```\n\n此函数只在头文件中定义，具体的实现是根据不同平台而定的。接着会动态加载jvm.so这个共享库，并把jvm.so中的相关函数导出并且初始化，而启动JVM的函数也在其中：\n\n```c\nif (!LoadJavaVM(jvmpath, &ifn)) {\n    return(6);\n}\n```\n\n比如mac平台下的实现：\n\n```c\njboolean\nLoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)\n{\n    Dl_info dlinfo;\n    void *libjvm;\n\n    JLI_TraceLauncher(\"JVM path is %s\\n\", jvmpath);\n\n    libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);\n    if (libjvm == NULL) {\n        JLI_ReportErrorMessage(DLL_ERROR1, __LINE__);\n        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());\n        return JNI_FALSE;\n    }\n\n    ifn->CreateJavaVM = (CreateJavaVM_t)\n        dlsym(libjvm, \"JNI_CreateJavaVM\");\n    if (ifn->CreateJavaVM == NULL) {\n        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());\n        return JNI_FALSE;\n    }\n\n    ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)\n        dlsym(libjvm, \"JNI_GetDefaultJavaVMInitArgs\");\n    if (ifn->GetDefaultJavaVMInitArgs == NULL) {\n        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());\n        return JNI_FALSE;\n    }\n\n    ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)\n    dlsym(libjvm, \"JNI_GetCreatedJavaVMs\");\n    if (ifn->GetCreatedJavaVMs == NULL) {\n        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());\n        return JNI_FALSE;\n    }\n\n    return JNI_TRUE;\n}\n```\n\n最后就是对JVM进行初始化了：\n\n```c\nreturn JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);\n```\n\n这也是由平台决定的，比如Mac下的实现为：\n\n```c\nint\nJVMInit(InvocationFunctions* ifn, jlong threadStackSize,\n                 int argc, char **argv,\n                 int mode, char *what, int ret) {\n    if (sameThread) {\n        \u002F\u002F无需关心....\n    } else {\n      \t\u002F\u002F正常情况下走这个\n        return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);\n    }\n}\n```\n\n可以看到最后进入了一个`ContinueInNewThread`函数（在刚刚的`java.c`中实现），这个函数会创建一个新的线程来执行：\n\n```c\nint\nContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,\n                    int argc, char **argv,\n                    int mode, char *what, int ret)\n{\n\n    ...\n\n      rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);\n      \u002F* If the caller has deemed there is an error we\n       * simply return that, otherwise we return the value of\n       * the callee\n       *\u002F\n      return (ret != 0) ? ret : rslt;\n    }\n}\n```\n\n接着进入了一个名为`ContinueInNewThread0`的函数，可以看到它将`JavaMain`函数传入作为参数，而此函数定义的第一个参数类型是一个函数指针：\n\n```c\nint\nContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {\n    int rslt;\n    pthread_t tid;\n    pthread_attr_t attr;\n    pthread_attr_init(&attr);\n    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);\n\n    if (stack_size > 0) {\n      pthread_attr_setstacksize(&attr, stack_size);\n    }\n\n    if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {\n      void * tmp;\n      pthread_join(tid, &tmp);\n      rslt = (int)tmp;\n    } else {\n     \u002F*\n      * Continue execution in current thread if for some reason (e.g. out of\n      * memory\u002FLWP)  a new thread can't be created. This will likely fail\n      * later in continuation as JNI_CreateJavaVM needs to create quite a\n      * few new threads, anyway, just give it a try..\n      *\u002F\n      rslt = continuation(args);\n    }\n\n    pthread_attr_destroy(&attr);\n    return rslt;\n}\n```\n\n最后实际上是在新的线程中执行`JavaMain`函数，最后我们再来看看此函数里面做了什么事情：\n\n```c\n\u002F* Initialize the virtual machine *\u002F\nstart = CounterGet();\nif (!InitializeJVM(&vm, &env, &ifn)) {\n    JLI_ReportErrorMessage(JVM_ERROR1);\n    exit(1);\n}\n```\n\n第一步初始化虚拟机，如果报错直接退出。接着就是加载主类（至于具体如何加载一个类，我们会放在后面进行讲解），因为主类是我们Java程序的入口点：\n\n```c\n\u002F*\n * Get the application's main class.\n *\n * See bugid 5030265.  The Main-Class name has already been parsed\n * from the manifest, but not parsed properly for UTF-8 support.\n * Hence the code here ignores the value previously extracted and\n * uses the pre-existing code to reextract the value.  This is\n * possibly an end of release cycle expedient.  However, it has\n * also been discovered that passing some character sets through\n * the environment has \"strange\" behavior on some variants of\n * Windows.  Hence, maybe the manifest parsing code local to the\n * launcher should never be enhanced.\n *\n * Hence, future work should either:\n *     1)   Correct the local parsing code and verify that the\n *          Main-Class attribute gets properly passed through\n *          all environments,\n *     2)   Remove the vestages of maintaining main_class through\n *          the environment (and remove these comments).\n *\n * This method also correctly handles launching existing JavaFX\n * applications that may or may not have a Main-Class manifest entry.\n *\u002F\nmainClass = LoadMainClass(env, mode, what);\n```\n\n某些没有主方法的Java程序比如JavaFX应用，会获取ApplicationMainClass：\n\n```c\n\u002F*\n * In some cases when launching an application that needs a helper, e.g., a\n * JavaFX application with no main method, the mainClass will not be the\n * applications own main class but rather a helper class. To keep things\n * consistent in the UI we need to track and report the application main class.\n *\u002F\nappClass = GetApplicationClass(env);\n```\n\n初始化完成：\n\n```c\n\u002F*\n * PostJVMInit uses the class name as the application name for GUI purposes,\n * for example, on OSX this sets the application name in the menu bar for\n * both SWT and JavaFX. So we'll pass the actual application class here\n * instead of mainClass as that may be a launcher or helper class instead\n * of the application class.\n *\u002F\nPostJVMInit(env, appClass, vm);\n```\n\n接着就是获取主类中的主方法：\n\n```java\n\u002F*\n * The LoadMainClass not only loads the main class, it will also ensure\n * that the main method's signature is correct, therefore further checking\n * is not required. The main method is invoked here so that extraneous java\n * stacks are not in the application stack trace.\n *\u002F\nmainID = (*env)->GetStaticMethodID(env, mainClass, \"main\",\n                                   \"([Ljava\u002Flang\u002FString;)V\");\n```\n\n没错，在字节码中`void main(String[] args)`表示为`([Ljava\u002Flang\u002FString;)V`我们之后会详细介绍。接着就是调用主方法了：\n\n```c\n\u002F* Invoke main method. *\u002F\n(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);\n```\n\n调用后，我们的Java程序就开飞速运行起来，直到走到主方法的最后一行返回：\n\n```c\n\u002F*\n * The launcher's exit code (in the absence of calls to\n * System.exit) will be non-zero if main threw an exception.\n *\u002F\nret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;\nLEAVE();\n```\n\n至此，一个Java程序的运行流程结束，在最后LEAVE函数中会销毁JVM。我们可以进行断点调试来查看是否和我们推出的结论一致：\n\n![image-20230306164622940](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FDgkhOvWYfAiB1yq.png)\n\n还是以我们之前编写的测试类进行，首先来到调用之前，我们看到主方法执行之前，控制台没有输出任何内容，接着我们执行此函数，再来观察控制台的变化：\n\n![image-20230306164639620](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FX3F2Hjvplnm17UJ.png)\n\n可以看到，主方法执行完成之后，控制台也成功输出了Hello World！\n\n继续下一步，整个Java程序执行完成，得到退出状态码`0`：\n\n![image-20230306164706976](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FSoP1fVekqM4R8sd.png)\n\n成功验证，最后总结一下整个执行过程：\n\n![image-20230306164716949](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002Fc4IKjgrhtw3ak9p.png)\n\n***\n\n## JNI调用本地方法\n\nJava还有一个JNI机制，它的全称：Java Native Interface，即Java本地接口。它允许在Java虚拟机内运行的Java代码与其他编程语言（如C\u002FC++和汇编语言）编写的程序和库进行交互（在Android开发中用得比较多）比如我们现在想要让C语言程序帮助我们的Java程序实现a+b的运算，首先我们需要创建一个本地方法：\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(sum(1, 2));\n    }\n\n    \u002F\u002F本地方法使用native关键字标记，无需任何实现，交给C语言实现\n    public static native int sum(int a, int b);\n}\n```\n\n创建好后，接着点击构建按钮，会出现一个out文件夹，也就是生成的class文件在其中，接着我们直接生成对应的C头文件：\n\n```sh\njavah -classpath out\u002Fproduction\u002FSimpleHelloWorld -d .\u002Fjni com.test.Main\n```\n\n生成的头文件位于jni文件夹下：\n\n```c\n\u002F* DO NOT EDIT THIS FILE - it is machine generated *\u002F\n#include \u003Cjni.h>\n\u002F* Header for class com_test_Main *\u002F\n\n#ifndef _Included_com_test_Main\n#define _Included_com_test_Main\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\u002F*\n * Class:     com_test_Main\n * Method:    sum\n * Signature: (II)V\n *\u002F\nJNIEXPORT void JNICALL Java_com_test_Main_sum\n  (JNIEnv *, jclass, jint, jint);\n\n#ifdef __cplusplus\n}\n#endif\n#endif\n```\n\n接着我们在CLion中新建一个C++项目，并引入刚刚生成的头文件，并导入jni相关头文件（在JDK文件夹中）首先修改CMake文件：\n\n```cmake\ncmake_minimum_required(VERSION 3.21)\nproject(JNITest)\n\ninclude_directories(\u002FLibrary\u002FJava\u002FJavaVirtualMachines\u002Fzulu-8.jdk\u002FContents\u002FHome\u002Finclude)\ninclude_directories(\u002FLibrary\u002FJava\u002FJavaVirtualMachines\u002Fzulu-8.jdk\u002FContents\u002FHome\u002Finclude\u002Fdarwin)\nset(CMAKE_CXX_STANDARD 14)\n\nadd_executable(JNITest com_test_Main.cpp com_test_Main.h)\n```\n\n接着就可以编写实现了，首先认识一下引用类型对照表：\n\n![image-20230306164733817](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FQS9FxGhdsMRBCcm.png)\n\n所以我们这里直接返回a+b即可：\n\n```cpp\n#include \"com_test_Main.h\"\n\nJNIEXPORT jint JNICALL Java_com_test_Main_sum\n        (JNIEnv * env, jclass clazz, jint a, jint b){\n    return a + b;\n}\n```\n\n接着我们就可以将cpp编译为动态链接库，在MacOS下会生成`.dylib`文件，Windows下会生成`.dll`文件，我们这里就只以MacOS为例，命令有点长，因为还需要包含JDK目录下的头文件：\n\n```sh\ngcc com_test_Main.cpp -I \u002FLibrary\u002FJava\u002FJavaVirtualMachines\u002Fzulu-8.jdk\u002FContents\u002FHome\u002Finclude -I \u002FLibrary\u002FJava\u002FJavaVirtualMachines\u002Fzulu-8.jdk\u002FContents\u002FHome\u002Finclude\u002Fdarwin -fPIC -shared -o test.dylib -lstdc++\n```\n\n编译完成后，得到`test.dylib`文件，这就是动态链接库了。\n\n最后我们再将其放到桌面，然后在Java程序中加载：\n\n```java\npublic class Main {\n    static {\n        System.load(\"\u002FUsers\u002Fnagocoler\u002FDesktop\u002Ftest.dylib\");\n    }\n\n    public static void main(String[] args) {\n        System.out.println(sum(1, 2));\n    }\n\n    public static native int sum(int a, int b);\n}\n```\n\n运行，成功得到结果：\n\n![image-20230306164747347](https:\u002F\u002Fs2.loli.net\u002F2023\u002F03\u002F06\u002FAdKbxHjlGwDfUY2.png)\n\n通过了解JVM的一些基础知识，我们心目中大致有了一个JVM的模型，在下一章，我们将继续深入学习JVM的内存管理机制和垃圾收集器机制，以及一些实用工具。","我们在JavaSE阶段的开篇就进行介绍了，我们的Java程序之所以能够实现跨平台，本质就是因为它是运行在虚拟机之上的，而不同平台只需要安装对应平台的Java虚拟机即可运行（在JRE中包含），所有的Java程序都采用统一的标准，在任何平台编译出来的字节码文件(.class)也是同样的，最后实际上是将编译后的字节码交给JVM处理执行。","2025-07-04 23:38:12",{"data":468,"status":460,"success":461},[469,474],{"id":8,"image":470,"link":471,"name":472,"type":473},"\u002Fimage\u002Fadv\u002Frainyun-2025-06.webp","https:\u002F\u002Fwww.rainyun.com\u002Fitbaima_","雨云优惠购","cloud",{"id":66,"image":475,"link":476,"name":477,"type":478},"\u002Fimage\u002Fadv\u002Fsimcard-2025-11.webp","https:\u002F\u002Fmall.itbaima.cn","号卡优惠","simcard"]