JVM 内存大致分为 线程私有区域 和 线程共享区域
虚拟机栈、本地方法栈和程序计数器,这三个区域是线程私有的。
虚拟机栈用于描述Java方法执行的过程。每一个方法在执行的过程中会创建一个栈帧。栈帧中包括局部变量、操作数栈、动态链接和方法出口等信息。当方法执行完成后,对应的栈帧就会弹栈。虚拟机栈的内存分配是确定的,所以不需要特别关注虚拟机栈及其他线程私有区域的内存使用情况。
方法区和堆是线程共享区域。方法区和堆主要存放对象、数组等不确定数量的数据。在方法执行的过程中创建的对象数量是不确定的。所以需要合理的内存管理机制来管理这两个区域。方法区和堆内存是垃圾回收器进行垃圾回收的主要区域。
内存中的垃圾
在程序运行过程中,会创建对象,但是当方法执行完成或对象不再使用时,它们被定义为"垃圾"。此时,需要通过垃圾回收器来清理这些内存区域。为了将"垃圾"量化成计算机语言,我们需要设计一套算法供垃圾回收器使用。垃圾回收的动作是由垃圾回收器自动运行和判定的。
判断一个对象是否为垃圾
常见的算法有两种:引用计数算法和根搜索算法。
引用计数算法(Reference Counting Collector)
在该算法中,每个对象都会有一个引用计数器。当对象被引用时,计数器会增加;当引用失效时,计数器会减少。当计数器为零时,意味着对象不再被引用,可以进行回收。
这种算法简单且高效,判定过程相对容易。然而,它存在一个致命的缺点,即无法处理循环引用的情况。当两个对象相互引用时,它们的计数器永远不会变为零,导致内存泄漏。因此,引用计数算法在早期的 JVM 中使用较多,但现在已经较少使用。
根搜索算法(Tracing Collector)
根搜索算法的核心思想,就是从某一些指定的对象出发,寻找与该根对象具有引用关系的对象,然后再从这些对象开始继续寻找,形成一个个的引用链。不在引用链上的对象被标记为引用不可达对象。
那么哪些对象可以叫做根对象呢?
- 虚拟机栈中引用的对象
- 方法区中常量引用的对象
- 方法区中静态属性引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
- 活跃线程
上述算法只是一个算法的中心思想,实际执行过程要比这个复杂的。另外,GC判断对象是否可达看的还是强引用。
-
进行根搜索的时候,是需要暂停所有线程的,即执行一次 STW(Stop The World),最主要的目的是防止上述的对象图在算法运行的过程中有变化从而影响算法的准确性。
-
线程暂停的时间长短,取决于对象的多少,和堆内存的大小无关。
-
宣告一个对象的“死亡”其实不仅仅通过上述的算法计算,而是需要经历两次的标记,本文暂不进行赘述。