Java内存管理
Java内存管理
为什么要学习 Java 内存管理?
我们都知道,Java 本身会管理内存,无需程序员明确干预。 垃圾回收器本身可确保清理未使用的空间,并在不需要时释放内存。 那么,程序员的作用是什么?为什么程序员需要学习 Java 内存管理? 作为一名程序员,你不需要为销毁对象等问题费心,这一切都要归功于垃圾回收器。 不过,自动垃圾回收并不能保证万无一失。 如果我们不知道内存管理是如何工作的,那么我们往往会遇到 JVM(Java 虚拟机)无法管理的情况。 因此,了解内存管理至关重要,因为这将有利于程序员编写不会崩溃的高性能程序,或者即使崩溃了,程序员也知道如何调试或克服崩溃。
一、Java内存结构
1.1 Java 内存结构:
JVM 定义了在程序执行过程中使用的各种运行时数据区。 其中一些区域由 JVM 创建,另一些则由程序中使用的线程创建。 不过,JVM 创建的内存区域只有在 JVM 退出时才会销毁。 线程的数据区在实例化过程中创建,并在线程退出时销毁。
1.1.1 堆 Heap:
它是共享的运行时数据区域,在内存中存储实际对象。 它在虚拟机启动时实例化,为所有类实例和数组分配内存。
- 堆的大小可以是固定的,也可以是动态的,这取决于系统配置。
- JVM 允许用户根据需要初始化或改变堆的大小。 使用 new 关键字时,对象会在堆中分配一个空间,但同一对象的引用会存在于堆栈中。
- 运行中的 JVM 进程有且仅有一个堆。
Scanner sc = new Scanner(System.in);
上述语句创建了Scanner对象,并将其分配到堆中,同时将引用“sc”推入堆栈。
- 堆区域的垃圾收集是强制性的
1.1.2方法区 Method:
它是堆区的逻辑部分,在虚拟机JVM启动时创建,为类结构、方法数据和构造函数字段数据以及类中使用的接口或特殊方法分配内存。堆的大熊啊可以是固定的,也可以是动态的,具体取决与系统配置。堆的大小可以是固定的,也可以根据计算需要进行拓展,无需连接。
注意:虽然方法区在逻辑上是堆的一部分,但即使堆区强制进行垃圾回收,方法区也可能不进行垃圾回收。
1.1.3JVM堆栈
创建线程时同时创建一个堆栈,用于存储数据和部分结果,这些数据和结果在方法返回值和执行动态链接时都需要。 堆栈的大小可以是固定的,也可以是动态的。 栈的大小可在创建时单独选择。 栈的内存无需连续。 本地方法栈:也称为 C 栈,本地方法栈不是用 Java 语言编写的。 该内存在创建时为每个线程分配。 它可以是固定的,也可以是动态的。
1.1.4程序计数器(PC)寄存器:
执行特定方法任务的每个 JVM 线程都有一个与之相关的程序计数器寄存器。 非本地方法的 PC 寄存器存储可用 JVM 指令的地址,而在本地方法中,程序计数器的值是未定义的。 在某些特定平台上,PC 寄存器可以存储返回地址或本地指针。
二、垃圾收集器工作原理
- JVM 会触发这一过程,并根据 JVM 的要求完成或停止垃圾回收过程。 它通过自动执行内存分配或取消分配,减轻了程序员的负担。
- 垃圾回收过程会导致其他进程或线程暂停,因此成本很高。 客户无法接受这个问题,但可以通过应用几种基于垃圾收集器的算法来解决。 应用算法的过程通常被称为垃圾收集器调整,对于提高程序性能非常重要。
- 另一种解决方案是代际垃圾收集器,它为分配给内存的对象添加一个年龄字段。 随着创建的对象越来越多,垃圾列表也随之增加,从而增加了垃圾收集时间。 根据对象存活的时钟周期,对象会被分组并分配相应的 "年龄"。
- 在当前情况下,所有垃圾收集器都是一代式的,因此也是最佳的。
注:System.gc()
和 Runtime.gc()
是向 JVM 明确请求垃圾收集的方法,但并不能确保垃圾收集,因为垃圾收集的最终决定权在 JVM 手中。
三、Java中的内存泄露问题
虽然Java拥有垃圾回收机制,但同样会出现内存泄露问题,比如下面提到的几种情况:
-
诸如 HashMap、Vector 等集合类的静态使用最容易出现内存泄露,因为这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。
private static Vector v = new Vector(); public void test(Vector v){ for (int i = 1; i<100; i++) { Object o = new Object(); v.add(o); o = null; } }
在这个例子中,虚拟机栈中保存者 Vector 对象的引用 v 和 Object 对象的引用 o 。在 for 循环中,我们不断的生成新的对象,然后将其添加到 Vector 对象中,之后将 o 引用置空。
问题是虽然我们将 o 引用置空,但当发生垃圾回收时,我们创建的 Object 对象也不能够被回收。因为垃圾回收在跟踪代码栈中的引用时会发现 v 引用,而继续往下跟踪就会发现 v 引用指向的内存空间中又存在指向 Object 对象的引用。也就是说,尽管o 引用已经被置空,但是 Object 对象仍然存在其他的引用,是可以被访问到的,所以 GC 无法将其释放掉。如果在此循环之后, Object 对象对程序已经没有任何作用,那么我们就认为此 Java 程序发生了内存泄漏。