当前位置:Java -> Java中的内存管理:介绍
在Java中,内存管理是由Java虚拟机(JVM)自动管理的一个过程,不需要显式干预。作为一个块结构语言,Java使用的内存模型将内存分为两种主要类型:栈和堆。
本地变量和方法参数基于“栈”使用内存。当进入或退出代码块或方法时,内存区域会自动增长和收缩。在请求系统分配一定数量的内存,其大小只在运行时或创建对象时才能确定时,通常会使用进程内存的一部分,称为“动态内存”或“堆”来满足这些请求。严格来说,有时本应分配到堆中的对象会被写入栈中;然而,我们将在后续文件中讨论该问题。
这两个内存区域如下图所示:
Figure 1. JVM Stack and Heap Memory Areas
* 方法参数和本地变量所在地
** 对象所在地
注:程序中的所有线程都有自己的栈,但共享一个堆。线程还可以有自己的小堆缓冲区,称为线程本地分配缓冲区(TLAB)。
堆动态内存的问题在于,程序在结束使用内存后必须释放内存。如果不释放,进程的大小会不断增长,直到达到没有更多内存资源可用的程度。为了解决这个问题,在堆内存使用已达到一定程度时,且程序不再需要某个对象时,Java中的对象是由一组执行任务的线程回收内存,这个过程称为“垃圾回收”。
简而言之,程序员在Java中不需要担心释放内存。
太好了!那我为什么要学习它呢?
虽然这一过程在Java中是自动的,但并不能保证系统的最佳性能。通过了解Java中的内存管理过程,您可以更加关注JVM并调整对象创建的方法,以减少对JVM和垃圾收集器的负载,从而获得轻微的性能提升。
除了垃圾收集之外,理解Java内存管理的一部分是把握“对象分配”的过程。因此,本文旨在探讨这一过程并提供对象分配的类比。了解这一过程及其在内存管理中的作用,可以更好地分析对象分配可能对系统性能造成的影响。
在Java中,使用引用来访问对象,引用是保存对象属性内存区域的“地址”的变量。这段内存是分配给堆区域的。当声明一个字段或本地变量时,这只是对堆上对象的引用,而不是对象本身。在创建对象时,从堆中请求所需量的内存,然后可以通过引用变量访问该对象。
需要注意的是,只要一个对象是“可达”的,它就是“活的”,垃圾收集器就不能销毁它。当一个对象不再“可达”时,垃圾收集器就可将其内存回收到堆中。在此需要澄清的概念是“可达性”。对象的引用必须被“根集”中的某些东西所引用,如图2所示。根集包括当前所有线程栈上的所有本地变量和方法参数。如果两个对象互相引用但根集中无法到达它们,它们将被垃圾回收。
Figure 2. Object Reachability
对象创建和销毁之间的时间被称为“对象生命周期”。对于“短寿命”对象,它们保留在堆内存的一个部分,这部分堆内存被称为“幼儿园”;它也被称为“年轻空间”或“伊甸园”。而对于生存周期更长的对象,通常会被移动到堆的另一个部分,称为“存放区”(即“老年空间”),以便释放出幼儿园用于分配新对象。值得一提的是,在大多数程序中,大部分创建的对象都是短寿命的;换句话说,它们被创建并很快就释放掉,因此永远不会到达存放区。无论如何,垃圾收集通常发生在需要释放更多内存时。幼儿园是分配更多对象的地方。这些对象通常是短寿命的,并且由于它通常是一个较小的区域,清理幼儿园比清理存放区要快得多。
重要的是要将目标定在要么是具有比幼儿园收集之间时间更短的生命周期的短寿命对象,要么在存放区中生存整个程序的生命周期,因为这样可以减少对整个应用程序的开销。如果对象分配非常少,并且堆的大小适合系统,垃圾收集发生的频率非常低,几乎不会影响应用程序的运行,这是我们在Chronicle中的目标。有关不同内存区域及其中存储对象的更多详细信息,请参阅此处。
图3. Java虚拟机堆内的幼儿园和存放区
简而言之,当字段和局部变量在类中声明时,只会为引用分配足够的内存。引用通常每个对象分配4或8字节,而与对象本身的大小无关,对象的大小是在堆的其他位置分配的。对象分配是使用“new”运算符时分配对象内存的过程。
还不清楚?让我们来看一个类比。
要说明对象分配,让我们想象一组架子,你想把咖啡杯放在上面:
图1. 整齐摆放的咖啡杯,代表系统中的对象
每个咖啡杯代表系统中的一个对象,而整理架子的人则可以代表JVM管理内存。类似于计算机内存有限空间,架子的容量也有限,无法容纳过多的咖啡杯。
当你决定要在架子上放一个新的咖啡杯时,首先需要确保架子上有足够的空间容纳这个咖啡杯。如果没有空间,你就无法进行下一步。如果有空间,你就寻找第一个足够大的空间,并且每次都从同一个起点开始,也许总是从最底层的架子开始。不再需要存放的咖啡杯将被收集起来以便重复利用(移除)。这样可以为将来可能想要存放在架子上的咖啡杯释放空间。为了进一步提高效率,我们可以重新整理剩余的咖啡杯,让架子底部再次有空间准备存放未来的咖啡杯。
同样的,在Java中创建对象时,JVM必须先确定是否有内存空间(“架子空间”)可以存储它。如果有空间,对象分配可以继续,因为有可分配给它的内存。为了确保有足够的内存,当JVM认为内存变得更加稀缺时,垃圾收集器将运行,类似于当我们注意到架子上空间不多时的情况。垃圾收集器将重新整理幼儿园的分配内存,使幼儿园的开始位置有空间。垃圾收集器更智能的功能之一是它知道自己检查并找到仍在使用的对象的次数。当此数字超过一定阈值时,对象将被移动到堆的另一个部分:存放区。在我们的架子类比中,这就像将长时间存放的咖啡杯移动到架子的更上面,从而释放出底部的空间以存放新的咖啡杯。进行这种内存管理的原因是为了通过只扫描幼儿园(仅扫描底部架子)而不是整个堆来释放足够的内存空间。有时,垃圾收集器必须查看并对存放区进行收集
本文介绍了Java中的内存管理,以及关于对象分配的简单类比和为什么考虑这一点的重要性。使JVM能够高效地管理动态内存对于确保系统性能最佳是至关重要的。
推荐阅读: 36.jdk1.6为什么要对synchronized进行优化?做了哪些优化?
本文链接: Java中的内存管理:介绍