首页 >> 大全

JVM虚拟机上篇之内存与垃圾回收

2023-12-31 大全 27 作者:考证青年

一、JVM 与 Java 体系结构 1.1 Java VS C++

Java 和 C++ 之间有一堵由内存动态分配和垃圾收集技术所围成的高墙。

1.2 Java:跨平台的语言

1.3 JVM:跨语言的平台

1.3 字节码 1.4 多语言混合编程

1.5 Java 发展的重大事件

1.6 虚拟

1.7 Java 虚拟机

1.8 JVM 的位置

1.9 JVM 的整体结构

1.10 Java 代码执行流程

1.11 JVM 的架构模型

1.12 JVM 的生命周期 虚拟机的启动。

Java 虚拟机的启动是通过引导类加载器( class )创建一个初始类( class)来完成的,这个类是由虚拟机的具体实现指定的。虚拟机的执行。

(1)一个运行中的 Java 虚拟机有着一个清晰的任务:执行 Java 程序。

(2)程序开始执行时它才运行,程序结束时它就停止。

(3)执行一个所谓的 Java 程序的时候,真正在执行的是一个叫做 Java 虚拟机的进程。虚拟机的退出。

有如下几种情况:

(1)程序正常执行结束。

(2)程序在执行过程中遇到了异常或错误而异常终止。

(3)由于操作系统出现错误而导致 Java 虚拟机进程终止。

(4)某线程调用 类或 类的 exit 方法,或 类的 halt 方法,并且 Java 安全管理器也允许这次 exit 或 halt 操作。

(5)另:JNI(Java )规范描述了用 JNI API 来加载或卸载 Java 虚拟机时,Java 虚拟机的退出情况。 1.13 JVM 发展历程 Sun VM Exact VM SUN 公司的 VM(重点) BEA 的 IBM 的 J9 二、类加载子系统 2.1 内存结构概述

2.2 类加载器与类的加载过程 2.2.1 类加载器子系统作用 2.2.2 类的加载过程

加载() 链接()

1、验证()

2、准备()

public class Test01 {private static int a = 3; // prepare阶段:a = 0;Initial:a = 3public static void main(String[] args) {System.out.println(a);}
}

3、解析()

初始化()

public class Test02 {private static int num = 1; // linking 的 prepare 阶段: num = 0; initial: num = 1 -> num = 2// 静态代码块只能访问到定义在静态代码块之前的变量,定义在它之后的变量,静态代码块可以赋值,但是不能访问static {num = 2;number = 20;System.out.println("->" + num);// System.out.println(number); 报错:非法的前向引用}private static int number = 10; // linking 的 prepare 阶段: number = 0; initial: number = 20 -> number = 10public static void main(String[] args) {System.out.println(num); // 2System.out.println(number); // 10}
}

2.3 几种类加载器 2.3.1 类加载器的分类

public class ClassLoaderTest {public static void main(String[] args) {// 获取系统类加载器ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();System.out.println(systemClassLoader); // sun.misc.Launcher$AppClassLoader@18b4aac2// 获取其上层:扩展类加载器ClassLoader extClassLoader = systemClassLoader.getParent();System.out.println(extClassLoader); // sun.misc.Launcher$ExtClassLoader@1b6d3586// 获取不到其上层:引导类加载器ClassLoader bootstrapClassLoader = extClassLoader.getParent();System.out.println(bootstrapClassLoader); // null// 对于用户自定义类来说:默认使用系统类加载器进行加载ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();System.out.println(classLoader); // sun.misc.Launcher$AppClassLoader@18b4aac2// String 类使用引导类加载器进行加载 ---> Java 的核心类库都是使用引导类加载器进行加载的ClassLoader classLoader1 = String.class.getClassLoader();System.out.println(classLoader1); // null}
}

2.3.2 引导类加载器(启动类加载器, ) 2.3.3 扩展类加载器( ) 2.3.4 系统类加载器(应用程序类加载器,) 2.3.5 用户自定义的类加载器 2.3.6 关于 2.4 双亲委派机制

package java.lang;
public class String {public static void main(String[] args) {// 在类 java.lang.String 中找不到 main 方法System.out.println("自定义 java.lang.String");}
}

package java.lang;
public class ShkStart {public static void main(String[] args) {// Prohibited package name: java.langSystem.out.println("java.lang.ShkStart");}
}

2.5 其他 2.5.1 对类加载器的引用

JVM 必须知道一个类型是由启动类加载器加载还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么 JVM 会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM 需要保证这两个类型的类加载器是相同的。

2.5.2 类的主动使用和被动使用 三、运行时数据区概述及线程 3.1 运行时数据区概述 3.1.1 运行时数据区的结构

3.2 线程 3.2.1 JVM 系统线程

四、运行时数据区 – 程序计数器(PC寄存器) 4.1 PC 介绍

4.2 举例说明

下面是一段 Java 程序的字节码指令:

4.3 两个常见问题 五、运行时数据区 – 虚拟机栈 5.1 虚拟机栈概述 5.1.1 虚拟机栈出现的背景

由于跨平台性的设计,Java 的指令都是根据栈来设计的。不同平台 CPU 架构不同,所以不能设计为基于寄存器的,因为基于寄存器的话它和具体的 CPU 耦合度高。

优点是跨平台,指令集小,编译器容易实现。缺点是性能比基于寄存器的差,实现同样的功能需要更多的指令。

5.1.2 内存中的堆与栈 5.1.3 虚拟机栈的基本内容 5.1.4 栈中可能出现的异常 5.1.5 设置栈的内存大小 5.2 栈的存储单位 5.2.1 栈中存储什么 5.2.2 栈的运行原理 5.2.3 栈帧的内部结构

5.3 局部变量表 5.3.1 Slot 概念 5.3.2 对 Slot 的理解

虚拟机内存回收机制_java虚拟机垃圾回收机制_

5.3.3 Slot 的重复利用

public class SlotTest {public void test(){int a = 0;{// 变量 b 出了大括号后就销毁了,但数组空间已经开辟了int b = 1;}// 变量 c 是使用之前已经销毁的变量 b 占用的 slot 的位置int c = 2;}
}

5.3.4 成员变量和局部变量的对比 5.3.5 补充说明 5.4 操作数栈 5.5 栈顶缓存技术

基于栈式架构的虚拟机所使用的零地址指令更加紧凑,但完成一项操作的时候必然需要使用更多的入栈和出栈指令,这意味着将需要更多的指令分派和内存读/写次数。

由于操作数是存储在内存中的,因此频繁地执行内存读/写操作必然会影响执行速度。为了解决这个问题, JVM 的设计者们提出了栈顶缓存(ToS)技术:将栈顶元素全部缓存在物理 CPU 的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率。

5.6 动态链接 5.7 方法的调用:解析与分派 5.7.1 方法的调用 5.7.2 虚方法与实方法 5.7.3 关于 指令 5.7.4 动态类型语言和静态类型语言

// 变量本身有类型信息
Java: String s = "abc";
// 变量本身没有类型信息,变量值有
JS: var name = "abc";   var name = 123;

5.8 方法返回地址 5.8.1 正常退出 5.8.2 遇到异常 5.8.3 两者的区别 5.9 一些附加信息

栈帧中还允许携带与 Java 虚拟机实现相关的一些附加信息。例如:对程序调试提供支持的信息。

5.10 栈的相关面试题

public class ThreadSafty {// 该方法中 s 的声明方式是线程安全的public static void method1(){StringBuilder s = new StringBuilder();s.append("a");s.append("b");}// s 的操作过程:是线程不安全的。因为 s 是从外面传进来的,有可能由多个线程所调用// 严格上 s 不算是方法内定义变量,算是形参的变量public static void method2(StringBuilder s){s.append("a");s.append("b");}// s 的操作过程:是线程不安全的。因为将 s 返回出去后就有可能被其他位置上的多个线程所调用public static StringBuilder method3(){StringBuilder s = new StringBuilder();s.append("a");s.append("b");return s;}// s 的操作过程:是线程安全的。因为 s 其实就在该方法内部消亡了,没有传到外面去。public static String method4(){StringBuilder s = new StringBuilder();s.append("a");s.append("b");return s.toString();}
}

六、本地方法接口 6.1 什么是本地方法 6.2 为什么要使用 6.3 现状

目前本地方法使用的越来越少了,除非是与硬件有关的应用,比如通过 Java 程序驱动打印机或者 Java 系统管理生产设备。在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用 通信,也可以使用 Web 等等。

七、运行时数据区 – 本地方法栈 八、运行时数据区 – 堆 8.1 堆的核心概念 堆内存细分

8.2 设置堆内存的大小与 OOM 8.2.1 堆空间大小的设置 8.2.2 实例

public class HeapSpace {public static void main(String[] args) {// 返回 Java 虚拟机中的初始堆内存大小long initialSize = Runtime.getRuntime().totalMemory() / 1024 / 1024;// 返回 Java 虚拟机中的最大堆内存大小long maxSize = Runtime.getRuntime().maxMemory() / 1024 / 1024;System.out.println("-Xms: " + initialSize + "M"); // -Xms: 575MSystem.out.println("-Xmx: " + maxSize + "M"); // -Xmx: 575M}
}

8.3 年轻代与老年代

配置新生代与老年代的占比

配置新生代内部区域的占比 8.4 对象的分配过程 8.4.1 对象分配的一般过程 new 的对象先放到伊甸园区。此区有大小限制。当伊甸园区的空间满了之后,如果程序又需要创建对象,此时 JVM 的垃圾回收器会对伊甸园区进行垃圾回收(YGC/Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁,然后将伊甸园区中剩余的对象移动到幸存者区 0 区,此时伊甸园区就完全为空了。接下来我们再在伊甸园区中存放对象。如果在放的过程中伊甸园区又满了,此时又会触发 YGC:将垃圾回收掉,并且将剩余的对象放到幸存者 1 区,同时还会将上次幸存下来放到幸存者 0 区的对象,如果还需要被使用(也就是没被回收),也会放到幸存者 1 区。此时它们的 age 会自动增加。什么时候能去养老区呢?可以设置次数,默认(阈值)是 15 次。可以设置参数:-XX:=进行设置。在养老区,相对悠闲。当养老区内存不足时,再次触发 GC:Major GC,进行养老区的内存清理。若养老区执行了 Major GC 之后发现依然无法进行对象的保存,就会产生 OOM 异常。java.lang.: Java heap space总结:

(1)我们为每个对象分配了一个年龄计数器 age。在伊甸园区放到幸存者区的时候我们把 age 赋值为 1。

(2)当 Eden 区满的时候会触发YGC(Minor GC),YGC 会将 Eden 区和 区一起进行垃圾回收。 区满的时候不会触发 YGC

(3)进行完一次 YGC 后,伊甸园区就会为空。

(4)针对幸存者 S0、S1 区的总结:复制之后有交换,谁空谁是 to。

(5)关于垃圾回收:频繁在新生代收集,很少在养老区收集,几乎不在永久代/元空间收集。 8.4.2 对象分配的特殊情况 如果进行完 YGC 之后,伊甸园区仍然放不下新的对象,此时这个新的对象会直接存放到老年代。如果老年代存放的下就放在老年代;如果老年代也存放不下,此时有两种情况:第一种是本来老年代空间足够,但此时老年代已经存放了其他对象,导致存放不了新的对象,因此会先进行 FGC,如果垃圾回收完之后能放进老年代就放进,如果还是不够那就直接报 OOM。第二种是老年代空间本来就放不下这个新的对象,因此也会报 OOM。如果空的 to 区放不下从伊甸园区中过来的对象,那么我们会直接把对象放到老年代。

8.5 Minor GC、Major GC、Full GC 8.5.1 概述

8.5.2 年轻代GC(Minor GC) 的触发机制 8.5.3 老年代GC(Major GC) 的触发机制 8.5.4 Full GC 的触发机制 8.6 堆空间的分代思想 为什么需要把 Java 堆分代? 8.7 内存分配策略(对象提升规则) 8.8 为对象分配内存:TLAB 8.8.1 为什么有 TLAB( Local ) 8.8.2 什么是 TLAB 8.8.3 对象分配过程(加上 TLAB)

8.9 小结堆空间的参数设置 8.9.1 常见的参数设置

8.9.2 空间分配担保

8.10 堆是分配对象的唯一选择吗? 逃逸分析概述

public class EscapeAnalysis {// 如何快速判断是否发生逃逸:大家就看 new 的对象实体是否有可能在方法外被使用。public EscapeAnalysis obj;// 方法返回 EscapeAnalysis 对象,发生逃逸。public EscapeAnalysis getInstance(){return obj == null ? new EscapeAnalysis() : obj;}// 为成员属性赋值,发生逃逸public void setObj(){obj = new EscapeAnalysis();}// 对象的作用域仅在当前方法内有效,没有发生逃逸public void useEscapeAnalysis(){EscapeAnalysis e = new EscapeAnalysis();}// 引用成员变量的值,发生逃逸。因为判断的是对象实体能否被方法外调用,对象实体才是放在堆空间中的。变量 e 对应的对象实体可以通过 obj 在方法外被调用public void useEscapeAnalysis1(){EscapeAnalysis e = getInstance();}
}

逃逸分析:代码优化 代码优化之栈上分配 代码优化之同步省略(锁消除) 代码优化之标量替换 逃逸分析小结:逃逸分析并不成熟

九、运行时数据区 – 方法区 9.1 栈、堆、方法区的交互关系 9.1.1 运行时数据区结构图

9.1.2 栈、堆、方法区交互关系

9.2 方法区的理解 9.2.1 方法区在哪里 9.2.2 方法区的基本理解 9.2.3 中方法区的演进 9.3 设置方法区大小 9.3.1 设置方法区内存的大小 JDK7 及以前 JDK8 及以后 9.4 方法区的内部结构 9.4.1 方法区中存储什么 类型信息

域(Field、成员变量、属性)信息 方法信息

non-final 的静态变量 全局常量: final 9.4.2 运行时常量池 和 常量池 为什么需要常量池 运行时常量池 9.5 方法区的演进细节 方法区的演进细节 为什么要用元空间替换永久代? 为什么要调整? 9.6 方法区的垃圾回收 废弃常量的回收 不再使用的类型的回收

9.7 总结

十、对象的实例化、内存布局与访问定位 10.1 对象的实例化

10.2 对象的内存布局

示例

public class CustomerTest {public static void main(String[] args) {Customer cust = new Customer();}
}
class Customer{int id = 1001;String name;Account acct;{name = "匿名用户";}public Customer(){acct = new Account();}
}
class Account{}

10.3 对象的访问定位 10.3.1 JVM 是如何通过栈帧中的对象引用访问到其内部的对象实例的呢?

10.3.2 对象访问的方式 句柄访问

直接指针( 采用)

十一、直接内存 十二、执行引擎( ) 12.1 执行引擎概述 示图

执行引擎的工作过程

12.2 Java 代码编译和执行过程 前端编译与后端编译 示图

什么是解释器,什么是 JIT 编译器? 为什么说 Java 是半编译半解释型语言? 12.3 机器码、指令、汇编语言 12.3.1 机器码

12.3.2 指令和指令集

12.3.3 汇编语言

12.3.4 高级语言

12.3.5 图示

12.3.5 字节码

12.4 解释器 解释器的工作任务 解释器的分类 现状 12.5 JIT 编译器 12.5.1 问题 12.5.2 JVM 的执行方式 12.5.3 概念解释 热点代码及探测方式 热点代码及探测方式 方法调用计数器 热度衰减

回边计数器 VM 可以设置程序执行方式 VM 中 JIT 的分类 C1 和 C2 编译器不同的优化策略

总结 12.6 Graal 编译器和 AOT 编译器 12.6.1 Graal 编译器 12.6.2 AOT 编译器 十三、 Table 13.1 的基本特性 为什么 jdk9 及之后修改为 byte 数组?

13.2 的内存分配 字符串常量池在内存中位置的变化

为什么要调整 的位置从永久代到堆 默认比较小,放大量的字符串可能导致永久代报 OOM永久代的垃圾回收频率很低 13.4 字符串拼接操作 结论 常量与常量的拼接结果在常量池,原理是编译期优化常量池中不会存在相同内容的常量。只要其中有一个是变量,结果就在堆中(不是常量池的区域)。变量拼接的原理是 如果拼接的结果调用 () 方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址。当通过语句 str.() 调用 () 方法后,JVM 就会在常量池中查找是否存在与 str 等值的 ,若存在则直接返回常量池中相应 的地址;若不存在,则会在常量池中创建一个等值的,然后返回这个 在常量池中的地址。 经典面试题

public class StringTest {// 常量与常量的拼接public void test1(){// 在生成字节码文件的时候,就直接将 "a" + "b" + "c" 等同于 "abc"(可查看字节码文件,或者查看反编译的结果)String s1 = "a" + "b" + "c"; // 等同于 "abc"String s2 = "abc"; // "abc" 一定是放在字符串常量池中的,然后将其地址赋给 s2System.out.println(s1 == s2); // trueSystem.out.println(s1.equals(s2)); // true}// "+" 两边至少有一个是变量public void test2(){String s1 = "a";String s2 = "b";String s3 = "ab";/*如下的 s1 + s2 细节:(变量 s 是我临时定义的,底层没说变量叫 s)(1) StringBuilder s = new StringBuilder()(2) s.append("a")(3) s.append("b")(4) s.toString()  --> 约等于 new String("ab"),但是字符串常量池中并不会存在 "ab"(通过 StringBuilder 中的 toString() 的字节码可知)补充:jdk5 及之后使用的是 StringBuilder,jdk5 之前使用的是 StringBuffer*/String s4 = s1 + s2; // s4 变量记录的地址为:new String("ab"),但是字符串常量池中并不会存在 "ab"System.out.println(s3 == s4); // false}@Testpublic void test3(){String s1 = "javaEE";String s2 = "hadoop";String s3 = "javaEEhadoop";String s4 = "javaEE" + "hadoop";// 如果拼接符号的前后出现了变量,则相当于在堆空间中 new String(),具体的内容为拼接后的结果:javaEEhadoopString s5 = s1 + "hadoop";String s6 = "javaEE" + s2;String s7 = s1 + s2;System.out.println(s3 == s4); // trueSystem.out.println(s3 == s5); // falseSystem.out.println(s3 == s6); // falseSystem.out.println(s3 == s7); // falseSystem.out.println(s5 == s6); // falseSystem.out.println(s5 == s7); // falseSystem.out.println(s6 == s7); // false/* intern():判断字符串常量池中是否存在 javaEEhadoop 值,如果存在,则返回常量池中 javaEEhadoop 的地址;如果不存在,则在常量池中加载一份 javaEEhadoop,并返回加载的 javaEEhadoop 在常量池中的地址 */String s8 = s6.intern();System.out.println(s3 == s8); // true}// 常量与常量的拼接public void test4(){final String s1 = "a";final String s2 = "b";String s3 = "ab";// 下面的字符串拼接操作仍然是编译期优化,可以看成是两个常量相加(final 修饰),而不是使用 StringBuilder 的方式String s4 = s1 + s2; // 因为加了 final 修饰,所以 s1 和 s2 就是常量System.out.println(s3 == s4); // true}
}

() 方式和 “+” 方式的效率对比

public class StringTest {/*** 执行效率:使用 StringBuilder 的 append() 方式添加字符串的效率要远高于使用 String 的字符串拼接方式* 为什么:*     StringBuilder 的 append() 方式,自始至终只创建过一个 StringBuilder 对象*     使用 String 的字符串拼接方式:创建了许多 StringBuilder 对象和 String 对象,占用内存过大;如果进行 GC,还要花费额外的时间** StringBuilder 的改进:*     StringBuilder s = new StringBuilder() 的方式,value 数组的默认大小是 16。如果字符串长度过大则需要对数组进行扩容。*     因此如果可以确定前前后后添加的字符串长度不高于某个最大值 highLevel,则可以使用 StringBuilder s = new StringBuilder(highLevel);*/@Testpublic void test5(){long start = System.currentTimeMillis();// method1(100000); // 6092 msmethod2(100000); // 9 mslong end = System.currentTimeMillis();System.out.println(end - start);}public void method1(int highLevel){String s = "";for(int i = 0; i < highLevel; i++){s = s + "a"; // 每次循环都会创建一个 StringBuilder 和 String (调用 StringBuilder 的 toString() 方法时会 new String)}}public void method2(int highLevel){StringBuilder s = new StringBuilder();for(int i = 0; i < highLevel; i++){s.append("a");}}
}

13.5 () 的使用 经典面试题 (1)new (“ab”) 会创建多少个对象

public class StringTest {/***  new String("ab") 会创建多少个对象*    看字节码文件,就知道是两个:*      (1)一个对象是:new 关键字在堆空间创建的*      (2)另一个对象是:字符串常量池中的对象 "ab"。字节码指令:ldc*  其实严谨点也可能是一个,因为有可能在 new String("ab") 之前字符串常量池中已经有 "ab",此时就直接用已有的 "ab"*/public void test6(){String s = new String("ab");}
}

字节码文件:

(2)new (“a”) + new (“b”) 会创建多少个对象

public class StringTest {/***  new String("a") + new String("b") 会创建多少个对象:*      (1)new StringBuilder()*      (2)new String("a")*      (3)常量池中的 "a"*      (4)new String("b")*      (5)常量池中的 "b"*  深入剖析:StringBuilder 的 toString():*      (6)new String("ab")*      注意:toString() 的调用,在字符串常量池中,并没有生成 "ab"*/public void test6(){String s = new String("a") + new String("b");}
}

字节码文件:

(3)()

public class StringTest1 {public static void main(String[] args) {String s1 = new String("1");s1.intern(); // 调用此方法之前,字符串常量池中已经存在了 "1"String s2 = "1";System.out.println(s1 == s2); // jdk6:false  jdk7/8:falseString s3 = new String("1") + new String("1"); // s3 变量记录的地址为:new String("11")// 执行完上一行代码以后,字符串常量池中不存在 "11"s3.intern(); // 在字符串常量池中生成 "11"。如何理解:jdk6:在常量池中创建了一个新的对象 "11";// jdk7/8:常量池中并没有创建 "11",而是创建了一个指向堆空间中 new String("11") 的地址String s4 = "11";System.out.println(s3 == s4); // jdk6:false  jdk7/8:true}
}

public class StringTest2 {public static void main(String[] args) {String s = new String("a") + new String("b"); // s 变量记录的地址为:new String("ab"),但是字符串常量池中并不存在 "ab"String s2 = s.intern(); // jdk6中:在字符串常量池中创建一份 "ab"// jdk7/8 中:字符串常量池中并没有创建 "ab",而是创建了一个引用,指向 new String("ab"),并返回该引用地址System.out.println(s2 == "ab"); // jdk6:true  jdk7/8:trueSystem.out.println(s == "ab"); // jdk6:false  jdk7/8:true}
}

() 的空间效率 13.6 G1 中的 去重操作 十四、垃圾回收概述 14.1 什么是垃圾 14.2 为什么需要 GC 14.3 早期垃圾回收

14.4 Java 垃圾回收机制 担忧

十五、垃圾回收的相关算法 垃圾标记阶段:对象存活判断 垃圾清除阶段 15.1 标记阶段:引用计数算法 循环引用 15.2 标记阶段:可达性分析算法 具体思路 GC Roots 注意事项 15.3 对象的 机制 判断一个对象是否可回收的具体过程 15.4 清除阶段:标记-清除算法 15.4.1 背景 15.4.2 执行过程 15.4.3 缺点 15.4.4 何为清除 15.5 清除阶段:复制算法 15.5.1 背景

15.5.2 核心思想 15.5.3 优缺点 15.5.4 应用场景 15.6 清除阶段:标记-压缩算法 15.6.1 背景 15.6.2 执行过程 15.6.3 优缺点 15.7 小结

15.8 分代收集算法 15.9 增量收集算法、分区算法 15.9.1 增量收集算法 15.9.2 分区算法 十六、垃圾回收相关概念 16.1 .gc() 的理解

public class SystemGCTest {public static void main(String[] args) {new SystemGCTest();System.gc(); // 提醒 JVM 的垃圾回收器执行 GC,但是不保证会马上执行System.runFinalization(); // 强制调用失去引用的对象的 finalize() 方法}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("重写了 finalize() 方法");}
}

public class LocalVarGC {public void localvarGC1(){byte[] buffer = new byte[10 * 1024 * 1024]; // 10MBSystem.gc(); // buffer 不会被回收掉空间}public void localvarGC2(){byte[] buffer = new byte[10 * 1024 * 1024];buffer = null;System.gc(); // buffer 会被回收掉空间}public void localvarGC3(){{byte[] buffer = new byte[10 * 1024 * 1024];}System.gc(); // buffer 不会被回收掉空间。在 gc 时,buffer 还占用着局部变量表中下标为 1 的位置,因此回收不了}public void localvarGC4(){{byte[] buffer = new byte[10 * 1024 * 1024];}int value = 1;System.gc(); // buffer 会被回收掉空间。由于 buffer 过了作用域,导致 value 会复用局部变量表中索引为 1 的位置(slot 重用),导致 buffer 这个引用就不存在了}public void localvarGC5(){localvarGC1();System.gc(); // 会回收掉 localvarGC1() 中的 buffer 的空间}public static void main(String[] args) {LocalVarGC local = new LocalVarGC();local.localvarGC5();}}

16.2 内存溢出与内存泄漏 16.2.1 内存溢出(OOM) 16.2.2 内存泄漏 内存泄露举例: 16.3 Stop the world 16.4 垃圾回收的并发与并行 16.4.1 并发() 16.4.2 并行() 16.4.3 并发和并行的对比

16.4.4 垃圾回收的并发与并行 16.5 安全点与安全区域 16.5.1 安全点() 16.5.2 安全区域(Safe ) 再谈引用

16.6 再谈引用:强引用 16.7 再谈引用:软引用 16.8 再谈引用:弱引用 16.9 再谈引用:虚引用 16.10 再谈引用:终结器引用 十七、垃圾回收器 17.1 GC 分类与性能指标 17.1.1 GC 分类 17.1.2 评价 GC 的性能指标 17.2 不同的垃圾回收器概述 17.2.1 垃圾收集器发展史

17.2.2 七款经典的垃圾收集器 17.2.3 七款经典收集器与垃圾分代之间的关系

17.2.4 垃圾收集器的组合关系 17.2.5 如何查看默认的垃圾收集器 17.3 回收器:串行回收 17.4 回收器:并行回收 17.5 回收器:吞吐量优先 参数配置 17.6 CMS 回收器:低延时 CMS 的工作原理

CMS 的优缺点总结 CMS 收集器的参数设置

JDK 后续版本中 CMS 的变化

小结 17.7 G1 回收器:区域化分代式 17.7.1 G1 回收器的优势 17.7.2 G1 回收器的劣势 17.7.3 G1 回收器的参数设置

17.7.4 G1 回收器的常见操作步骤 17.7.5 G1 回收器的适用场景 17.7.6 介绍 17.7.7 记忆集( Set) 17.7.8 G1 垃圾回收过程 17.7.9 G1 回收器优化建议 17.8 垃圾回收器总结 怎么选择垃圾回收器

17.9 GC 日志分析 GC 日志说明

17.10 垃圾回收器的新发展 的 GC

革命性的 ZGC 其它厂商的垃圾回收器

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了