前言
深入研究Java内存管理,将增强你对堆如何工作、引用类型和垃圾回收的认识。
你可能会思考,如果你使用Java编程,关于内存如何工作你需要了解哪些哪些信息?Java可以进行自动内存管理,而且有一个很好的、安静的垃圾回收器,它在后台工作,清理那些未使用的对象并释放一些内存。
因此,作为一名Java程序员,你不需要再为销毁无用对象这样的问题而烦恼了。但是,虽然这个过程在Java中是自动的,它也不能保证任何事情。由于不知道垃圾回收器和Java内存是如何设计的,有些对象即使你不再使用了,却也不符合垃圾回收的条件。
因此,了解Java中内存实际是如何工作的非常重要,因为它为你编写高性能和优化的应用程序提供了帮助,这些应用程序永远不会因内存不足而崩溃。另一方面,当你发现自己处于糟糕的境地时,你将能够很快发现内存的漏洞。
首先,让我们看看内存在Java中通常是如何组织的:
通常,内存分为两大部分:堆栈和堆。请记住,内存类型在上图中的大小与实际内存大小不成比例。与堆栈相比,堆是一个巨大数量的内存。
堆栈
堆栈内存负责保存对堆对象的引用和存储值类型(在Java中也称为基元类型),值类型保存值本身而不保存对堆中对象的引用。
此外,堆栈上的变量具有一定的可见性,也称为作用域。只有活跃作用域内的对象才能被使用。例如,假设我们没有任何全局作用域变量(字段),只有局部变量,如果编译器执行方法的主体,它只能访问方法主体内堆栈中的对象。它不能访问其它局部变量,因为这些变量超出了作用域。一旦方法完成并返回,堆栈顶部就会溢出,活跃作用域也会发生变化。
或许你注意到了在上图中显示的多个堆栈内存,这是因为Java中的堆栈内存是按线程分配的。因此,每次一个线程被创建和启动时,它都有自己的堆栈内存,并且不能访问另一个线程的堆栈内存。
堆
堆内存将实际对象存储在内存中。这些对象被堆栈中的变量引用。例如,让我们分析下面一行代码发生了什么:
StringBuilder builder = new StringBuilder();
“new”关键字负责确保堆上有足够的可用空间,在内存中创建一个StringBuilder类型的对象,并通过堆栈中的“builder”引用它。
每个正在运行的JVM进程只有一个堆内存。因此,无论运行多少线程,这都是内存中的一个共享部分。实际上,堆结构与上图中显示的略有不同。堆本身被分成几个部分,这有助于垃圾回收进程。
大堆栈和堆大小都没有预定义 - 这取决于正在运行的计算机。 然而,在后文中,我们将研究一些JVM配置,这些配置允许我们为正在运行的应用程序明确设定它们的大小
引用类型
如果仔细观察内存结构图片,你或许会注意到,代表对堆中对象引用的箭头的样式实际是不同的。这是因为,在Java编程语言中,我们有不同类型的引用:强引用、弱引用、软引用和虚引用。引用类型之间的区别在于它们所引用堆上的对象在不同的条件下可以被作为垃圾回收。让我们来仔细认识一下每一种引用类型。
1. 强引用>>>
这种引用类型是我们都习惯并且最受欢迎的引用类型。在上面的StringBuilder示例中,我们实际上使用了对堆中对象的强引用。当有一个强引用指向堆上的对象时,或者通过一系列强引用可以强访问该对象,则该对象不会被作为垃圾回收。
2. 弱引用>>
简单来说,在下一个垃圾回收进程之后,对堆中对象的弱引用很可能不会继续存在了。弱引用的创建示例如下:
WeakReferencereference = new WeakReference<>(new StringBuilder());
弱引用的一个很好的用例是缓存方案。假设你检索了一些数据,并且还希望将其存储在内存中—这样同样的数据可以被再次请求。另一方面,你不确定何时或者是否会再次请求这些数据。因此,你可以保留对它的弱引用,万一垃圾回收器运行,它可能会破坏堆中的对象。因此,过了一会儿,如果你想要检索你引用的对象,你可能会突然得到一个空的返回值。缓存方案的一个很好的使用是回收WeakHashMap。如果我们在Java API中打开WeakHashMap类,我们会看到它的条目实际上扩展了WeakReference类,并使用它的引用字段作为映射的关键字:
private static class Entryextends WeakReference