diff --git a/docs/Java/JVM/JVM系列-第10章-垃圾回收概述和相关算法.md b/docs/Java/JVM/JVM系列-第10章-垃圾回收概述和相关算法.md index a3c83e4..8c2e00b 100644 --- a/docs/Java/JVM/JVM系列-第10章-垃圾回收概述和相关算法.md +++ b/docs/Java/JVM/JVM系列-第10章-垃圾回收概述和相关算法.md @@ -10,6 +10,7 @@ keywords: JVM,虚拟机。 description: JVM系列-第10章-垃圾回收概述和相关算法。 cover: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/logo.png' top_img: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/blog/top_img.jpg' +abbrlink: d54daa0f date: 2020-11-16 18:14:02 --- @@ -20,7 +21,7 @@ date: 2020-11-16 18:14:02 - + 1. Java 和 C++语言的区别,就在于垃圾收集技术和内存动态分配上,C++语言没有垃圾收集技术,需要程序员手动的收集。 @@ -102,7 +103,7 @@ date: 2020-11-16 18:14:02 **十几年前磁盘碎片整理的日子** - + @@ -174,7 +175,7 @@ date: 2020-11-16 18:14:02 ### 应该关心哪些区域的回收? - + 1. 垃圾收集器可以对年轻代回收,也可以对老年代回收,甚至是全栈和方法区的回收, 1. 其中,**Java堆是垃圾收集器的工作重点** @@ -216,7 +217,7 @@ date: 2020-11-16 18:14:02 ### 循环引用 - + 当p的指针断开的时候,内部的引用形成一个循环,计数器都还算1,无法被回收,这就是循环引用,从而造成内存泄漏 @@ -255,7 +256,7 @@ public class RefCountGC { - + * 如果不小心直接把`obj1.reference`和`obj2.reference`置为null。则在Java堆中的两块内存依然保持着互相引用,无法被回收 @@ -353,7 +354,7 @@ Process finished with exit code 0 3. 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。 4. 在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。 - + @@ -371,7 +372,7 @@ Process finished with exit code 0 - 基本数据类型对应的Class对象,一些常驻的异常对象(如:NullPointerException、OutofMemoryError),系统类加载器。 7. 反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。 - + @@ -453,7 +454,7 @@ Object 类中 finalize() 源码 **通过 JVisual VM 查看 Finalizer 线程** - + @@ -579,7 +580,7 @@ MAT与JProfiler的GC Roots溯源 **方式一:命令行使用 jmap** - + @@ -631,23 +632,23 @@ public class GCRootsTest { 1、先执行第一步,然后停下来,去生成此步骤dump文件 - + 2、 点击【堆 Dump】 - + 3、右键 --\> 另存为即可 - + 4、输入命令,继续执行程序 - + 5、我们接着捕获第二张堆内存快照 - + @@ -657,19 +658,19 @@ public class GCRootsTest { > 点击Open Heap Dump也行 - + 2、选择Java Basics --> GC Roots - + 3、第一次捕捉堆内存快照时,GC Roots 中包含我们定义的两个局部变量,类型分别为 ArrayList 和 Date,Total:21 - + 4、打开第二个dump文件,第二次捕获内存快照时,由于两个局部变量引用的对象被释放,所以这两个局部变量不再作为 GC Roots ,从 Total Entries = 19 也可以看出(少了两个 GC Roots) - + @@ -716,13 +717,13 @@ public class GCRootsTest { 1、 - + 2、 - + - + 可以发现颜色变绿了,可以动态的看变化 @@ -730,11 +731,11 @@ public class GCRootsTest { 3、右击对象,选择 Show Selection In Heap Walker,单独的查看某个对象 - + - + @@ -742,13 +743,13 @@ public class GCRootsTest { 点击Show Paths To GC Roots,在弹出界面中选择默认设置即可 - + - + - + ### JProfiler 分析 OOM @@ -798,11 +799,11 @@ count = 6 1、看这个超大对象 - + 2、揪出 main() 线程中出问题的代码 - + @@ -837,7 +838,7 @@ count = 6 * 注意:标记的是被引用的对象,也就是可达对象,并非标记的是即将被清除的垃圾对象 2. 清除:Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收 - + @@ -876,7 +877,7 @@ count = 6 将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收 - + 新生代里面就用到了复制算法,Eden区和S0区存活对象整体复制到S1区 @@ -906,7 +907,7 @@ count = 6 2. 老年代大量的对象存活,那么复制的对象将会有很多,效率会很低 3. 在新生代,对常规应用的垃圾回收,一次通常可以回收70% - 99% 的内存空间。回收性价比很高。所以现在的商业虚拟机都是用这种收集算法回收新生代。 - + @@ -935,7 +936,7 @@ count = 6 2. 第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。之后,清理边界外所有的空间。 - + @@ -1066,7 +1067,7 @@ A:无,没有最好的算法,只有最合适的算法 1. 一般来说,在相同条件下,堆空间越大,一次GC时所需要的时间就越长,有关GC产生的停顿也越长。为了更好地控制GC产生的停顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。 3. 分代算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同小区间。每一个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间。 - + diff --git a/docs/Java/JVM/JVM系列-第11章-垃圾回收相关概念.md b/docs/Java/JVM/JVM系列-第11章-垃圾回收相关概念.md index 89b8ccb..b0f4975 100644 --- a/docs/Java/JVM/JVM系列-第11章-垃圾回收相关概念.md +++ b/docs/Java/JVM/JVM系列-第11章-垃圾回收相关概念.md @@ -10,6 +10,7 @@ keywords: JVM,虚拟机。 description: JVM系列-第11章-垃圾回收相关概念。 cover: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/logo.png' top_img: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/blog/top_img.jpg' +abbrlink: 4d401a8b date: 2020-11-17 12:33:24 --- @@ -117,7 +118,7 @@ JVM参数: 2、我也查过了大对象阈值的默认值 - + 我不太懂这个默认值为啥是0,我猜测可能是代表什么比例,目前也没有搜到相关的东西。这个不太重要,暂时就没有太深究,希望读者有知道的可以告知我一声。 @@ -185,11 +186,11 @@ Heap 1、来看看字节码:实例方法局部变量表第一个变量肯定是 this - + 2、你有没有看到,局部变量表的大小是 2。但是局部变量表里只有一个索引为0的啊?那索引为1的是哪个局部变量呢?实际上索引为1的位置是buffer在占用着,执行 System.gc() 时,栈中还有 buffer 变量指向堆中的字节数组,所以没有进行GC - + 3、那么这种代码块的情况,什么时候会被GC呢?我们来看第四个方法 @@ -217,11 +218,11 @@ A:局部变量表长度为 2 ,这说明了出了代码块时,buffer 就出 > 这点看不懂的可以看我前面的文章:虚拟机栈 --> Slot的重复利用 - + - + @@ -302,7 +303,7 @@ Heap 右边的图:后期有一些对象不用了,按道理应该断开引用,但是存在一些链没有断开(图示中的Forgotten Reference Memory Leak),从而导致没有办法被回收。 - + @@ -446,7 +447,7 @@ Process finished with exit code -1 2. 并发不是真正意义上的“同时进行”,只是CPU把一个时间段划分成几个时间片段(时间区间),然后在这几个时间区间之间来回切换。由于CPU处理的速度非常快,只要时间间隔处理得当,即可让用户感觉是多个应用程序同时在进行 - + @@ -461,7 +462,7 @@ Process finished with exit code -1 3. 适合科学计算,后台处理等弱交互场景 - + > **并发与并行的对比** @@ -482,7 +483,7 @@ Process finished with exit code -1 * 相较于并行的概念,单线程执行。 * 如果内存不够,则程序暂停,启动JVM垃圾回收器进行垃圾回收(单线程) - + @@ -492,7 +493,7 @@ Process finished with exit code -1 - 比如用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上; 2. 典型垃圾回收器:CMS、G1 - + @@ -551,7 +552,7 @@ Process finished with exit code -1 1、一般的垃圾回收算法至少会划分出两个年代,年轻代和老年代。但是单纯的分代理论在垃圾回收的时候存在一个巨大的缺陷:为了找到年轻代中的存活对象,却不得不遍历整个老年代,反过来也是一样的。 - + 2、如果我们从年轻代开始遍历,那么可以断定N, S, P, Q都是存活对象。但是,V却不会被认为是存活对象,其占据的内存会被回收了。这就是一个惊天的大漏洞!因为U本身是老年代对象,而且有外部引用指向它,也就是说U是存活对象,而U指向了V,也就是说V也应该是存活对象才是!而这都是因为我们只遍历年轻代对象! @@ -599,7 +600,7 @@ Process finished with exit code -1 4. 这4种引用强度依次逐渐减弱。除强引用外,其他3种引用均可以在java.lang.ref包中找到它们的身影。如下图,显示了这3种引用类型对应的类,开发人员可以在应用程序中直接使用它们。 - + @@ -661,7 +662,7 @@ Hello,尚硅谷 `StringBuffer str = new StringBuffer("hello,尚硅谷");` - + diff --git a/docs/Java/JVM/JVM系列-第6章-方法区.md b/docs/Java/JVM/JVM系列-第6章-方法区.md index 7302aa3..e548959 100644 --- a/docs/Java/JVM/JVM系列-第6章-方法区.md +++ b/docs/Java/JVM/JVM系列-第6章-方法区.md @@ -10,6 +10,7 @@ keywords: JVM,虚拟机。 description: JVM系列-第6章-方法区。 cover: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/logo.png' top_img: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/blog/top_img.jpg' +abbrlink: 136cd965 date: 2020-11-13 19:38:42 --- @@ -24,7 +25,7 @@ date: 2020-11-13 19:38:42 ThreadLocal:如何保证多个线程在并发环境下的安全性?典型场景就是数据库连接管理,以及会话管理。 - + **栈、堆、方法区的交互关系** @@ -35,7 +36,7 @@ ThreadLocal:如何保证多个线程在并发环境下的安全性?典型场 3. 真正的 person 对象存放在 Java 堆中 4. 在 person 对象中,有个指针指向方法区中的 person 类型数据,表明这个 person 对象是用方法区中的 Person 类 new 出来的 - + 方法区的理解 -------- @@ -47,7 +48,7 @@ ThreadLocal:如何保证多个线程在并发环境下的安全性?典型场 1. 《Java虚拟机规范》中明确说明:尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。但对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。 3. 所以,**方法区可以看作是一块独立于Java堆的内存空间**。 - + @@ -87,7 +88,7 @@ public class MethodAreaDemo { 简单的程序,加载了1600多个类 - + @@ -103,7 +104,7 @@ public class MethodAreaDemo { 5. 永久代、元空间二者并不只是名字变了,内部结构也调整了 6. 根据《Java虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出OOM异常 - + @@ -120,7 +121,7 @@ public class MethodAreaDemo { 2. -XX:MaxPermsize来设定永久代最大可分配空间。32位机器默认是64M,64位机器模式是82M 3. 当JVM加载的类信息容量超过了这个值,会报异常OutofMemoryError:PermGen space。 - + ### JDK8及以后(元空间) @@ -228,11 +229,11 @@ Exception in thread "main" java.lang.OutOfMemoryError: Metaspace #### 概念 - + 《深入理解Java虚拟机》书中对方法区(Method Area)存储内容描述如下:它用于存储已被虚拟机加载的**类型信息、常量、静态变量、即时编译器编译后的代码缓存**等。 - + @@ -714,7 +715,7 @@ public static int count; - + 1. 方法区,内部包含了运行时常量池 2. 字节码文件,内部包含了常量池。(之前的字节码文件中已经看到了很多Constant pool的东西,这个就是常量池) @@ -728,7 +729,7 @@ public static int count; 1. 一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述符信息外。还包含一项信息就是**常量池表**(**Constant Pool Table**),包括各种字面量和对类型、域和方法的符号引用。 2. 字面量: 10 , “我是某某”这种数字和字符串都是字面量 - + **为什么需要常量池?** @@ -749,7 +750,7 @@ public static int count; 2. 比如说我们这个文件中有6个地方用到了"hello"这个字符串,如果不用常量池,就需要在6个地方全写一遍,造成臃肿。我们可以将"hello"等所需用到的结构信息记录在常量池中,并通过**引用的方式**,来加载、调用所需的结构 4. 这里的代码量其实很少了,如果代码多的话,引用的结构将会更多,这里就需要用到常量池了。 - + **常量池中有啥?** @@ -924,69 +925,69 @@ SourceFile: "MethodAreaDemo.java" 1、初始状态 - + 2、首先将操作数500压入操作数栈中 - + 3、然后操作数 500 从操作数栈中取出,存储到局部变量表中索引为 1 的位置 - + 4、 - + 5、 - + 6、 - + 7、 - + 8、 - + 9、 - + 10、 - + 11、图片写错了是#25和#26(获得System类) - + 12、 - + 13、 - + 15、执行加法运算后,将计算结果放在操作数栈顶 - + 16、就是真正的打印 - + 17、 - + @@ -1021,7 +1022,7 @@ SourceFile: "MethodAreaDemo.java" 方法区由永久代实现,使用 JVM 虚拟机内存(虚拟的内存) - + @@ -1029,7 +1030,7 @@ SourceFile: "MethodAreaDemo.java" 方法区由永久代实现,使用 JVM 虚拟机内存 - + @@ -1037,7 +1038,7 @@ SourceFile: "MethodAreaDemo.java" 方法区由元空间实现,使用物理机本地内存 - + @@ -1095,15 +1096,15 @@ public class StaticFieldTest { JDK6环境下 -image-20201113224231761 +image-20201113224231761 JDK7环境下 - + JDK8环境 - + @@ -1150,7 +1151,7 @@ public class StaticObjTest { 4、测试发现:三个对象的数据在内存中的地址都落在Eden区范围内,所以结论:**只要是对象实例必然会在Java堆中分配**。 - + > 1、0x00007f32c7800000(Eden区的起始地址) ---- 0x00007f32c7b50000(Eden区的终止地址) > @@ -1162,7 +1163,7 @@ public class StaticObjTest { 5、接着,找到了一个引用该staticObj对象的地方,是在一个java.lang.Class的实例里,并且给出了这个实例的地址,通过Inspector查看该对象实例,可以清楚看到这确实是一个java.lang.Class类型的对象实例,里面有一个名为staticobj的实例字段: - + 从《Java虚拟机规范》所定义的概念模型来看,所有Class相关的信息都应该存放在方法区之中,但方法区该如何实现,《Java虚拟机规范》并未做出规定,这就成了一件允许不同虚拟机自己灵活把握的事情。JDK7及其以后版本的HotSpot虚拟机选择把静态变量与类型在Java语言一端的映射Class对象存放在一起,**存储于Java堆之中**,从我们的实验中也明确验证了这一点 @@ -1217,7 +1218,7 @@ public class StaticObjTest { - + @@ -1271,7 +1272,7 @@ public class BufferTest { 直接占用了 1G 的本地内存 - + @@ -1281,13 +1282,13 @@ public class BufferTest { 原来采用BIO的架构,在读写本地文件时,我们需要从用户态切换成内核态 - + **直接缓冲区(NIO)** NIO 直接操作物理磁盘,省去了中间过程 - + ### 直接内存与 OOM @@ -1351,7 +1352,7 @@ Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory - + diff --git a/docs/Java/JVM/JVM系列-第7章-对象的实例化内存布局与访问定位.md b/docs/Java/JVM/JVM系列-第7章-对象的实例化内存布局与访问定位.md index c87b54a..82d08f9 100644 --- a/docs/Java/JVM/JVM系列-第7章-对象的实例化内存布局与访问定位.md +++ b/docs/Java/JVM/JVM系列-第7章-对象的实例化内存布局与访问定位.md @@ -10,6 +10,7 @@ keywords: JVM,虚拟机。 description: JVM系列-第7章-对象的实例化内存布局与访问定位。 cover: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/logo.png' top_img: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/blog/top_img.jpg' +abbrlink: debff71a date: 2020-11-14 19:38:42 --- @@ -36,7 +37,7 @@ date: 2020-11-14 19:38:42 - + ### 对象创建的方式 @@ -211,7 +212,7 @@ class Account{ 对象的内存布局 --------- - + @@ -242,7 +243,7 @@ class Account{ 图解内存布局 - + @@ -251,7 +252,7 @@ class Account{ **JVM是如何通过栈帧中的对象引用访问到其内部的对象实例呢?** - + 定位,通过栈上reference访问 @@ -262,7 +263,7 @@ class Account{ 1. 缺点:在堆空间中开辟了一块空间作为句柄池,句柄池本身也会占用空间;通过两次指针访问才能访问到堆中的对象,效率低 2. 优点:reference中存储稳定句柄地址,对象被移动(垃圾收集时移动对象很普遍)时只会改变句柄中实例数据指针即可,reference本身不需要被修改 - + @@ -271,4 +272,4 @@ class Account{ 1. 优点:直接指针是局部变量表中的引用,直接指向堆中的实例,在对象实例中有类型指针,指向的是方法区中的对象类型数据 2. 缺点:对象被移动(垃圾收集时移动对象很普遍)时需要修改 reference 的值 - \ No newline at end of file + \ No newline at end of file diff --git a/docs/Java/JVM/JVM系列-第8章-执行引擎.md b/docs/Java/JVM/JVM系列-第8章-执行引擎.md index 6e8de03..053c148 100644 --- a/docs/Java/JVM/JVM系列-第8章-执行引擎.md +++ b/docs/Java/JVM/JVM系列-第8章-执行引擎.md @@ -10,6 +10,7 @@ keywords: JVM,虚拟机。 description: JVM系列-第8章-执行引擎。 cover: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/logo.png' top_img: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/blog/top_img.jpg' +abbrlink: 408712f4 date: 2020-11-15 19:48:42 --- @@ -23,7 +24,7 @@ date: 2020-11-15 19:48:42 - + ### 执行引擎概述 @@ -34,7 +35,7 @@ date: 2020-11-15 19:48:42 3. JVM的主要任务是负责**装载字节码到其内部**,但字节码并不能够直接运行在操作系统之上,因为字节码指令并非等价于本地机器指令,它内部包含的仅仅只是一些能够被JVM所识别的字节码指令、符号表,以及其他辅助信息。 4. 那么,如果想要让一个Java程序运行起来,执行引擎(Execution Engine)的任务就是**将字节码指令解释/编译为对应平台上的本地机器指令才可以**。简单来说,JVM中的执行引擎充当了将高级语言翻译为机器语言的译者。 - + 1、前端编译:从Java程序员-字节码文件的这个过程叫前端编译 @@ -51,7 +52,7 @@ date: 2020-11-15 19:48:42 3. 当然方法在执行的过程中,执行引擎有可能会通过存储在局部变量表中的对象引用准确定位到存储在Java堆区中的对象实例信息,以及通过对象头中的元数据指针定位到目标对象的类型信息。 4. 从外观上来看,所有的Java虚拟机的执行引擎输入、处理、输出都是一致的:输入的是字节码二进制流,处理过程是字节码解析执行、即时编译的等效过程,输出的是执行过程。 - + @@ -71,18 +72,18 @@ Java代码编译和执行过程 - + 3. javac编译器(前端编译器)流程图如下所示: - + 4. Java字节码的执行是由JVM执行引擎来完成,流程图如下所示 - + @@ -105,7 +106,7 @@ Java代码编译和执行过程 **用图总结一下** - + 机器码 指令 汇编语言 ------------- @@ -164,7 +165,7 @@ Java代码编译和执行过程 - + @@ -193,7 +194,7 @@ Java代码编译和执行过程 2. 汇编过程:实际上指把汇编语言代码翻译成目标机器指令的过程。 - + @@ -211,7 +212,7 @@ Java代码编译和执行过程 3. 当一条字节码指令被解释执行完成后,接着再根据PC寄存器中记录的下一条需要被执行的字节码指令执行解释操作。 - + @@ -288,7 +289,7 @@ Java代码编译和执行过程 2. 在生产环境发布过程中,以分批的方式进行发布,根据机器数量划分成多个批次,每个批次的机器数至多占到整个集群的1/8。曾经有这样的故障案例:某程序员在发布平台进行分批发布,在输入发布总批数时,误填写成分为两批发布。如果是热机状态,在正常情况下一半的机器可以勉强承载流量,但由于刚启动的JVM均是解释执行,还没有进行热点代码统计和JIT动态编译,导致机器启动之后,当前1/2发布成功的服务器马上全部宕机,此故障说明了JIT的存在。—**阿里团队** - + @@ -317,7 +318,7 @@ public class JITTest { 通过 JVisualVM 查看 JIT 编译器执行的编译次数 - + @@ -369,7 +370,7 @@ public class JITTest { * 如果已超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。 * 如果未超过阈值,则使用解释器对字节码文件解释执行 - + @@ -387,7 +388,7 @@ public class JITTest { 它的作用是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge)。显然,建立回边计数器统计的目的就是为了触发OSR编译。 - + @@ -401,7 +402,7 @@ public class JITTest { - + diff --git a/docs/Java/JVM/JVM系列-第9章-StringTable(字符串常量池).md b/docs/Java/JVM/JVM系列-第9章-StringTable(字符串常量池).md index 8cce765..ad50be0 100644 --- a/docs/Java/JVM/JVM系列-第9章-StringTable(字符串常量池).md +++ b/docs/Java/JVM/JVM系列-第9章-StringTable(字符串常量池).md @@ -10,6 +10,7 @@ keywords: JVM,虚拟机。 description: JVM系列-第9章-StringTable(字符串常量池)。 cover: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/logo.png' top_img: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/blog/top_img.jpg' +abbrlink: ee2ba71e date: 2020-11-16 12:38:02 --- @@ -166,11 +167,11 @@ str 的内容并没有变:“test ok” 位于字符串常量池中的另一 4. 在JDK7中,StringTable的长度默认值是60013,StringTablesize设置没有要求 5. 在JDK8中,StringTable的长度默认值是60013,StringTable可以设置的最小值为1009 - + - + @@ -276,11 +277,11 @@ String 的内存分配 - + - + @@ -382,23 +383,23 @@ public class StringTest4 { 1、程序启动时已经加载了 2293 个字符串常量 - + 2、加载了一个换行符(println),所以多了一个 - + 3、加载了字符串常量 “1”~“9” - + 4、加载字符串常量 “10” - + 5、之后的字符串"1" 到 "10"不会再次加载 - + @@ -425,7 +426,7 @@ class Memory { 分析运行时内存(foo() 方法是实例方法,其实图中少了一个 this 局部变量) - + @@ -493,7 +494,7 @@ class Memory { IDEA 反编译 class 文件后,来看这个问题 - + @@ -929,7 +930,7 @@ public class StringNewTest { 5. `23 ldc #8 ` :在字符串常量池中放入 “b”(如果之前字符串常量池中没有 “b” 的话) 6. `31 invokevirtual #9 ` :调用 StringBuilder 的 toString() 方法,会生成一个 String 对象 - + @@ -988,13 +989,13 @@ JDK6 :正常眼光判断即可 * new String() 即在堆中 * str.intern() 则把字符串放入常量池中 - + JDK7及后续版本,**注意大坑** - + @@ -1053,11 +1054,11 @@ public class StringExer1 { **JDK6** -![image-20201116113423492](https://cdn.jsdelivr.net/gh/youthlql/lql_img/JVM/chapter_009/0015.png) +![image-20201116113423492](https://cdn.jsdelivr.net/gh/youthlql/lql_img_002/JVM/chapter_009/0015.png) **JDK7/8** - + @@ -1080,7 +1081,7 @@ public class StringExer1 { } ``` - + **练习3** @@ -1169,11 +1170,11 @@ public class StringIntern2 { arr[i] = new String(String.valueOf(data[i % data.length])); ``` - + - + 2、使用 intern() 方法:由于数组中字符串的引用都指向字符串常量池中的字符串,所以程序需要维护的 String 对象更少,内存占用也更低 @@ -1182,11 +1183,11 @@ arr[i] = new String(String.valueOf(data[i % data.length])); arr[i] = new String(String.valueOf(data[i % data.length])).intern(); ``` - + - + @@ -1218,11 +1219,11 @@ public class StringGCTest { * Number of entries 和 Number of literals 明显没有 100000 * 以上两点均说明 StringTable 区发生了垃圾回收 - + - +