<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>林明菁</title>
        <link>https://blog.lxuan.fun/</link>
        <description>一个思念的人</description>
        <lastBuildDate>Sun, 10 May 2026 02:53:12 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-CN</language>
        <copyright>All rights reserved 2026, 林明菁</copyright>
        <item>
            <title><![CDATA[JVM内存优化]]></title>
            <link>https://blog.lxuan.fun/article/jvmncyh</link>
            <guid>https://blog.lxuan.fun/article/jvmncyh</guid>
            <pubDate>Mon, 12 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[优化可以从四个层面考虑：首先是新生代 GC 和老年代 GC 的执行时间，老年代 GC 时间很久，尽可能的减少老年代回收次数，同时还要保证新生代回收的稳定。其次是整个堆内存的大小，内存太小就容易导致 OOM，或者频繁发生 GC。根据业务的实际情况，选择适合程序的垃圾收集器。Java 代码层面，控制生成对象的大小和数量。]]></description>
            <content:encoded><![CDATA[<div id="notion-article" class="mx-auto overflow-hidden "><main class="notion light-mode notion-page notion-block-319d28e7adc681bd829bda6f2cb74838"><div class="notion-viewport"></div><div class="notion-collection-page-properties"></div><h4 class="notion-h notion-h3 notion-h-indent-0 notion-block-319d28e7adc681e0908ed30ea040b984" data-id="319d28e7adc681e0908ed30ea040b984"><span><div id="319d28e7adc681e0908ed30ea040b984" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681e0908ed30ea040b984" title="栈溢出"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">栈溢出</span></span></span></h4><div class="notion-text notion-block-319d28e7adc681efa5f5f9299606889c"><span class="notion-default">栈溢出一般就是</span><span class="notion-default"><b>代码不当</b></span><span class="notion-default">导致的，在 JVM的内存结构 中有讲到过，每个方法在调用时都会在本线程的栈中创建一个栈帧，每一个栈帧都会占用栈的一块内存空间，所以栈帧越大或者越多，就会出现</span><span class="notion-default"><b>栈溢出（StackOverflowError）</b></span><span class="notion-default">。但这样说实在是有过浅显了，所以我们还是要问一句，为什么？</span></div><div class="notion-text notion-block-319d28e7adc68161aa94d2c5e80ee197"><span class="notion-default">首先我要说第一个前提——</span><span class="notion-default"><b>JVM 的每一个栈帧的大小在方法调用时就确定了，是固定的，不会动态变化。</b></span></div><div class="notion-text notion-block-319d28e7adc6817c9398e03a6c02fe35"><span class="notion-default">大家如果了解过 JVM 的内存结构就会知道，栈中放的都是大小已知的东西：局部变量表（全是基本变量）、操作数栈、方法出口（返回地址）、一些额外数据（栈帧指针等），因此栈帧的大小在</span><span class="notion-default"><b>方法调用前</b></span><span class="notion-default">就已经根据 </span><span class="notion-default"><code class="notion-inline-code">.class</code></span><span class="notion-default"> 文件（字节码）中的元数据确定了。每个方法都带着两个重要的属性：</span></div><ul class="notion-list notion-list-disc notion-block-319d28e7adc681dd89a0ee85c78d61cb"><li><span class="notion-default"><code class="notion-inline-code">max_locals</code></span><span class="notion-default"> —— 局部变量表大小</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc681a08634c0b05429a34b"><li><span class="notion-default"><code class="notion-inline-code">max_stack</code></span><span class="notion-default"> —— 操作数栈最大深度</span></li></ul><div class="notion-text notion-block-319d28e7adc68176af24c26c6bfc0cbe"><span class="notion-default">例如：</span></div><div class="notion-text notion-block-319d28e7adc681e78462f878e8a23603"><span class="notion-default">编译后会确定：</span></div><ul class="notion-list notion-list-disc notion-block-319d28e7adc68162afbec9c0284b3528"><li><span class="notion-default">max_locals = 3（a、b、c）</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc681c69389df59ea889e87"><li><span class="notion-default">max_stack = 2（执行 a + b 时需要两个操作数）</span></li></ul><div class="notion-text notion-block-319d28e7adc681a58fc2dc99fe69b380"><span class="notion-default">这两个值是编译器编译时算出来的，因此无需在运行时动态扩张。</span></div><div class="notion-text notion-block-319d28e7adc681a6b008c820c9f10786"><span class="notion-default">那么问题来了：既然栈帧的大小不会变，那么线程的栈同样也是栈，它的大小会变吗？</span></div><div class="notion-text notion-block-319d28e7adc681cc846cc1ffdaa7f5ea"><span class="notion-default">答案：不能，JVM 启动时指定每个线程栈大小（-Xss），每个线程创建时直接一次性申请这个大小的连续内存块，之后就不会动态扩张了。因此，当栈帧不断增多，很可能就会 &quot; 撑爆 &quot; 线程的栈，从而出现栈溢出，最简单的例子就是递归，每一个递归在 return 前它所占用内存空间都不会释放。</span></div><div class="notion-text notion-block-319d28e7adc681ec87c1cc695b928720"><span class="notion-default">虽然说线程的栈的大小是固定的，但栈帧大小已知，那为什么线程不直接按 &quot; 最大栈帧大小 × 最大调用深度 &quot; 顶格分配？</span></div><div class="notion-text notion-block-319d28e7adc681c7b0dfe1fb41d40084"><span class="notion-default">首先有一个最大的问题：</span><span class="notion-default"><b>无法预先知道方法调用路径</b></span><span class="notion-default">。JVM 不知道你在运行时会执行哪些方法、递归多深，甚至这个递归的深度可能会根据用户的输入而改变，这是运行时改变，还记得栈是线程创建时创建的吗，上一个因用户所带来的未知而出现的是 &quot; 堆 &quot;。</span></div><div class="notion-text notion-block-319d28e7adc681a9b54fe79a15a6a0b9"><span class="notion-default">其次，即使我们确定了最大的一个栈帧的大小、规定了其最大递归深度，那也太太太浪费资源了，哪怕 Java 是企业级开发，内存相对而言不太紧张，但也正因为企业级的项目一不留神可能就会出现井喷式的 API 调用，以防用户使用不便，每一个线程和每一个 G 的内存都是宝贵的资源，更要细心一点 </span><em><span class="notion-default">（说白了还是成本控制的问题）</span></em><span class="notion-default">。</span></div><h4 class="notion-h notion-h3 notion-h-indent-0 notion-block-319d28e7adc681bd9186f1add2369fb8" data-id="319d28e7adc681bd9186f1add2369fb8"><span><div id="319d28e7adc681bd9186f1add2369fb8" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681bd9186f1add2369fb8" title="堆的垃圾回收"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">堆的垃圾回收</span></span></span></h4><div class="notion-text notion-block-319d28e7adc681339cbbce650d6b0554"><span class="notion-default">栈溢出的原因大多都是代码写的差，跟程序员交互的比较多，少写点垃圾代码就能解决。</span></div><div class="notion-text notion-block-319d28e7adc6819e9284dd41b80209d1"><span class="notion-default">因此内存优化主要还是看堆，首先照例说一下堆里存了哪些东西：创建的实例对象、数组以及字符串常量池。</span></div><div class="notion-text notion-block-319d28e7adc681deb264db58283905da"><span class="notion-default">开始说内存优化之前先来讲一下垃圾回收相关的知识，我们现在常用的 JDK 版本主要是 8、11、17。8 默认的垃圾收集器是 Parallel 和 Parallel Old，而 11 和 17 默认的是 G1，它们都是分代收集器，也就是会分为一个新生代和一个老年代，新生代的回收叫做 Minor GC，而老年代的回收叫做 Major GC，Minor GC 回收的频率非常快，而 Major GC 频率比较低，相比前者慢 10 倍。</span></div><div class="notion-text notion-block-319d28e7adc681af9532f6c39c3c9256"><span class="notion-default">我们先讲一下 Java 8 的收集器，分别是新生代的 Paralled 和老年代使用的 Parallel Old，新生代用的是复制算法，老年代用的是标记整理法，这些算法的概念参考 [[GC]] 或者 ChatGPT，详细就不说了。</span></div><div class="notion-text notion-block-319d28e7adc68122ab43ea6669aa6ce6"><span class="notion-default">这两个收集器尤其是新生代的，它的特点就是高吞吐量，什么是高吞吐量？比如说我现在吞吐量是 99，就代表一个虚拟机总共运行了 100 分钟，其中垃圾回收用了 1 分钟，实际干活的时间是 99 分钟，这就是吞吐量 99 的意思。与此相对会有个比较大的缺点——响应时间比较长，用户可能会出现长时间的卡顿，因为为了保证高吞吐量，他会一下子打扫干净，比如在 Eden 区和 Survivor From 区的垃圾会被一下子全部回收，也就是一次打扫的时间会比较久，这样就会导致 STM（所有应用程序暂停）时间会非常的长，垃圾收集器会独占 CPU 的资源，卡顿的频率不会很高，但是卡顿的时间可能会有点久，对于面向客户体验的系统，这个垃圾回收器就不是一个很好的选择。</span></div><div class="notion-text notion-block-319d28e7adc68161acb5dfb61ed7533b"><span class="notion-default">而且在高并发的场景下，长时间 STW 可能会一下子堆积大量的请求，导致系统崩溃，也就是当我们在垃圾收集的时候，可能会发送大量的请求进来，因为垃圾收集器在工作的时候应用线程是不能执行任务的，所以请求都会卡在这个地方，等到垃圾回收结束，所有的请求会一下子涌入进来，就很容易导致系统崩溃。</span></div><div class="notion-text notion-block-319d28e7adc6817d8d91c182a88c16a4"><span class="notion-default">当然 Paralle 依旧是热门收集器之一，除了高并发场景之外，发生问题或者影响用户体验的概率还是比较低的。</span></div><div class="notion-text notion-block-319d28e7adc68161b87cd70a4ea5b3f7"><span class="notion-default">接着说一下 Java11 和 17 用的 G1，G1 整体上用的是标记整理算法，局部上用的是复制算法，他新生代和老年代的垃圾回收器都是用的 G1，那为什么还说它是分代收集器呢？因为它会把内存划分为 2048 个区域，即 region。每一个 region 可以是 Eden，也可以是 Survivor，也可以是 Old，所以它从概念上还是划分成新生代和老年代的。G1 整体上还是用的标记整理算法，但是它每一个 region，也就是每一个内存区域，用的是复制算法。</span></div><div class="notion-text notion-block-319d28e7adc6818293befdf7c97f78ce"><span class="notion-default">我们来简单说一下这个算法的流程：首先，G1 会根据每个 region 的回收价值，优先回收价值最高的 region（Eden），这也就是为什么叫 G1 的原因，one 代表 first。首先标记所有被选中的 region，比如选中了两个 Eden，它就会把这两个 region 中存活的对象先标记下来，将存货的对象复制到空闲的 region 中，这部操作体现了复制和整理。不同 region 中存活的对象集中复制到一个新的 region 中，这便是整理存活对象，最后就是把这两个旧 region 回收掉。</span></div><div class="notion-text notion-block-319d28e7adc681999405de9a14ae7a7e"><em><span class="notion-default">其实 G1 中的标记整理算法和复制算法与传统的还是有点区别的，思想上还是一致的，有兴趣的可以自己去了解一下。</span></em></div><div class="notion-text notion-block-319d28e7adc6814e9437de5bb5086afa"><span class="notion-default">G1 有两个显著的优势：第一个。因为 region 特别多，有 2048 个，如果每次要把所有的 region 都回收，这样造成 STW 时间就会很长，而回收价值最高的，这样就能保证垃圾收集的一个高效性；第二个。可以通过设置期望停顿时间，也就是 STW 时间是可以控制的，这样就能避免 Java8 中会出现的 Parallel 收集器响应时间过长的问题，用户也就能避免出现长时间卡顿的问题。</span></div><div class="notion-text notion-block-319d28e7adc681eeabc5d2bcb5252fd8"><span class="notion-default">最后关于垃圾回收器还有一个概念————可达性分析。可达性分析说法有很多，我觉得最好理解的就是一个对象要有与之关联的引用，我在 ThreadLocal 这篇文章中提到过强引用和弱引用，我这里简述一下：当我们在堆中创建一个普通的对象的时候，创建它的线程同时也会在栈帧中的局部变量表中存放该对象的引用值 </span><em><span class="notion-default">（这个引用值在 JVM 内部可能是一个句柄（Handle），通过句柄可以访问到堆内存中的对象信息。）</span></em><span class="notion-default">。当这个方法结束，或者说栈帧结束的时候，局部变量表被清理，与之对应的引用就会消失，此时堆中的这个对象就可以被回收了，它也就不会被 G1 标记了。</span></div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-319d28e7adc681b19ef8da295c8ac154" data-id="319d28e7adc681b19ef8da295c8ac154"><span><div id="319d28e7adc681b19ef8da295c8ac154" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681b19ef8da295c8ac154" title="JVM 调优"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">JVM 调优</span></span></span></h3><div class="notion-text notion-block-319d28e7adc681b0a460daf521350c54"><span class="notion-default">看了以上的内容，了解了大概，那么优化对你们来说就只是需要想。</span></div><div class="notion-text notion-block-319d28e7adc68133bcc4fd0544bba798"><span class="notion-default">优化可以从四个层面考虑：</span></div><ul class="notion-list notion-list-disc notion-block-319d28e7adc6816c808fff6ab26cecff"><li><span class="notion-default">首先是新生代 GC 和老年代 GC 的执行时间，老年代 GC 时间很久，尽可能的减少老年代回收次数，同时还要保证新生代回收的稳定。</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc681ffbd91efc84e28c92d"><li><span class="notion-default">其次是整个堆内存的大小，内存太小就容易导致 OOM，或者频繁发生 GC。</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc6817fb5aed77eecd1c414"><li><span class="notion-default">根据业务的实际情况，选择适合程序的垃圾收集器。</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc681e59deec484e9895015"><li><span class="notion-default">Java 代码层面，控制生成对象的大小和数量。</span></li></ul><div class="notion-text notion-block-319d28e7adc681b395a0f92aeaa03f01"><span class="notion-default">以下是具体的调优方案：</span></div><ol start="1" class="notion-list notion-list-numbered notion-block-319d28e7adc681b490b4e454ed94cc32" style="list-style-type:decimal"><li><span class="notion-default"><code class="notion-inline-code">-Xmn</code></span><span class="notion-default">：可以为应用程序分配一个合理的新生代空间，以最大限度避免新对象直接进去老年代。</span></li><ol class="notion-list notion-list-numbered notion-block-319d28e7adc681b490b4e454ed94cc32" style="list-style-type:lower-alpha"><div class="notion-text notion-block-319d28e7adc68178a5b8f61dec542244"><span class="notion-default"><code class="notion-inline-code">java -Xmn256m -jar application.jar</code></span></div></ol></ol><ol start="2" class="notion-list notion-list-numbered notion-block-319d28e7adc6812cbcf1c924b7e3e543" style="list-style-type:decimal"><li><span class="notion-default"><code class="notion-inline-code">-XX:+UseAdaptiveSizePolicy</code></span><span class="notion-default">：开启 UseAdaptiveSizePolicy 动态调整 Survivor 区域，可以让新生代区动态的挪用部分老年代的内存，减少新生代回收次数；也可以防止新生代太小频繁 Minor GC 或者提前进入老年代，新生代太大，也会增加 Minor GC 的时间。</span></li><ol class="notion-list notion-list-numbered notion-block-319d28e7adc6812cbcf1c924b7e3e543" style="list-style-type:lower-alpha"><div class="notion-text notion-block-319d28e7adc681feab7bcfcf52084589"><span class="notion-default"><code class="notion-inline-code">java -XX:+UseAdaptiveSizePolicy -jar application.jar</code></span></div></ol></ol><ol start="3" class="notion-list notion-list-numbered notion-block-319d28e7adc6818fa026dba79eedb0a2" style="list-style-type:decimal"><li><span class="notion-default"><code class="notion-inline-code">-XX:PretenureSizeThreshold</code></span><span class="notion-default">：设置大对象直接进入老年代的阈值（单位是字节），当对象超过这个阈值时，将直接在老年代中分配。</span></li><ol class="notion-list notion-list-numbered notion-block-319d28e7adc6818fa026dba79eedb0a2" style="list-style-type:lower-alpha"><div class="notion-text notion-block-319d28e7adc68105864ed8e1aec41f8c"><span class="notion-default"><code class="notion-inline-code">java -XX:PretenureSizeThreshold=1048576 -jar application.jar</code></span></div></ol></ol><ol start="4" class="notion-list notion-list-numbered notion-block-319d28e7adc681808bbbc3ae3701ec39" style="list-style-type:decimal"><li><span class="notion-default"><code class="notion-inline-code">-XX:MaxTenuringThreshold</code></span><span class="notion-default">：进入老年代的最大年龄值，每次存活下来年龄就会 +1，默认是 15，一些内存比较小的对象很多，可以增加最大年龄值，让这些小登对象尽可能一直留在新生代。</span></li><ol class="notion-list notion-list-numbered notion-block-319d28e7adc681808bbbc3ae3701ec39" style="list-style-type:lower-alpha"><div class="notion-text notion-block-319d28e7adc681c0804cc69a73fe1038"><span class="notion-default"><code class="notion-inline-code">java -XX:MaxTenuringThreshold=16 -jar application.jar</code></span></div></ol></ol><ol start="5" class="notion-list notion-list-numbered notion-block-319d28e7adc68199a578e63c30488f8a" style="list-style-type:decimal"><li><span class="notion-default"><code class="notion-inline-code">-Xmx -Xms -XX:minheaoFreeRatio -XX:MaxHeapFreeRatio</code></span><span class="notion-default">：设置堆的初始化大小、最大大小、堆最小空闲比例、堆最大空闲比例。</span></li></ol><ol start="6" class="notion-list notion-list-numbered notion-block-319d28e7adc681808c0bfbfddf8d8022" style="list-style-type:decimal"><li><span class="notion-default"><code class="notion-inline-code">-XX:+UseParallelGC</code></span><span class="notion-default">：根据项目不同情况，选择合适的垃圾收集器，比如响应速度优先的 G1。</span></li></ol><ol start="7" class="notion-list notion-list-numbered notion-block-319d28e7adc68109bdb4c6a1036c202f" style="list-style-type:decimal"><li><span class="notion-default">当我们知道哪些对象会在堆内存时，引用类型、数组、字符串，针对实际情况，减少这些对象的创建，或者减少这个对象占据内存的大小；当然还可以声明一些非强引用对象，这样也能避免 OOM。</span></li></ol></main></div>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[共情是人类最伟大的情感]]></title>
            <link>https://blog.lxuan.fun/article/gqsrlzwddqg</link>
            <guid>https://blog.lxuan.fun/article/gqsrlzwddqg</guid>
            <pubDate>Thu, 18 Jul 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<div id="notion-article" class="mx-auto overflow-hidden "><main class="notion light-mode notion-page notion-block-319d28e7adc6811fa2caee56f2916120"><div class="notion-viewport"></div><div class="notion-collection-page-properties"></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-319d28e7adc68157a4e3cded1502aa37" data-id="319d28e7adc68157a4e3cded1502aa37"><span><div id="319d28e7adc68157a4e3cded1502aa37" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc68157a4e3cded1502aa37" title="共情是人类最伟大的情感"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">共情是人类最伟大的情感</span></span></span></h2><div class="notion-text notion-block-319d28e7adc681728a78eba421804165"><span class="notion-default">高中时看完了《三体》三部曲，时至今日都仍然觉得很震撼，光怪陆离的科幻世界更是让我心潮澎湃，那时候最让我感兴趣的就是对三体人的描写，没有任何的外貌特征，仅仅四个字——“思想透明”，引来我无限遐想。</span></div><div class="notion-text notion-block-319d28e7adc681de84f0f273128246a1"><span class="notion-default">那时候我就在想：“思想透明”到底是什么样子的？又是什么感觉？没等我细细品味，翻书的手就打开了第三部。这本书出现了一个新的外星文明——歌者文明，在这个文明中上位者可以肆意翻阅下位者的记忆，就像翻阅着一本书籍。于是我又想了：记忆被翻阅是什么感觉？这样的文明又会是什么样子的？</span></div><div class="notion-text notion-block-319d28e7adc6811ab34de8798444645d"><span class="notion-default">假设有这样一个文明，这个文明中的所有个体都可以无视空间和时间共享记忆，那么这个群体是否已经可以被称之为一个个体呢？这个想法是我在看完《新世纪福音战士》这部动漫的时候诞生的，在这部动漫中，有一个被称之为“人类补完计划”的东西，当这个计划实现后所有人类都会归为一体，而同时每个人都将瞬间经历完所有人类的一生，人们互相理解、互相包容，人类再无隔阂。在记忆方面似乎有点类似三体文明和歌者文明，但不完全一样，至少这两个文明仍然是由群体构成的。</span></div><div class="notion-text notion-block-319d28e7adc6818d8cc9f74a1991e0c3"><span class="notion-default">但那之前呢，《新世纪福音战士》中有十几个使徒，也叫做天使。每个使徒都有一道“A.T立场”，这道可以防御人类几乎一切武器的绝对领域，将每个使徒分隔为一个个个体，而人类也是一种使徒，但这份力量被分摊到拥有繁衍能力的人类身上就变得十分薄弱，仅仅只能隔开人的记忆和思想，然后我们便与三体人不同了。这个立场保护着我们的思想不受侵犯，在阿谀奉承时、在面对责骂批评时也都可以做到笑脸相迎，因为没人知道我们在想什么，就像蛋壳里的小鸡一样。但我们也失去了和别人感同身受的能力，我们没办法在别人嚎啕大哭时和他经历一样的悲伤，也没办法在别人幸福美满时和他经历一样的开心。哪怕别人竭尽全力去描述自己的伤心欲绝，与自己度过了四十年的深爱他的父母离去了，我们仍然不能感同身受——“我不是你，我怎会知道？”，是我们之间永远跨不过去的天堑。或许这一刻我理解了人类补完计划，这确实是一件令人悲伤的事情，补完后我即是你、你即是我，再无分别。我会理解过去欺凌我的人想法，他有着什么样的家庭？经历了什么样的人生？又有着什么样的朋友？我理解了过去杀人无数的死刑犯，他为什么要杀人？纯粹的恶意？毫无理由？身不由己？还是另有隐情？一切都无所遁形。我也理解了那些曾经深爱过我的人，他们是那样爱我，对我的无私奉献，给予我的安全感和满足感，毫无保留的呈现出来是那样的幸福。</span></div><div class="notion-text notion-block-319d28e7adc68138af98fd8e45c8ba3b"><span class="notion-default">但一切都没有任何意义了，我们此刻有着无限大的包容、无限大的理解，但却失去了彼此，我不再是我，你也不再是你。或许成为那样的神后也不会在意这些细枝末节，但想想还是算了吧，还是让力场将我们分开吧，其实这样也不错，既不像三体文明那样思想透明失去内心，也不像歌者文明那样记忆被肆意翻阅毫无遮拦，我们都有着自己的小世界，也谨慎地和别人共享着小世界。即使做不到完美的包容和理解，我也仍然可以做到看着你开心而为你开心、看着你伤心而为你伤心，尝试着与你一同经历着世间的一切，并与你一起感受。感谢人类的最伟大的共情能力，让我在你面前不会陌生地如同远在五万亿光年外的一只蚂蚁一般。</span></div></main></div>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[“动画化万岁！”]]></title>
            <link>https://blog.lxuan.fun/article/dhhws</link>
            <guid>https://blog.lxuan.fun/article/dhhws</guid>
            <pubDate>Fri, 11 Oct 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<div id="notion-article" class="mx-auto overflow-hidden "><main class="notion light-mode notion-page notion-block-319d28e7adc68149bb0af238beafc2f8"><div class="notion-viewport"></div><div class="notion-collection-page-properties"></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-319d28e7adc681a9983fe639901d8319" data-id="319d28e7adc681a9983fe639901d8319"><span><div id="319d28e7adc681a9983fe639901d8319" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681a9983fe639901d8319" title="“动画化万岁！”"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">“动画化万岁！”</span></span></span></h2><div class="notion-text notion-block-319d28e7adc6816fa498de9cddbb4fd3"><span class="notion-default">2017 年播出的《如果有妹妹就好了》是一部轻松搞笑的作品，然而其中也不乏对轻小说、漫画作者内心挣扎的刻画。作品里，作者自己的小说和漫画被改编成动画的情节引发了观众深思。虽说原作被改编为动画是许多作者梦寐以求的时刻，但在现实中，这个过程并不像想象中那样美好。</span></div><div class="notion-text notion-block-319d28e7adc6813e98bac7584238504b"><span class="notion-default">动画化，曾被视为作品成功的象征。然而，对于某些原作者来说，这个象征可能伴随着无尽的失望。动画制作是一个复杂的过程，涉及到原作、动画制作团队、市场和观众等多个方面。就像《如果有妹妹就好了》里讨论到的一样，原作被改编成动画后，结果往往无法达到作者预期，甚至可能让作品的整体质量大打折扣。这种情况会导致所谓的 &quot; 动画党 &quot;（只看动画的观众）对原作产生误解或负面评价，而这些评价又反过来打击了原作的声誉。</span></div><div class="notion-text notion-block-319d28e7adc6812da535f53edcfe8b25"><span class="notion-default">对于很多轻小说和漫画作者而言，动画化的风险不仅仅在于质量上的失控，还有原作声誉的损害。原作本身可能是经过作者多次打磨的心血之作，但当它进入动画化流程时，由于制作时间紧迫、预算限制和团队经验等原因，改编作品往往无法传达原作中的深刻细节和情感，甚至可能扭曲原作的核心。这种 &quot; 劣质改编 &quot; 让原作者感到无力，因为他们既无法干涉动画制作，又不得不面对粉丝对作品的失望与批评。一方面，作为创作者，他希望自己的作品被更多的人看到，渴望获得更高的关注度和认可；但另一方面，动画化带来的负面反馈却让他陷入低谷。这种负面影响不仅会打击创作的热情，还可能直接影响到作者未来的创作方向。很多时候，作者即便意识到这只是一次失败的改编，也难以轻易摆脱这种困扰，继续创作同样的作品可能会勾起对失败的回忆，放弃这条路则意味着此前努力的心血白费。</span></div><div class="notion-text notion-block-319d28e7adc68184a026c7b37c2eace2"><span class="notion-default">尽管动画化失败的风险让很多作者望而却步，但这并不意味着他们会因此停止创作。正如作品中的男二一样，面对种种挑战，创作者依然选择坚持下去，因为创作本身是他们的热情所在。这种热情不是因为一次动画化的失败而轻易泯灭。事实上，许多作者在经历了改编失败后，反而会更加坚定地追求创作的独立性和品质。他们意识到，改编终究只是作品传播的一个方式，而创作的初衷是无法被改变的。</span></div><div class="notion-text notion-block-319d28e7adc681288643ca2b5788fbf1"><span class="notion-default">总之，动画化对于许多轻小说和漫画作者来说既是梦寐以求的机会，也是让人心生畏惧的挑战。成功的动画化可以让作品焕发新生，而失败的改编则可能给创作者带来深深的失望和无奈。然而，无论如何，真正的创作者始终会坚持自己的道路，继续用心创作，超越那些外界的干扰与打击。正如《如果有妹妹就好了》中那样，创作的热情和信念才是他们前行的动力。</span></div><hr class="notion-hr notion-block-319d28e7adc6810bb53dfd909e01cd9a"/><div class="notion-text notion-block-319d28e7adc6811b945eea20a038992d"><span class="notion-default">作为观众，当我们看到自己喜欢的作品宣布要动画化时，心中总会涌起难以抑制的兴奋。毕竟，原作不仅仅是文字和图画，而通过动画这种更加动态的媒介，故事与角色将获得全新的生命。这种期待不仅是因为对原作的深情厚爱，更是对动画化后的视听盛宴充满想象。于是，当喜爱的作品即将动画化时，我们感到双重的快乐：一份来自于原作本身的共鸣，另一份来自于即将呈现的新体验。</span></div><div class="notion-text notion-block-319d28e7adc681a6a39ad44fea436e29"><span class="notion-default">然而，这种兴奋常常伴随着一丝忐忑。毕竟，改编的质量无法预知。当我们怀着满心期待按下播放键，结果却看到画质崩坏、角色性格被曲解，或者剧情被粗暴地删减和修改时，内心的失落简直无法言喻。那种从高度期待到深深失望的转折，让人感到煎熬。一方面，作为原作党，我们清楚地知道作品原本的魅力；另一方面，眼前的改编却让人难以接受，甚至担心那些 &quot; 只看动画 &quot; 的观众会对原作产生误解。这种时刻，真是痛并难忍。</span></div><div class="notion-text notion-block-319d28e7adc68116bc89d26b94f87280"><span class="notion-default">尽管如此，改编作品也有很多时候能带来意外的惊喜。有些动画不仅忠实于原作，还在叙事、人物塑造和情感表达上更加丰富。例如，通过优秀的配乐、精致的分镜和动人的声优表演，角色仿佛从纸面上活了起来。某些原作中的情感细节，因动画的表现手法被放大，让观众获得了比阅读时更强的情感冲击。这样的改编，反而让我们对原作有了更深的认识，同时也让更多人加入了对这个世界的喜爱之中。</span></div><div class="notion-text notion-block-319d28e7adc6813ba385e26d7fb5da80"><span class="notion-default">有趣的是，原作党对作品的喜爱，有时并不仅仅基于其客观的质量，而是源自内心深处的某种共鸣。我们喜欢某部作品，不仅是因为它优秀，还因为它在某个特定的时刻打动了我们，让我们在角色的成长、情节的起伏中找到了自己的影子。这种情感共鸣，是为什么不同的作品吸引不同的人群。而这种喜爱是难以被量化的，它超越了制作的瑕疵，超越了理性的分析。这也是为什么，尽管改编有风险，动画化仍然能够激发观众无尽的期待。</span></div><div class="notion-text notion-block-319d28e7adc681f48eefd10c76daa176"><span class="notion-default">此外，当我们看到自己深爱的作品被更多人熟知时，那种骄傲和喜悦是无可比拟的。讨论、分析、分享，看到其他观众因这部作品而感到快乐，就像我们当初的那份感动一样，令人倍感满足。即使改编不够完美，它依然让更多人了解并爱上了这个故事。动画化作为一种更大众化的传播媒介，给了这些作品从小众走向主流的机会。这种传播本身，或许就是一种成功。</span></div><div class="notion-text notion-block-319d28e7adc6811fa858e972d4b3f1f8"><span class="notion-default">对我来说，动画化的过程虽然充满未知与挑战，但它依然是我永远期待的旅程。每一部作品的改编，都是一次与更多观众分享我所热爱的机会。无论动画质量如何，只要有机会让更多人接触到这些宝贵的作品，何尝不是一件好事呢？失败固然令人遗憾，但那双重的快乐，永远值得去追逐。</span></div><div class="notion-text notion-block-319d28e7adc6815ca9fcef26c224a0ab"><span class="notion-default">最后一句，仅代表我个人而言，我永远期待着作品的改编，并且 &quot; 动画化万岁！&quot;</span></div></main></div>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JWT]]></title>
            <link>https://blog.lxuan.fun/article/JWT</link>
            <guid>https://blog.lxuan.fun/article/JWT</guid>
            <pubDate>Mon, 20 Jan 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[JSON Web Token 简称即 JWT，也就是通过 JSON 形式的数据作为 Web 应用的令牌，用于在各方之间安全地将信息作为 JSON 对象传输。在数据传输过程中还可以完成数据加密，签名等相关处理。]]></description>
            <content:encoded><![CDATA[<div id="notion-article" class="mx-auto overflow-hidden "><main class="notion light-mode notion-page notion-block-319d28e7adc6816f9d7efb43d4706b15"><div class="notion-viewport"></div><div class="notion-collection-page-properties"></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-319d28e7adc6817abfe6d93aedd1dd49" data-id="319d28e7adc6817abfe6d93aedd1dd49"><span><div id="319d28e7adc6817abfe6d93aedd1dd49" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc6817abfe6d93aedd1dd49" title="JWT"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">JWT</span></span></span></h2><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-319d28e7adc681cbb52ade7d55ce8e83" data-id="319d28e7adc681cbb52ade7d55ce8e83"><span><div id="319d28e7adc681cbb52ade7d55ce8e83" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681cbb52ade7d55ce8e83" title="0 什么是 SSO"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">0 什么是 SSO</span></span></span></h3><div class="notion-text notion-block-319d28e7adc6817abcfdd4a2206dd1dd"><span class="notion-default">SSO（Single Sign On），中文翻译为</span><span class="notion-default"><b>单点登录</b></span><span class="notion-default">，它是目前流行的企业业务整合的解决方案之一，SSO 的目标是在多个应用系统中，用户只需要登录一次就可以访问所有相互信任的应用系统，使用 SSO 整合后，只需要登录一次就可以进入多个系统，而不需要重新登录，这不仅仅带来了更好的用户体验，更重要的是降低了安全的风险和管理的消耗。</span></div><ul class="notion-list notion-list-disc notion-block-319d28e7adc68197a642f70f769be6e8"><li><span class="notion-default">为什么要用 SSO？</span></li><ul class="notion-list notion-list-disc notion-block-319d28e7adc68197a642f70f769be6e8"><div class="notion-text notion-block-319d28e7adc681f78788c8f70ca1b953"><span class="notion-default">现在流行的 Web 系统不断变的复杂，从最早的单系统单模块发展到现在的多系统多模块的应用群，用户在访问这些子系统或者分系统的时候，如果有 N 多个系统都要分别登录和登出，用户可能会疯掉。用户希望的是在这些系统中统一的登录和登出，换句话说，不管登录哪个系统之后，其他子系统就无需再登录了。</span></div><div class="notion-text notion-block-319d28e7adc6813b99fee1ceb953c80b"><span class="notion-default">举个实际应用的例子，比如微软的各个子系统，你会发现在浏览器不删除 Cookie 的情况下，登录官网后，再访问 OneDrive 是无需再登录的，这种方式就是单点登录 SSO 的应用。</span></div><div class="notion-text notion-block-319d28e7adc681a1bd6cd9983f2feaf2"><span class="notion-default">要实现单点登录，方式有很多，原理也名不相同，在这里主要讲主流的方案：使用 JWT 实现单点登录。</span></div></ul></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc6816d9a92d0bed39258e1"><li><span class="notion-default">前端用什么保存？</span></li><ul class="notion-list notion-list-disc notion-block-319d28e7adc6816d9a92d0bed39258e1"><div class="notion-text notion-block-319d28e7adc68129aaffe017880850c9"><span class="notion-default">Cookie，Sessionstorage，Localstorage</span></div></ul></ul><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-319d28e7adc681eebef0d6f988604625" data-id="319d28e7adc681eebef0d6f988604625"><span><div id="319d28e7adc681eebef0d6f988604625" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681eebef0d6f988604625" title="1 什么是 JWT"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">1 什么是 JWT</span></span></span></h3><div class="notion-text notion-block-319d28e7adc681f28740f97b2989352a"><span class="notion-default">官网：</span><span class="notion-default"><a class="notion-link" href="https://jwt.io/introduction" target="_blank" rel="noopener noreferrer">https://jwt.io/introduction</a></span></div><div class="notion-text notion-block-319d28e7adc681e7b0a5ce8a9a96a05f"><span class="notion-default">JSON Web Token 令牌 （JWT） 是一种开放标准（RFC 7519） 它定义了一种紧凑且自包含的方式，用于在各方之间作为 JSON 对象安全地传输信息，此信息可以验证和信任，因为它是经过数字签名的。JWT 可以使用密钥（使用 HMAC 算法）或使用 RSA 或 ECDSA 的公钥/私钥对进行签名，</span></div><div class="notion-text notion-block-319d28e7adc68172b365f9b33eb021d4"><span class="notion-default">通俗的说：  JSON Web Token 简称即 JWT，也就是通过 JSON 形式的数据作为 Web 应用的令牌，用于在各方之间安全地将信息作为 JSON 对象传输。在数据传输过程中还可以完成数据加密，签名等相关处理。</span></div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-319d28e7adc68147897fc32be3dcc2c9" data-id="319d28e7adc68147897fc32be3dcc2c9"><span><div id="319d28e7adc68147897fc32be3dcc2c9" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc68147897fc32be3dcc2c9" title="2 JWT 工作流程"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">2 JWT 工作流程</span></span></span></h3><div class="notion-text notion-block-319d28e7adc6814ab477f82082fe6761"><span class="notion-default">Authorization（授权）：这是使用 JWT 的最常见场景。一旦用户登录，后续每个请求都将包含 JWT，允许用户访问该令牌允许的路由、服务和资源。</span></div><div class="notion-text notion-block-319d28e7adc681b089d6f6d9f995ee2b"><span class="notion-default">流程上是这样的：</span></div><ol start="1" class="notion-list notion-list-numbered notion-block-319d28e7adc68147bec9c1ad596bba70" style="list-style-type:decimal"><li><span class="notion-default">用户使用用户名密码来请求服务器</span></li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-319d28e7adc6814aa8cccce426ae203e" style="list-style-type:decimal"><li><span class="notion-default">服务器进行验证用户的信息</span></li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-319d28e7adc68175b217f16b7d351bf5" style="list-style-type:decimal"><li><span class="notion-default">服务器通过验证发送给用户一个 token</span></li></ol><ol start="4" class="notion-list notion-list-numbered notion-block-319d28e7adc6816d8af1fde8963ee317" style="list-style-type:decimal"><li><span class="notion-default">客户端存储 token，并在每次请求时携带上这个 token 值</span></li></ol><ol start="5" class="notion-list notion-list-numbered notion-block-319d28e7adc6815b850ff0bb22bb4682" style="list-style-type:decimal"><li><span class="notion-default">服务端验证 token 值，并返回数据</span></li></ol><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-319d28e7adc681719c85ea342c949080" data-id="319d28e7adc681719c85ea342c949080"><span><div id="319d28e7adc681719c85ea342c949080" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681719c85ea342c949080" title="3 JWT 长什么样"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">3 JWT 长什么样</span></span></span></h3><div class="notion-text notion-block-319d28e7adc681eabaf0d17f4e779dc8"><span class="notion-default">JWT 是由三段信息构成的，将这三段信息文本用 &quot;</span><span class="notion-default"><code class="notion-inline-code">.</code></span><span class="notion-default">&quot; 链接在一起就构成了 JWT 字符串。就像这样：</span></div><h4 class="notion-h notion-h3 notion-h-indent-2 notion-block-319d28e7adc681a9bca8f763a1bf2e84" data-id="319d28e7adc681a9bca8f763a1bf2e84"><span><div id="319d28e7adc681a9bca8f763a1bf2e84" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681a9bca8f763a1bf2e84" title="3.1 第一部分——头部 （header）"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">3.1 第一部分——头部 （header）</span></span></span></h4><div class="notion-text notion-block-319d28e7adc681eaaea0ce37839daec6"><span class="notion-default">JWT 的头部承载两部分信息：</span></div><ul class="notion-list notion-list-disc notion-block-319d28e7adc6812aa711d4d4a718fb5e"><li><span class="notion-default">声明类型，这里是 jwt</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc6819cbccecc127157be0d"><li><span class="notion-default">声明加密的算法，通常直接使用 HMAC SHA256</span></li></ul><div class="notion-text notion-block-319d28e7adc681e989baff84ac32ea0f"><span class="notion-default">完整的头部就像下面这样的 JSON:</span></div><div class="notion-text notion-block-319d28e7adc6819ab413d024d6fe0b23"><span class="notion-default">然后将头部进行 base64 加密，构成了第一部分：eyJOeXAioiJKV1QiLCJhbGcioiJIUzIINi]9</span></div><h4 class="notion-h notion-h3 notion-h-indent-2 notion-block-319d28e7adc6813caf07ecec2f74e517" data-id="319d28e7adc6813caf07ecec2f74e517"><span><div id="319d28e7adc6813caf07ecec2f74e517" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc6813caf07ecec2f74e517" title="3.2 第二部分——载荷 （payload，类似于飞机上承载的物品）"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">3.2 第二部分——载荷 （payload，类似于飞机上承载的物品）</span></span></span></h4><div class="notion-text notion-block-319d28e7adc681f29f36f46a1316c8fa"><span class="notion-default">载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品，这些有效信息包含三个部分。</span></div><ul class="notion-list notion-list-disc notion-block-319d28e7adc6817583cfc43432a48fdc"><li><span class="notion-default">标准注册的声明</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc68148a172e72df374939a"><li><span class="notion-default">公共的声明</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc68196b043e92abf35a8e6"><li><span class="notion-default">私有的声明</span></li></ul><div class="notion-text notion-block-319d28e7adc68137a3a4ee501b253faf"><span class="notion-default">标准中注册的声明（建议但不强制使用）：</span></div><ul class="notion-list notion-list-disc notion-block-319d28e7adc68163b048c158767ae89a"><li><span class="notion-default">iss: jwt 签发者</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc681349b05d8632b2afd4d"><li><span class="notion-default">sub: jwt 所面向的用户</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc68169a6deddc4d23c4351"><li><span class="notion-default">aud: 接收 jwt 的一方</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc681988761fddec86b601e"><li><span class="notion-default">exp: jwt 的过期时间，这个过期时间必须要大于签发时间</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc681d780b0ec17dc877478"><li><span class="notion-default">nbf: 定义在什么时间之前，该 jwt 都是不可用的</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc681958344fec006aa5e29"><li><span class="notion-default">iat: jwt 的签发时间</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc681f8a059ed34a97cefc3"><li><span class="notion-default">jti: jwt 的唯一身份标识，主要用来作为一次性 token，从而回避重放攻击</span></li></ul><div class="notion-text notion-block-319d28e7adc6818e9cd3f9b7f87c1ccf"><span class="notion-default">公共的声明：</span></div><div class="notion-text notion-block-319d28e7adc681eba834da0542afa320"><span class="notion-default">公共的声明可以添加任何的信息，一般添加用户的相关信息或淇他业务需要的必要信息但不建议添加敏感信息，因为该部分在客户端可解密。</span></div><div class="notion-text notion-block-319d28e7adc68129a3b3c89875961e10"><span class="notion-default">私有的声明：</span></div><div class="notion-text notion-block-319d28e7adc681f08f70e7857c8f4a58"><span class="notion-default">私有声明是提供者和消费者所共同定义的声明，一般不建议存放敏感信息，因为 bas64 是对称解密的，意味着该部分信息可以归类为明文信息。</span></div><div class="notion-text notion-block-319d28e7adc68166bf39ec6e1d55acee"><span class="notion-default">定义一个 patload：用户信息</span></div><div class="notion-text notion-block-319d28e7adc6814f9c99f93426575fb8"><span class="notion-default">然后将其进行 base64 加密，得到 jwt 的第二部分：ey]zdWIioiIxM jMONTY30DkwIiwibmFtZSI6IkpvaG4gRG91IiwiYWRtaw4ionRydwv9</span></div><h4 class="notion-h notion-h3 notion-h-indent-2 notion-block-319d28e7adc681ecaedbcc89ffad6356" data-id="319d28e7adc681ecaedbcc89ffad6356"><span><div id="319d28e7adc681ecaedbcc89ffad6356" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681ecaedbcc89ffad6356" title="3.3 第三部分——签证 （signature）,"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">3.3 第三部分——签证 （signature）,</span></span></span></h4><div class="notion-text notion-block-319d28e7adc68124af15ce7b6ca5d652"><span class="notion-default">jwt 的第三部分是一个签证信息，这个签证信息由三部分组成：</span></div><ul class="notion-list notion-list-disc notion-block-319d28e7adc681b9adecc3f256a2fd67"><li><span class="notion-default">header（base64 后的）</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc6810d960ac25bbef11599"><li><span class="notion-default">payload（base64 后的）</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc68175b50fe68cf206f900"><li><span class="notion-default">secret（盐）</span></li></ul><div class="notion-text notion-block-319d28e7adc681fcb62bec10ac68a0d4"><span class="notion-default">这个部分需要 base64 加密后的 header 和 base64 加密后的 payload 使用 </span><span class="notion-default"><code class="notion-inline-code">.</code></span><span class="notion-default"> 连接组成的字符串，然后通过 headere 中声明的加密方式进行加盐 secret 组合加密，然后
就构成了 jwt 的第三部分。</span></div><div class="notion-text notion-block-319d28e7adc68195ab89fc2eadd678c7"><span class="notion-default">注意：secret 是保程在服务器端的，jwt 的签发生成也是在服务器端的，secret 就是用来进行 jwt 的签发和 jwt 的验证，所以，它就是你服务端的私钥，在任何场景都不应该流露出去。一旦客户端得知这个 secret，那就意味着客户端是可以自我签发 jwt 了。</span></div><hr class="notion-hr notion-block-319d28e7adc68136a2deda0db80d8a29"/><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-319d28e7adc681acabf6cb351c299154" data-id="319d28e7adc681acabf6cb351c299154"><span><div id="319d28e7adc681acabf6cb351c299154" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681acabf6cb351c299154" title="补充：为什么JWT更适合分布式系统？"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">补充：为什么JWT更适合分布式系统？</span></span></span></h3><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-319d28e7adc681b987d8f9d1924c29b6" data-id="319d28e7adc681b987d8f9d1924c29b6"><span><div id="319d28e7adc681b987d8f9d1924c29b6" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681b987d8f9d1924c29b6" title="补充：Hutool工具包生成JWT"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">补充：</span><span class="notion-default"><a class="notion-link" href="https://doc.hutool.cn/pages/jwt/#jwt生成" target="_blank" rel="noopener noreferrer">Hutool工具包生成JWT</a></span></span></span></h3></main></div>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JVM的内存结构]]></title>
            <link>https://blog.lxuan.fun/article/jvmdncjg</link>
            <guid>https://blog.lxuan.fun/article/jvmdncjg</guid>
            <pubDate>Tue, 11 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[JVM 的内存结构分为程序计数器、虚拟机栈、本地方法栈、元空间（方法区）、堆。  前三个是线程隔离的，后两个是线程共享的。]]></description>
            <content:encoded><![CDATA[<div id="notion-article" class="mx-auto overflow-hidden "><main class="notion light-mode notion-page notion-block-319d28e7adc6815e9087f82ee9de95f8"><div class="notion-viewport"></div><div class="notion-collection-page-properties"></div><h2 class="notion-h notion-h1 notion-h-indent-0 notion-block-319d28e7adc6817d9026d42345519ccc" data-id="319d28e7adc6817d9026d42345519ccc"><span><div id="319d28e7adc6817d9026d42345519ccc" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc6817d9026d42345519ccc" title="JVM的内存结构"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">JVM的内存结构</span></span></span></h2><div class="notion-text notion-block-319d28e7adc681d4b2b4fb0423ad5c56"><span class="notion-default">JVM 的内存结构分为 </span><span class="notion-default"><b>程序计数器</b></span><span class="notion-default">、</span><span class="notion-default"><b>虚拟机栈</b></span><span class="notion-default">、</span><span class="notion-default"><b>本地方法栈</b></span><span class="notion-default">、</span><span class="notion-default"><b>元空间（方法区）</b></span><span class="notion-default">、</span><span class="notion-default"><b>堆</b></span><span class="notion-default">。</span></div><div class="notion-text notion-block-319d28e7adc681df91addb433ea66a2f"><span class="notion-default">前三个是 </span><span class="notion-default"><b>线程隔离</b></span><span class="notion-default"> 的，后两个是 </span><span class="notion-default"><b>线程共享</b></span><span class="notion-default"> 的。</span></div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-319d28e7adc68118a7cace9cb8bfdc1d" data-id="319d28e7adc68118a7cace9cb8bfdc1d"><span><div id="319d28e7adc68118a7cace9cb8bfdc1d" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc68118a7cace9cb8bfdc1d" title="0 字节码"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">0 字节码</span></span></span></h3><div class="notion-text notion-block-319d28e7adc6815295d9db66f247e5a7"><span class="notion-default">在介绍这五个区域之前，先得明白「字节码」是什么。</span></div><div class="notion-text notion-block-319d28e7adc681618c57d34d904ba051"><span class="notion-default">当 Java 项目被编译或启动时，</span><span class="notion-default"><code class="notion-inline-code">.java</code></span><span class="notion-default"> 文件会被编译成 </span><span class="notion-default"><code class="notion-inline-code">.class</code></span><span class="notion-default"> 文件，也就是 </span><span class="notion-default"><b>二进制格式的字节码文件</b></span><span class="notion-default">，其中包含一条条字节码指令，例如：</span></div><div class="notion-text notion-block-319d28e7adc6813ab7b1e97aef8ce2eb"><span class="notion-default">对应的字节码指令如下：</span></div><div class="notion-text notion-block-319d28e7adc681a18b7ecab0db1bdd87"><span class="notion-default">^2cd2b7</span></div><div class="notion-text notion-block-319d28e7adc681e0b228f3076d64312d"><span class="notion-default">这些字节码是一种 </span><span class="notion-default"><b>中间语言（中间指令集）</b></span><span class="notion-default">，是 JVM 的 &quot; 母语 &quot;，而非 CPU 的机器码 </span><em><span class="notion-default">（常有新手搞错这一点）</span></em><span class="notion-default">。</span></div><div class="notion-text notion-block-319d28e7adc68199a68ae722184d8d15"><span class="notion-default">JVM 在执行时会把字节码 </span><span class="notion-default"><b>逐条解释或编译成机器码</b></span><span class="notion-default">。</span></div><div class="notion-text notion-block-319d28e7adc681899a80ddba47ac4ea5"><span class="notion-default">逐条翻译？这样岂不是会慢很多？没错，这确实会带来性能损耗，但正是因为有了 JVM 的这层抽象，Java 才能实现那句著名的口号——</span><span class="notion-default"><b>&quot; 一次编写，到处运行&quot;</b></span><span class="notion-default">。</span></div><div class="notion-text notion-block-319d28e7adc68122beecf0e6085bd686"><span class="notion-default">当然不止如此，JVM 重要的是能统一管理内存（GC）、提供安全沙箱、线程调度、异常处理、运行时监控接口，甚至能在不改源码的情况下自动优化（比如替换 GC 或 JIT 策略）。它本身就是一个专门为程序而生的系统。</span></div><div class="notion-text notion-block-319d28e7adc6811e84b6cff120bfea55"><span class="notion-default">如果直接编译成机器码，这些机制都无法实现。</span></div><div class="notion-text notion-block-319d28e7adc681529505cf8f4cfe9491"><span class="notion-default">其他语言为什么不选择 JVM 呢：</span></div><ul class="notion-list notion-list-disc notion-block-319d28e7adc681acbb8fda69d108db1c"><li><span class="notion-default"><code class="notion-inline-code">C/C++</code></span><span class="notion-default"> 注重极致性能和底层控制；</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc6810fa646e50a75026ce9"><li><span class="notion-default"><code class="notion-inline-code">Python</code></span><span class="notion-default"> 追求简洁易用；</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc6812aae50d7e3b62a7579"><li><span class="notion-default"><code class="notion-inline-code">.NET</code></span><span class="notion-default"> 有自己的 CLR；</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc6813b85c1c298abf206a6"><li><span class="notion-default"><code class="notion-inline-code">Go</code></span><span class="notion-default">、</span><span class="notion-default"><code class="notion-inline-code">Rust</code></span><span class="notion-default"> 追求更快的启动和更小的部署体积。</span></li></ul><div class="notion-text notion-block-319d28e7adc6817ca610cafd569ffe59"><span class="notion-default">JVM 的生态系统则更适合构建</span><span class="notion-default"><b>大型复杂的企业系统、大数据处理和移动端应用</b></span><span class="notion-default">。</span></div><div class="notion-text notion-block-319d28e7adc6811bb40dd9a9389476fd"><span class="notion-default">值得一提的是，现代 JVM（如 HotSpot、GraalVM）已经非常智能。</span></div><div class="notion-text notion-block-319d28e7adc6815d90d8c3125acbbc3b"><span class="notion-default">当某段代码被频繁执行时（即 </span><span class="notion-default"><b>热点代码</b></span><span class="notion-default">），JVM 会触发 </span><span class="notion-default"><b>JIT 编译器（C1/C2）</b></span><span class="notion-default">，把这部分字节码编译成本地机器码，从而让 Java 实际运行速度非常接近甚至超过 C++。</span></div><div class="notion-text notion-block-319d28e7adc6813ba554ebdfa6acb874"><span class="notion-default">以上，简单介绍了字节码指令。</span></div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-319d28e7adc6810c9bd0f3b953f644f9" data-id="319d28e7adc6810c9bd0f3b953f644f9"><span><div id="319d28e7adc6810c9bd0f3b953f644f9" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc6810c9bd0f3b953f644f9" title="1 元空间（方法区）"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">1 元空间（方法区）</span></span></span></h3><div class="notion-text notion-block-319d28e7adc6811eba62d2b262724f65"><span class="notion-default">由 JVM 加载的 </span><span class="notion-default"><b>类信息</b></span><span class="notion-default">、</span><span class="notion-default"><b>字节码指令</b></span><span class="notion-default">、</span><span class="notion-default"><b>运行时常量池</b></span><span class="notion-default"> 等内容都存在于元空间中。</span><em><span class="notion-default">（元空间并不在 JVM 中，而是使用了本地内存）</span></em><span class="notion-default">。</span></div><div class="notion-text notion-block-319d28e7adc681b7b637c9be02d0847a"><span class="notion-default">既然在元空间中，那么元空间肯定是线程共享了。很好理解吧，毕竟代码可不能给每个线程复制一份，那空间全被代码浪费了，所以代码肯定是线程共享一份的，哪个线程需要什么功能就拿着输入在里面执行一遍，最后拿着结果走就行了。</span></div><h4 class="notion-h notion-h3 notion-h-indent-2 notion-block-319d28e7adc681e5afdbd84912afae58" data-id="319d28e7adc681e5afdbd84912afae58"><span><div id="319d28e7adc681e5afdbd84912afae58" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681e5afdbd84912afae58" title="1.1 类信息"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">1.1 类信息</span></span></span></h4><div class="notion-text notion-block-319d28e7adc6814d8eb2e044cbda994a"><span class="notion-default">字节码都懂了，但是这里的类信息是什么？有些教程中会把这两个分开，我这里也是便于解释，但其实它们都在 </span><span class="notion-default"><code class="notion-inline-code">.class</code></span><span class="notion-default"> 文件里。说白了就是字面意思，它包含了类的结构信息，如</span><span class="notion-default"><b>类名、父类、接口、字段、方法、修饰符</b></span><span class="notion-default">等。</span></div><div class="notion-text notion-block-319d28e7adc68172a22dec592a0ee8cd"><span class="notion-default">看上面那段 代码，大家有没有发现字节码指令其实就是一个表格，按照顺序排列的表格实际上就是个数组，因此也有人称字节码指令为 </span><span class="notion-default"><b>字节码数组</b></span><span class="notion-default">。那堆字节码，只是 &quot; 代码实现 &quot;，除此之外的类结构描述，就是类信息（元数据）。</span></div><div class="notion-text notion-block-319d28e7adc681049462d23c219c2e3f"><span class="notion-default">还是上面的代码，大家应该知道没写全，写全的话应该是下面这样。</span></div><div class="notion-text notion-block-319d28e7adc681bcadf3e5d1608e8fbd"><span class="notion-default">将这个文件真正转为 </span><span class="notion-default"><code class="notion-inline-code">.class</code></span><span class="notion-default"> 文件，因为 </span><span class="notion-default"><code class="notion-inline-code">.class</code></span><span class="notion-default"> 文件是二进制格式，所以真实样子是这样的。</span></div><div class="notion-text notion-block-319d28e7adc681cca406c2f0a8e63b24"><span class="notion-default">反编译一下大概如下所示。</span></div><div class="notion-text notion-block-319d28e7adc681f09012c8798a354f54"><span class="notion-default">这样大家就明白了吧，其实只有最后这一块才是 </span><span class="notion-default"><code class="notion-inline-code">System.out.println(&quot;Hi, I&#x27;m &quot; + name)</code></span><span class="notion-default"> 真实的样子，除此之外，其他部分就是类的结构，也叫做</span><span class="notion-default"><b>类信息</b></span><span class="notion-default">。</span></div><h4 class="notion-h notion-h3 notion-h-indent-2 notion-block-319d28e7adc681f38b30f81fa5f3d491" data-id="319d28e7adc681f38b30f81fa5f3d491"><span><div id="319d28e7adc681f38b30f81fa5f3d491" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681f38b30f81fa5f3d491" title="1.2 运行时常量池"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">1.2 运行时常量池</span></span></span></h4><div class="notion-text notion-block-319d28e7adc681f19abbd312377d7e1c"><span class="notion-default">看上面的字节码。虽然 </span><span class="notion-default"><code class="notion-inline-code">.class</code></span><span class="notion-default"> 文件（直接称元空间即可）中也有 </span><span class="notion-default"><code class="notion-inline-code">Constant pool</code></span><span class="notion-default"> ，但这个运行时常量池可不是大家常说的那个堆中的字符串常量池，运行时常量池中存放的是 </span><span class="notion-default"><b>字面量</b></span><span class="notion-default"> 和 </span><span class="notion-default"><b>符号引用</b></span><span class="notion-default">。</span></div><table class="notion-simple-table notion-block-319d28e7adc6819eaa06ff54afc6b83f"><tbody><tr class="notion-simple-table-row notion-simple-table-header-row notion-block-319d28e7adc68185af0fedac73575a35"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">类型</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">示例</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">含义</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc68156ae37ca34682f03ad"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">字面量</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">42</code></span><span class="notion-default">、</span><span class="notion-default"><code class="notion-inline-code">3.14</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">源码中的常量值</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc68131ba73dfede678f79f"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">类符号引用</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">java/lang/StringBuilder</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">类的全限定名</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc681508caac999253d2a39"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">字段符号引用</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">System.out</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">字段名及其描述符</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc681eca10cfb1b79e1e3c2"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">方法符号引用</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">println:(Ljava/lang/String;)V</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">方法名与方法签名</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc68198b140f18f31ed1fd1"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">字符串描述符 (Utf8)</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">&quot;name&quot;</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">标识符的名字（类名、字段名等）</span></div></td></tr></tbody></table><div class="notion-text notion-block-319d28e7adc68141989efeaf0c63eb3b"><span class="notion-default">为什么这些字面量放在元空间？因为这些常量永远不会变，它相当于属于这个代码的一部分，所以编译的时候直接嵌进去了 </span><em><span class="notion-default">（除了字符串，存在于运行时常量池中的字符串在程序启动时都加载到堆中的字符串常量池）</span></em><span class="notion-default">。举例如下。</span></div><table class="notion-simple-table notion-block-319d28e7adc681b793e3c23f9f16cd99"><tbody><tr class="notion-simple-table-row notion-simple-table-header-row notion-block-319d28e7adc68112b132d5c695573c43"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">类型</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">存放位置</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc6811cb390c417db46264f"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">final int COUNT = 10</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">元空间（常量内联）</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc681ac87a4c8ae20253853"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">final static int COUNT = 10</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">元空间（常量内联）</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc681e0b440f4b93f64b69d"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">System.out.println(&quot;Hi, I&#x27;m&quot;)</code></span><span class="notion-default"> 中的 </span><span class="notion-default"><code class="notion-inline-code">Hi, I&#x27;m</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">堆（字符串常量池）</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc68195b3afe1f27154438a"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">final static String NAME = &quot;Alice&quot;</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">堆（字符串常量池）</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc681ce89c1e97727aa23a3"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">static int age = 18</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">堆（类静态变量区）</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc6818581dace332a016c62"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">static String msg = &quot;Hi&quot;</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">堆（类静态变量区 + 字符串常量池）</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc681c89d25c992ff32a71a"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">int id = 1</code></span><span class="notion-default">（实例变量）</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">堆（对象实例中）</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc68172839dca0ba0b3fce8"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default"><code class="notion-inline-code">String name = &quot;Tom&quot;</code></span><span class="notion-default">（实例变量）</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">堆（对象实例中 + 字符串常量池）</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc681668a38cb02f5cdae8e"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">局部变量 </span><span class="notion-default"><code class="notion-inline-code">int x = 5</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">栈帧</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc68194a860c07a497af2b0"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">局部变量 </span><span class="notion-default"><code class="notion-inline-code">String s = &quot;Hi&quot;</code></span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">栈帧（引用）+ 堆（字符串）</span></div></td></tr></tbody></table><h4 class="notion-h notion-h3 notion-h-indent-2 notion-block-319d28e7adc6817ba99feb3fdf41bf22" data-id="319d28e7adc6817ba99feb3fdf41bf22"><span><div id="319d28e7adc6817ba99feb3fdf41bf22" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc6817ba99feb3fdf41bf22" title="1.2 静态变量（请看完再说）"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">1.2 静态变量（请看完再说）</span></span></span></h4><div class="notion-text notion-block-319d28e7adc68103b12cde44673aff62"><span class="notion-default">有些人搞不懂静态变量到底在堆里还是元空间里，这个很好理解，静态变量是类的一部分，因此对于静态变量来说，这个引用变量、字段描述是很重要的，这些描述信息确实放在元空间里，但而创建的实例对象则在堆中（无论对象还是基本类型），随着类的加载而加载 </span><em><span class="notion-default">（当然，堆会有一块区域专门属于类级别的存储区）</span></em><span class="notion-default">。</span></div><div class="notion-text notion-block-319d28e7adc681ea89b4f585addc8396"><span class="notion-default">记住一句话——元空间存放的是 &quot; 关于类本身 &quot; 的信息，不存放任何有关 &quot; 值 &quot; 这个概念的东西 </span><em><span class="notion-default">（基本类型的常量算不算值？我觉得应该直接算作代码的一部分了）</span></em><span class="notion-default">。</span></div><div class="notion-text notion-block-319d28e7adc681d1877cec95ce4fe2f7"><span class="notion-default">从 ChatGPT 那偷来一句话：</span></div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-319d28e7adc681d5a6efd314e574d52e" data-id="319d28e7adc681d5a6efd314e574d52e"><span><div id="319d28e7adc681d5a6efd314e574d52e" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681d5a6efd314e574d52e" title="2 堆"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">2 堆</span></span></span></h3><div class="notion-text notion-block-319d28e7adc6816eaf4ced08526be159"><span class="notion-default">介绍完元空间就顺带介绍一下堆，这玩意大家应该最不陌生了，它是真正存放 &quot; 值 &quot; 这个概念的地方。</span></div><div class="notion-text notion-block-319d28e7adc6811e9121c72a5c1663cf"><span class="notion-default">堆是 JVM 中</span><span class="notion-default"><b>最大的一块内存区域</b></span><span class="notion-default">，几乎所有对象都在这里分配。</span></div><div class="notion-text notion-block-319d28e7adc681b28e35f6663eaec9ae"><span class="notion-default">它存放以下内容：</span></div><ul class="notion-list notion-list-disc notion-block-319d28e7adc6811ab233c202668b2b85"><li><span class="notion-default">所有的</span><span class="notion-default"><b>实例对象</b></span><span class="notion-default">（包括数组、对象实例）；</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc6812e97c4dd143c5224b7"><li><span class="notion-default"><b>字符串常量池</b></span><span class="notion-default">（JDK 7 以后迁移到堆）；</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc6815ab003e3c2a45c5f3f"><li><span class="notion-default"><b>静态变量的值</b></span><span class="notion-default">（而非定义）；</span></li></ul><ul class="notion-list notion-list-disc notion-block-319d28e7adc681ae8ed2f10872bd4d98"><li><span class="notion-default"><b>运行时常量池中引用到的动态内容</b></span><span class="notion-default">。</span></li></ul><div class="notion-text notion-block-319d28e7adc681fdaeb1e33e9b1ce088"><span class="notion-default">堆是 </span><span class="notion-default"><b>线程共享</b></span><span class="notion-default"> 的，因此需要通过 GC（垃圾收集器）统一管理生命周期。</span></div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-319d28e7adc681c78c86d9aecb4d19fe" data-id="319d28e7adc681c78c86d9aecb4d19fe"><span><div id="319d28e7adc681c78c86d9aecb4d19fe" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681c78c86d9aecb4d19fe" title="3 程序计数器"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">3 程序计数器</span></span></span></h3><div class="notion-text notion-block-319d28e7adc681058170d25fa4307e8b"><span class="notion-default">程序计数器是 </span><span class="notion-default"><b>每个线程私有的寄存器</b></span><span class="notion-default">，用来记录</span><span class="notion-default"><b>当前执行的字节码指令的地址</b></span><span class="notion-default">。它指向 &quot; 下一条要执行的字节码 &quot;，以便在方法切换、异常跳转时恢复执行。由于每个线程都有自己的执行路径，所以计数器必须线程隔离。</span></div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-319d28e7adc68187a06fc9cde3dabc62" data-id="319d28e7adc68187a06fc9cde3dabc62"><span><div id="319d28e7adc68187a06fc9cde3dabc62" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc68187a06fc9cde3dabc62" title="4 本地方法栈"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">4 本地方法栈</span></span></span></h3><div class="notion-text notion-block-319d28e7adc681fab3b1eabf368c362c"><span class="notion-default">本地方法栈用于</span><span class="notion-default"><b>支持 JVM 执行 Native 方法（本地方法）</b></span><span class="notion-default">，也就是那些用 C/C++ 等语言实现、通过 </span><span class="notion-default"><code class="notion-inline-code">native</code></span><span class="notion-default"> 关键字声明的方法，比如 </span><span class="notion-default"><code class="notion-inline-code">Object.hashCode()</code></span><span class="notion-default">、</span><span class="notion-default"><code class="notion-inline-code">System.arraycopy()</code></span><span class="notion-default">。</span></div><div class="notion-text notion-block-319d28e7adc681cc8e39c7fa3a5fb593"><span class="notion-default">它和虚拟机栈类似，也会为每个线程分配独立的栈空间，用于存储</span><span class="notion-default"><b>本地方法调用时的局部变量、返回地址和操作数栈</b></span><span class="notion-default">等信息。</span></div><div class="notion-text notion-block-319d28e7adc681b29fc9d245614843e5"><span class="notion-default">区别在于，虚拟机栈服务于 Java 方法执行，而本地方法栈服务于</span><span class="notion-default"><b>本地（Native）方法执行</b></span><span class="notion-default">。</span></div><div class="notion-text notion-block-319d28e7adc681fdb7a6f12104d3622b"><span class="notion-default">当 Java 调用到本地方法时，JVM 会通过 </span><span class="notion-default"><b>JNI（Java Native Interface）</b></span><span class="notion-default"> 进入本地方法栈，由本地语言代码执行，执行完毕后再返回 JVM 世界。在这个过程中，程序计数器也会记录对应的本地方法调用位置，以便能够正确恢复。</span></div><h3 class="notion-h notion-h2 notion-h-indent-1 notion-block-319d28e7adc681abbc81ecbf56438601" data-id="319d28e7adc681abbc81ecbf56438601"><span><div id="319d28e7adc681abbc81ecbf56438601" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681abbc81ecbf56438601" title="5 虚拟机栈"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">5 虚拟机栈</span></span></span></h3><div class="notion-text notion-block-319d28e7adc6817eae9ae4fc704677b7"><span class="notion-default">虚拟机栈，众所周知，栈和堆可谓是异父异母的非双胞胎结拜兄弟。</span></div><div class="notion-text notion-block-319d28e7adc681f8a6e8f6274b98f0bd"><span class="notion-default">虚拟机栈用于描述 </span><span class="notion-default"><b>Java 方法的调用与执行过程</b></span><span class="notion-default">，每个线程都有自己的虚拟机栈。</span></div><div class="notion-text notion-block-319d28e7adc68114a336f3a94301506d"><span class="notion-default">栈中每个方法的调用对应一个 </span><span class="notion-default"><b>栈帧（Stack Frame）</b></span><span class="notion-default">。所有的字节码指令都只会对当前栈帧进行操作，如果这个方法内部调用了多个其他方法，就会出现多个其他栈帧。</span></div><div class="notion-text notion-block-319d28e7adc681c4aa60c10c843ccb88"><span class="notion-default">JVM 直接对栈的操作只有两个：</span><span class="notion-default"><b>入栈</b></span><span class="notion-default">和</span><span class="notion-default"><b>出栈</b></span><span class="notion-default">，遵循先进后出原理。如：首先执行 </span><span class="notion-default"><code class="notion-inline-code">Method01</code></span><span class="notion-default">，则 </span><span class="notion-default"><code class="notion-inline-code">Method01</code></span><span class="notion-default"> 会入栈进入虚拟机栈，而后调用 </span><span class="notion-default"><code class="notion-inline-code">Method02</code></span><span class="notion-default">，则 </span><span class="notion-default"><code class="notion-inline-code">Method02</code></span><span class="notion-default"> 入栈进入虚拟机栈，等到 </span><span class="notion-default"><code class="notion-inline-code">Method02</code></span><span class="notion-default"> 执行完 </span><span class="notion-default"><code class="notion-inline-code">return</code></span><span class="notion-default"> 后就会出栈，接着回到 </span><span class="notion-default"><code class="notion-inline-code">Method01</code></span><span class="notion-default"> 执行完 </span><span class="notion-default"><code class="notion-inline-code">return</code></span><span class="notion-default"> 后也出栈。</span></div><div class="notion-text notion-block-319d28e7adc681349daae81fbd2dfe2a"><span class="notion-default">了解了栈帧的概念，那就开始深入栈帧中的</span><span class="notion-default"><b>局部变量表</b></span><span class="notion-default">、</span><span class="notion-default"><b>操作数栈</b></span><span class="notion-default">、</span><span class="notion-default"><b>动态链接</b></span><span class="notion-default">、</span><span class="notion-default"><b>方法出口</b></span><span class="notion-default">、</span><span class="notion-default"><b>异常表</b></span><span class="notion-default">这五个部分。</span></div><h4 class="notion-h notion-h3 notion-h-indent-2 notion-block-319d28e7adc68165b616eeef7ca8c12d" data-id="319d28e7adc68165b616eeef7ca8c12d"><span><div id="319d28e7adc68165b616eeef7ca8c12d" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc68165b616eeef7ca8c12d" title="5.1 局部变量表"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">5.1 局部变量表</span></span></span></h4><div class="notion-text notion-block-319d28e7adc6818ab130d38a1db6188d"><span class="notion-default">局部变量表存放的就是</span><span class="notion-default"><b>方法上的参数</b></span><span class="notion-default">和</span><span class="notion-default"><b>方法内局部变量</b></span><span class="notion-default">的值，方法中的这些值只有在这个方法执行的时候才会被加载到局部变量表中，需要注意的是，只有基本数据类型的值会被加载到局部变量表中，而字符串或者引用类型则是将</span><span class="notion-default"><b>地址</b></span><span class="notion-default">加载到局部变量表中，通过地址在堆中就能找到值。</span></div><div class="notion-text notion-block-319d28e7adc6819ebd8be12ae881f53c"><span class="notion-default">这很容易想通，Java 中的基本类型（</span><span class="notion-default"><code class="notion-inline-code">int</code></span><span class="notion-default">、</span><span class="notion-default"><code class="notion-inline-code">double</code></span><span class="notion-default">、</span><span class="notion-default"><code class="notion-inline-code">boolean</code></span><span class="notion-default"> 等）在 JVM 中的大小是固定的，它们不包含复杂的内部结构，也没有引用关系，所以 JVM 知道这些变量在栈帧中应该分配多少空间，非常方便。并且每一次对其变量的值的修改也不会重新分配空间，而是直接</span><span class="notion-default"><b>覆盖这个槽里的值</b></span><span class="notion-default">，这也是为什么基本类型会出现数据溢出的问题，JVM 定死了空间大小，栈也只会给出这么大的空间，想要扩展？想要更多空间？那就去堆吧。</span></div><h4 class="notion-h notion-h3 notion-h-indent-2 notion-block-319d28e7adc6811a9dded74304e22bd1" data-id="319d28e7adc6811a9dded74304e22bd1"><span><div id="319d28e7adc6811a9dded74304e22bd1" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc6811a9dded74304e22bd1" title="5.2 操作数栈"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">5.2 操作数栈</span></span></span></h4><div class="notion-text notion-block-319d28e7adc681cba59acb461825a0f8"><span class="notion-default">操作数栈在方法执行过程中，根据字节码指令往操作数栈中写入数据或提取数据，如下所示。</span></div><div class="notion-text notion-block-319d28e7adc681c0b384eb1937d9b53b"><span class="notion-default">这是一个方法：</span></div><div class="notion-text notion-block-319d28e7adc68142a5c2ef69b769d591"><span class="notion-default">这是 Method02 方法的字节码指令：</span></div><div class="notion-text notion-block-319d28e7adc681309712ec0525b0ca36"><span class="notion-default">通过上面的例子我们可以看到，这些值会临时写入到操作数栈中，又把数据提取到局部变量表中，所以操作数栈的作用就是写入数据和提取数据。可以把操作数栈理解为草稿纸，程序执行时需要把这个值写道草稿纸上，计算后再将值写入到局部变量表中。</span></div><h4 class="notion-h notion-h3 notion-h-indent-2 notion-block-319d28e7adc68174b50fe3e77cb3cb07" data-id="319d28e7adc68174b50fe3e77cb3cb07"><span><div id="319d28e7adc68174b50fe3e77cb3cb07" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc68174b50fe3e77cb3cb07" title="5.3 动态链接"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">5.3 动态链接</span></span></span></h4><div class="notion-text notion-block-319d28e7adc681e1bd52e3712355d54e"><span class="notion-default">动态链接保存了一个 &quot; 符号引用 &quot; 的编号，到运行时常量池 &quot; 直接引用 &quot;（全限定名）的内存地址的映射关系。直接引用就是元空间中运行时常量池存储的类和方法的全限定名。当在 </span><span class="notion-default"><code class="notion-inline-code">Method01</code></span><span class="notion-default"> 中调用 </span><span class="notion-default"><code class="notion-inline-code">Method02</code></span><span class="notion-default"> 时就会创建这样一条字节码指令：</span></div><div class="notion-text notion-block-319d28e7adc681179c4be2b3870404da"><span class="notion-default">因为动态链接保存了符号引用和直接引用的内存地址的映射关系，所以通过符号引用 </span><span class="notion-default"><code class="notion-inline-code">#9</code></span><span class="notion-default"> 这个编号就能找到 </span><span class="notion-default"><code class="notion-inline-code">Method02</code></span><span class="notion-default"> 这个方法的字节码指令的地址，并修改程序计数器保存的字节码指令的地址。</span></div><div class="notion-text notion-block-319d28e7adc681659f01fee9cd6e97b6"><span class="notion-default">之前在说程序计数器的时候说过，它会记录下一条字节码指令的地址，所以原本要保存的 </span><span class="notion-default"><code class="notion-inline-code">Method01</code></span><span class="notion-default"> 的字节码指令的地址，就会变成 </span><span class="notion-default"><code class="notion-inline-code">Method02</code></span><span class="notion-default"> 开始的字节码指令地址。</span></div><div class="notion-text notion-block-319d28e7adc681cfb3effc7f5cca2d76"><span class="notion-default">那么问题来了，怎么返回上一个方法 </span><span class="notion-default"><code class="notion-inline-code">Method01</code></span><span class="notion-default"> 呢？往下看。</span></div><h4 class="notion-h notion-h3 notion-h-indent-2 notion-block-319d28e7adc68157800ed489c1af4242" data-id="319d28e7adc68157800ed489c1af4242"><span><div id="319d28e7adc68157800ed489c1af4242" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc68157800ed489c1af4242" title="5.4 方法出口"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">5.4 方法出口</span></span></span></h4><div class="notion-text notion-block-319d28e7adc681979c1efb6676b80865"><span class="notion-default">方法出口，存放调用该方法的指令的下一条指令的地址，这话听着有点抽象。</span></div><div class="notion-text notion-block-319d28e7adc68196bd5ccf049e146b47"><span class="notion-default">举个例子：</span><span class="notion-default"><code class="notion-inline-code">Method01</code></span><span class="notion-default"> 调用 </span><span class="notion-default"><code class="notion-inline-code">Method02</code></span><span class="notion-default"> ，</span><span class="notion-default"><code class="notion-inline-code">Method02</code></span><span class="notion-default"> 执行完最后一行代码后，应回到 </span><span class="notion-default"><code class="notion-inline-code">Method01</code></span><span class="notion-default"> 继续执行。在调用 </span><span class="notion-default"><code class="notion-inline-code">Method02</code></span><span class="notion-default"> 时，</span><span class="notion-default"><code class="notion-inline-code">Method02</code></span><span class="notion-default"> 的栈帧的方法出口就记录了返回 </span><span class="notion-default"><code class="notion-inline-code">Method01</code></span><span class="notion-default"> 后应继续执行的下一条字节码指令的地址，因此程序可以继续执行，或者可以说每个栈帧在结束后都会从方法出口出去。**（打个比方，字节码指令就如同一个个链表一样，程序计数器会记录下一个指令的地址，如果调用了其他方法就进入了新的链表，新的链表也走到头了，此时程序计数器就没有下一个指令的地址了，所以需要方法出口来作为新的链表的头节点）*</span></div><h4 class="notion-h notion-h3 notion-h-indent-2 notion-block-319d28e7adc681c6add8ebdd37873fa7" data-id="319d28e7adc681c6add8ebdd37873fa7"><span><div id="319d28e7adc681c6add8ebdd37873fa7" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681c6add8ebdd37873fa7" title="5.5 异常表"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">5.5 异常表</span></span></span></h4><div class="notion-text notion-block-319d28e7adc6819c8a19c9ad53add57b"><span class="notion-default">异常表——存放代码中异常的处理信息，有</span><span class="notion-default"><b>起始指令地址</b></span><span class="notion-default">、</span><span class="notion-default"><b>结束指令地址</b></span><span class="notion-default">、</span><span class="notion-default"><b>跳转指令地址</b></span><span class="notion-default">。</span></div><div class="notion-text notion-block-319d28e7adc68152b03fd5ebfec7af47"><span class="notion-default">比如此下面这段 </span><span class="notion-default"><code class="notion-inline-code">try……catch</code></span><span class="notion-default">。</span></div><div class="notion-text notion-block-319d28e7adc68189aeb3f636fac49080"><span class="notion-default">进入这个方法时，这个方法的栈帧中就会存在类似这样的一张异常表。</span></div><table class="notion-simple-table notion-block-319d28e7adc681d8bc92e444aa7dc6da"><tbody><tr class="notion-simple-table-row notion-simple-table-header-row notion-block-319d28e7adc681568e62d6f3b7daa37e"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">Nr.</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">起始 PC</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">结束 PC</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">跳转 PC</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">捕获类型</span></div></td></tr><tr class="notion-simple-table-row notion-block-319d28e7adc681888a26df7f1d8ee62e"><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">0</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">5</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">8</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">11</span></div></td><td class="" style="width:120px"><div class="notion-simple-table-cell"><span class="notion-default">cp_info #4 java/lang/Exception</span></div></td></tr></tbody></table><div class="notion-text notion-block-319d28e7adc6815ba4f4da3650d66319"><span class="notion-default">起始 PC，即起始字节码指令地址，指开始捕获异常的字节码指令地址，也就是 </span><span class="notion-default"><code class="notion-inline-code">try</code></span><span class="notion-default"> 下的第一个字节码指令地址，如：</span><span class="notion-default"><code class="notion-inline-code">5 bipush 100</code></span><span class="notion-default">；</span></div><div class="notion-text notion-block-319d28e7adc6819d8652d9f4ea04f5c7"><span class="notion-default">结束 PC，即结束字节码指令地址，如果没有发生异常，</span><span class="notion-default"><code class="notion-inline-code">try</code></span><span class="notion-default"> 与 </span><span class="notion-default"><code class="notion-inline-code">catch</code></span><span class="notion-default"> 中间的最后一条字节码指令是一个跳转指令，如：</span><span class="notion-default"><code class="notion-inline-code">8 goto 20 </code></span><span class="notion-default">，跳转到捕获异常结束的字节码指令地址，也就是 </span><span class="notion-default"><code class="notion-inline-code">catch</code></span><span class="notion-default"> 结束的下一条字节码指令地址，在这个例子中，20 即 return，直接退出了方法体；</span></div><div class="notion-text notion-block-319d28e7adc6818ab88ff2c8e2f6685d"><span class="notion-default">跳转 PC，即跳转字节码指令地址，如果 </span><span class="notion-default"><code class="notion-inline-code">try</code></span><span class="notion-default"> 中出现了异常，就会根据异常表跳到 </span><span class="notion-default"><code class="notion-inline-code">catch</code></span><span class="notion-default"> 开始的第一条字节码指令，如：</span><span class="notion-default"><code class="notion-inline-code">11 astore_2</code></span><span class="notion-default">，将 </span><span class="notion-default"><code class="notion-inline-code">Exception</code></span><span class="notion-default"> 类型的局部变量 </span><span class="notion-default"><code class="notion-inline-code">e</code></span><span class="notion-default"> 添加到局部变量表中的 </span><span class="notion-default"><code class="notion-inline-code">2</code></span><span class="notion-default"> 位置。</span></div></main></div>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ThreadLocal]]></title>
            <link>https://blog.lxuan.fun/article/ThreadLocal</link>
            <guid>https://blog.lxuan.fun/article/ThreadLocal</guid>
            <pubDate>Thu, 06 Feb 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[ThreadLocal 是 Java 中所提供的线程本地存储机制，可以利用该机制将数据缓存在某个线程内部，该线程可以在任意时刻、任意方法中获取缓存的数据。]]></description>
            <content:encoded><![CDATA[<div id="notion-article" class="mx-auto overflow-hidden "><main class="notion light-mode notion-page notion-block-319d28e7adc68155b6b4cd57849c9257"><div class="notion-viewport"></div><div class="notion-collection-page-properties"></div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-319d28e7adc681be96dce07e9c2e5180" data-id="319d28e7adc681be96dce07e9c2e5180"><span><div id="319d28e7adc681be96dce07e9c2e5180" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681be96dce07e9c2e5180" title="ThreadLocal 底层实现"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">ThreadLocal 底层实现</span></span></span></h3><div class="notion-text notion-block-319d28e7adc681079057fbc4c70fa341"><span class="notion-default"><code class="notion-inline-code">ThreadLocal</code></span><span class="notion-default"> 是 Java 中所提供的线程本地存储机制，可以利用该机制将数据 </span><span class="notion-default"><b>缓存在某个线程内部</b></span><span class="notion-default"> ，该线程可以在任意时刻、任意方法中获取缓存的数据。</span></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-319d28e7adc681a9a448d5d6967bc6f7"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column"><img src="https://r2.lxuan.fun/B_%E4%BB%A3%E7%A0%81%E7%82%BC%E9%87%91%E6%9C%AF/B3_%E5%B7%B2%E5%BD%92%E6%A1%A3/Java/10_%E7%BA%BF%E7%A8%8B%E7%9B%B8%E5%85%B3/assets/ThreadLocal/ThreadLocal_1.png?spaceId=b562607a-6753-49c5-81c0-b55bec749ebe&amp;t=319d28e7-adc6-81a9-a448-d5d6967bc6f7" alt="notion image" loading="lazy" decoding="async"/></div></figure><div class="notion-text notion-block-319d28e7adc681c9845ef57d32f81816"><span class="notion-default"><code class="notion-inline-code">ThreadLocal</code></span><span class="notion-default"> 底层是通过 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocalMap</code></span><span class="notion-default"> 来实现的，每个 </span><span class="notion-default"><code class="notion-inline-code">Thread</code></span><span class="notion-default"> 对象中都存在一个 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocalMap</code></span><span class="notion-default"> 变量，这个 </span><span class="notion-default"><code class="notion-inline-code">Map</code></span><span class="notion-default"> 的 </span><span class="notion-default"><code class="notion-inline-code">key</code></span><span class="notion-default"> 就是我们要操作的 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocal</code></span><span class="notion-default"> 对象，</span><span class="notion-default"><code class="notion-inline-code">Map</code></span><span class="notion-default"> 的 </span><span class="notion-default"><code class="notion-inline-code">value</code></span><span class="notion-default"> 为需要缓存的值。</span></div><div class="notion-text notion-block-319d28e7adc681e994a8f5d327ddf58f"><span class="notion-default">我们在使用 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocal</code></span><span class="notion-default"> 的时候其实就是拿到当前线程中的 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocalMap</code></span><span class="notion-default"> 中的 </span><span class="notion-default"><code class="notion-inline-code">key</code></span><span class="notion-default"> 和 </span><span class="notion-default"><code class="notion-inline-code">value</code></span><span class="notion-default">，</span><span class="notion-default"><code class="notion-inline-code">ThreadLocal</code></span><span class="notion-default"> 本身不过是个工具类罢了。</span></div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-319d28e7adc681f88777c18eb75017cb" data-id="319d28e7adc681f88777c18eb75017cb"><span><div id="319d28e7adc681f88777c18eb75017cb" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681f88777c18eb75017cb" title="注意事项"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">注意事项</span></span></span></h3><div class="notion-text notion-block-319d28e7adc68134b3f8e3799bf48ecf"><span class="notion-default">如果在线程池中使用 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocal</code></span><span class="notion-default"> 可能会造成内存泄漏，因为当 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocal</code></span><span class="notion-default"> 对象使用完之后，应该要把设置的 </span><span class="notion-default"><code class="notion-inline-code">key</code></span><span class="notion-default"> 和 </span><span class="notion-default"><code class="notion-inline-code">value</code></span><span class="notion-default">——也就是 </span><span class="notion-default"><code class="notion-inline-code">Entry</code></span><span class="notion-default"> 对象进行回收。但线程池中的线程不会回收，并且 </span><span class="notion-default"><code class="notion-inline-code">Thread</code></span><span class="notion-default"> 对象是通过强引用指向 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocalMap</code></span><span class="notion-default">，</span><span class="notion-default"><code class="notion-inline-code">ThreadLocalMap</code></span><span class="notion-default"> 也是通过强引用指向 </span><span class="notion-default"><code class="notion-inline-code">Entry</code></span><span class="notion-default"> 对象，线程不被回收，</span><span class="notion-default"><code class="notion-inline-code">Entry</code></span><span class="notion-default"> 对象也就不会被回收，从而导致不再使用的 </span><span class="notion-default"><code class="notion-inline-code">Entry</code></span><span class="notion-default"> 对象越来越多，出现内存泄漏。</span></div><div class="notion-text notion-block-319d28e7adc6814b967fc6b76005f729"><span class="notion-default">解决办法是，在使用了 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocal</code></span><span class="notion-default"> 对象之后，手动调用 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocal</code></span><span class="notion-default"> 的 </span><span class="notion-default"><code class="notion-inline-code">remove()</code></span><span class="notion-default"> 方法，手动清除 </span><span class="notion-default"><code class="notion-inline-code">Entry</code></span><span class="notion-default"> 对象。</span></div><figure class="notion-asset-wrapper notion-asset-wrapper-image notion-block-319d28e7adc68125b4d6fa071537ed5d"><div style="position:relative;display:flex;justify-content:center;align-self:center;width:100%;max-width:100%;flex-direction:column"><img src="https://r2.lxuan.fun/B_%E4%BB%A3%E7%A0%81%E7%82%BC%E9%87%91%E6%9C%AF/B3_%E5%B7%B2%E5%BD%92%E6%A1%A3/Java/10_%E7%BA%BF%E7%A8%8B%E7%9B%B8%E5%85%B3/assets/ThreadLocal/ThreadLocal_2.png?spaceId=b562607a-6753-49c5-81c0-b55bec749ebe&amp;t=319d28e7-adc6-8125-b4d6-fa071537ed5d" alt="notion image" loading="lazy" decoding="async"/></div></figure><div class="notion-text notion-block-319d28e7adc6816ea0acef47ddfb1695"><em><span class="notion-default">实线箭头是强引用，虚线箭头是弱引用</span></em></div><div class="notion-text notion-block-319d28e7adc681a0bd0df7d3607de2af"><span class="notion-default">^4efc86</span></div><div class="notion-text notion-block-319d28e7adc6813c8d4ef1ba259dc22f"><span class="notion-default">总结：当你在一个方法/栈中创建 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocal</code></span><span class="notion-default">，该对象就被栈强引用着，直到方法执行完毕，栈被销毁，失去了强引用的、只有 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocalMap.Entry</code></span><span class="notion-default"> 弱引用的 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocal</code></span><span class="notion-default"> 就会被 GC 回收，但 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocalMap.Entry</code></span><span class="notion-default"> 中的 </span><span class="notion-default"><code class="notion-inline-code">value</code></span><span class="notion-default"> 却实打实强引用了一个对象，</span><span class="notion-default"><code class="notion-inline-code">entry</code></span><span class="notion-default"> 不死它就不死，所以说养成良好习惯 </span><span class="notion-default"><code class="notion-inline-code">new ThreadLocal()</code></span><span class="notion-default"> 和 </span><span class="notion-default"><code class="notion-inline-code">ThreadLocal.remove()</code></span><span class="notion-default"> 要在方法中成对出现。</span></div><h3 class="notion-h notion-h2 notion-h-indent-0 notion-block-319d28e7adc681dc877cf134b688ce47" data-id="319d28e7adc681dc877cf134b688ce47"><span><div id="319d28e7adc681dc877cf134b688ce47" class="notion-header-anchor"></div><a class="notion-hash-link" href="#319d28e7adc681dc877cf134b688ce47" title="应用场景"><svg viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg></a><span class="notion-h-title"><span class="notion-default">应用场景</span></span></span></h3><div class="notion-text notion-block-319d28e7adc681c6a25fc7999dd02c88"><span class="notion-default">当一个变量是共享的（成员变量、static 等），但是需要每个线程互不影响，相互隔离，就可以使用 ThreadLocal。</span></div><ol start="1" class="notion-list notion-list-numbered notion-block-319d28e7adc6814997edeacdbc228df0" style="list-style-type:decimal"><li><span class="notion-default">跨层传递信息的时候，每个方法都声明一个参数很麻烦，A、B、C、D 4 个类互相传递，每个方法都声明参数降低了维护性，可以用一个 ThreadLocal 共享变量，在 A 存值， B、C、D 都可以获取。</span></li></ol><ol start="2" class="notion-list notion-list-numbered notion-block-319d28e7adc68170aa14c6015adf90d5" style="list-style-type:decimal"><li><span class="notion-default">隔离线程，存储一些线程不安全的工具对象，如（SimpleDateFormat）。</span></li></ol><ol start="3" class="notion-list notion-list-numbered notion-block-319d28e7adc6812bb11de308ef9ce32b" style="list-style-type:decimal"><li><span class="notion-default">Spring 中的事务管理器就是使用的 ThreadLocal，这也是为什么声明式事务如果用到了多线程，它是没有办法达到一致性的，因为一个线程就存储了一个事务。</span></li></ol><ol start="4" class="notion-list notion-list-numbered notion-block-319d28e7adc6817dbdc3e2b4856cdb31" style="list-style-type:decimal"><li><span class="notion-default">SpringMVC 的 HttpSession、HttpServletReuqest、HttpServletResponse 都是放在 ThreadLocal，因为 servlet 是单例的，而 SpringMVC 允许在 controller 类中通过@Autowired 配置 request、response 以及 requestcontexts 等实例对象，在任何地方获取请求都永远是当前的请求。底层就是搭配 ThreadLocal 才实现线程安全。</span></li></ol></main></div>]]></content:encoded>
        </item>
    </channel>
</rss>