一、序言
写过 java 的小伙伴肯定都知道 JVM 分为年轻代、永久代,但为什么这么分,有没有什么理论根本呢?
二、JVM 分代## 2.1 JVM 中工具生命周期分布 经由对大量实际运用程序的不雅观察和剖析,得出如下图:
图片.png

x 轴表示工具在生命周期内,分配的字节数。 y 轴上表示相应生命周期内的工具占用的总字节数。左边的尖峰代表分配后不久可以回收的工具。蓝色区域表示工具生命周期的范例分布。以是可以得出如下结论: 1. 大多数工具的生命周期很短暂:在许多运用程序中,绝大多数工具的生命周期都非常短暂,即它们很快就会变成垃圾,被回收掉 1. 少数工具的生命周期较长:虽然大部分工具很快就会被回收,但也有一小部分工具的生命周期比较长,它们可能会存活很永劫光
为了优化上述的两点,在内存管理方面,JVM按代进行管理,利用内存池保存不同年事的工具。每当某一代充满时,会触发垃圾网络操作。
大多数工具被分配到年轻代,个中绝大多数工具终极在这一代消亡。当年轻代达到容量时,会触发垃圾网络,只针对年轻代进行垃圾回收;而其他代中的垃圾暂不处理。在每次次要网络时,一部分幸存工具会被移至永久代。永久代终极也会填满,须要对全体堆进行回收,持续的韶光常日年轻代长,由于牵扯的工具数量更多。
2.2 JVM 分代图JVM默认的分代图,须要把稳的是打消:Parallel Collector and G1回收算法
图片.png
在初始化时,虚拟地保留最大地址空间,但除非须要,否则不会分配给物理内存。为工具内存保留的完全地址空间可以分为年轻代和终生代。年轻代由 eden 和两个 survivor 空间组成。大多数工具最初是在 eden 等分配的。一个survivor 空间在任何时候都是空的,并且作为 eden 中任何存活工具的目的地;另一个 survivor 空间是下一次复制网络期间的目的地。工具以这种办法在幸存者空间之间复制,直到它们足够老而可以被保留(复制到终生代)。2.3 分外情形G1网络器内存分代不再明显的分代观点:与传统的分代网络器不同,G1网络器没有严格的年轻代和老年代的划分。它将全体堆分为多个大小相等的区域(Region),并根据垃圾网络的活动动态地划分为年轻代和老年代区域。独特的回收方法:在G1中,每个区域都可以用作年轻代或老年代,因此没有严格意义上的固定分代。它会选择多个区域进行垃圾网络,并利用一种叫做“垃圾优先”(Garbage First)算法来进行整体的堆回收。Parallel Collector 分代在Parallel Collector中,与其他经典的垃圾网络器(如Serial网络器和CMS网络器)不同,它在新生代的设计中没有显式地利用Eden区和Survivor区的划分。而是将新生代划分为一部分专门用于存放工具的区域,这使得Parallel Collector更看重全体新生代的高效垃圾回收
三、GC测试3.1 查看 JVM 模型配置java -XX:+PrintCommandLineFlags -version
我这台机器的默认配置如下:
-XX:InitialHeapSize=264819584 -XX:MaxHeapSize=4237113344 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGCopenjdk version "1.8.0_332"OpenJDK Runtime Environment (Temurin)(build 1.8.0_332-b09)OpenJDK 64-Bit Server VM (Temurin)(build 25.332-b09, mixed mode)
阐明如下:
- `-XX:InitialHeapSize=264819584`:设置 JVM 初始堆内存大小为大约 252MB。这是 JVM 启动时分配的最小堆内存量。- `-XX:MaxHeapSize=4237113344`:设置 JVM 最大堆内存大小为大约 4040MB(即约 3.94GB)。这是 JVM 堆内存能够增长到的最大限度。在运行过程中,如果堆内存需求增加,JVM 堆大小可以动态增长直至此上限。- `-XX:+PrintCommandLineFlags`:指示 JVM 在启动时打印出所有的命令行标志(参数),这有助于调试和记录当前虚拟机的配置状态。- `-XX:+UseCompressedClassPointers`:启用类指针的压缩。在 64 位 JVM 上,这可以减少类元数据占用的内存空间,从而降落内存花费,并可能提升性能。- `-XX:+UseCompressedOops`:启用“普通工具指针(Ordinary Object Pointers)”的压缩。此设置减少了64位系统上工具引用的大小,从而减少了内存利用并且没有显著影响到性能。这是提升大内存Java运用性能的一项常用技能。- `-XX:-UseLargePagesIndividualAllocation`:禁止为每个大页面(Large Page)进行单独的分配。大页面技能常日用于提高大型运用的性能,通过减少页面表条款标数量来减少CPU缓存的压力,但须要操作系统的支持。这个参数指定了不该用单独分配大页的办法,这可能是由于配置了通用的大页支持或者没有需求利用该特性。- `-XX:+UseParallelGC`:启用 Parallel 垃圾网络器。Parallel 网络器是一个并行的新生代垃圾网络器,利用多线程来提高垃圾网络效率,紧张目标是增加运用程序的吞吐量。
3.2 代码测试public static void main(String[] args) {while (true){try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}ArrayList<Object> objects = new ArrayList<>(1024);for (int i = 0; i < 1024 10; i++) {objects.add(new Object[]{});}}}
不断的创建 List,为了更快的不雅观察到 GC 日志,我们设置
-Xmx80m -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:./gc.log
阐明如下:
- `-Xmx20m`:设置了 Java 堆的最大内存为 20MB。这个参数限定了 Java 程序运行时最大可用的堆内存大小,超过这个大小后 JVM 将会抛出 OutOfMemoryError 缺点。- `-verbose:gc`:启用了垃圾回收输出信息。当垃圾回收器实行垃圾回收时,会输出简要的垃圾网络情形,包括开始和结束的韶光点。- `-XX:+PrintGCDetails`:详细输出垃圾网络的信息。这个参数会打印出关于每次垃圾网络的详细信息,包括各个区域的利用情形、垃圾回收韶光、被网络工具等信息。- `-XX:+PrintGCDateStamps`:打印垃圾回收发生的日期韶光戳。此参数会在详细的垃圾网络日志中包含日期韶光信息,有助于更好地跟踪和剖析垃圾回收的情形。- `-Xloggc:./gc.log`:将垃圾网络日志输出到指定的文件中,这里指定为当前目录下的 `gc.log` 文件。通过这个参数,JVM 会将详细的垃圾网络日志记录到指定文件中,可以用于后续剖析和调试。
运行上述代码,可以看到 GC 日志如下:
OpenJDK 64-Bit Server VM (25.332-b09) for windows-amd64 JRE (1.8.0_332-b09), built on Apr 23 2022 01:25:28 by "jenkins" with MS VC++ 12.0 (VS2013)Memory: 4k page, physical 16551224k(667012k free), swap 52371688k(14504324k free)CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 2024-05-21T20:05:55.659+0800: 8.192: [GC (Allocation Failure) [PSYoungGen: 5632K->510K(6144K)] 5632K->1384K(19968K), 0.0024955 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 2024-05-21T20:06:15.807+0800: 28.340: [GC (Allocation Failure) [PSYoungGen: 6142K->488K(6144K)] 7016K->1369K(19968K), 0.0008134 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 2024-05-21T20:06:34.936+0800: 47.468: [GC (Allocation Failure) [PSYoungGen: 6120K->502K(6144K)] 7001K->1440K(19968K), 0.0009532 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 2024-05-21T20:06:55.174+0800: 67.706: [GC (Allocation Failure) [PSYoungGen: 6134K->504K(6144K)] 7072K->1449K(19968K), 0.0008710 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 2024-05-21T20:07:14.356+0800: 86.888: [GC (Allocation Failure) [PSYoungGen: 6136K->492K(6144K)] 7081K->1486K(19968K), 0.0007995 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 2024-05-21T20:07:33.536+0800: 106.068: [GC (Allocation Failure) [PSYoungGen: 6078K->486K(4096K)] 7072K->1496K(17920K), 0.0010000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 2024-05-21T20:07:46.621+0800: 119.153: [GC (Allocation Failure) [PSYoungGen: 4070K->160K(5120K)] 5080K->1501K(18944K), 0.0004745 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
3.3 GC 日志阐明我们一块一块的阐明:
OpenJDK 64-Bit Server VM (25.332-b09) for windows-amd64 JRE (1.8.0_332-b09), built on Apr 23 2022 01:25:28 by "jenkins" with MS VC++ 12.0 (VS2013)
这段 GC 日志供应了关于 JVM 的版本信息和构建详情,下面是对这部分日志的阐明:- OpenJDK 64-Bit Server VM (25.332-b09) for windows-amd64 JRE (1.8.0_332-b09) :这部分解释了 JVM 的详细信息。个中:- OpenJDK 64-Bit Server VM 表示这是一个64位的做事器端虚拟机。- 25.332-b09 是 JVM 的版本号,供应了关于编译版号的详细信息。- windows-amd64 表示 JVM 运行在 Windows 系统的 64 位架构上。- JRE (1.8.0_332-b09) 供应了 Java 运行时环境版本信息。- Built on Apr 23 2022 01:25:28 by "jenkins" with MS VC++ 12.0 (VS2013) :这部分供应了 JVM 构建的韶光和工具信息。- Built on Apr 23 2022 01:25:28 表示 JVM 是在 2022年4月23日凌晨01:25:28 构建的。- by "jenkins" 表示利用 Jenkins 自动化工具构建。- with MS VC++ 12.0 (VS2013) 表明利用 Microsoft Visual C++ 12.0 (VS2013) 编译器来构建 JVM。
Memory: 4k page, physical 16551224k(667012k free), swap 52371688k(14504324k free)
这段 GC 日志中供应了关于系统内存情形的信息,下面是对这段日志的阐明:- Memory: 4k page:指定系统内存页的大小为 4KB。这表示系统在管理内存时利用 4KB 作为一页的基本单位。这是操作系统中很常见的内存页大小。- physical 16551224k(667012k free) :表示系统的物理内存情形。在括号内的部分是详细数值,16551224k 表示系统的物理内存统共为 16,551,224 KB(约 16.5 GB),个中 667,012 KB(约 667 MB)是可用的空闲内存。系统内存中可能包括用于程序运行和缓存的内存等。- swap 52371688k(14504324k free) :给出了系统的交流空间(swap space)情形。在括号内的部分表示详细数值,52371688k 表示统共的交流空间为 52,371,688 KB(约 52.4 GB),个中有 14,504,324 KB(约 14.5 GB)是可用的空闲交流空间。交流空间是硬盘上用来扩展物理内存的一部分,当物理内存不敷时,操作系统会将部分数据从内存中交流到硬盘的交流分区(swap partition)中。
2024-05-21T20:07:46.621+0800: 119.153: [GC (Allocation Failure) [PSYoungGen: 4070K->160K(5120K)] 5080K->1501K(18944K), 0.0004745 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
这段 GC 日志供应了一次垃圾回收事宜的详细信息,下面是对这条日志的阐明:- 韶光戳信息:2024年5月21日,UTC+0800时区,详细韶光为20:07:46.621。这个韶光戳供应了垃圾回收事宜发生的韶光。- 持续韶光信息:早年次垃圾回收到本次垃圾回收经由了约119.153秒。- GC事宜:这次垃圾回收事宜是由“Allocation Failure”引起的,即由于在年轻代进行工具分配时失落败触发了垃圾回收。- PSYoungGen信息:涉及的内存区域为 PSYoungGen(Parallel Scavenge算法的年轻代)。详细变革如下:- 在垃圾回收前,年轻代中的内存从4070KB减少到160KB。- 年轻代的总容量为5120KB,表示年轻代的总大小。- 全体堆内存信息:全体堆的内存变革如下:- 在垃圾回收前,全体堆内存从5080KB减少到1501KB。- 全体堆的总容量为18944KB,表示全体堆的总大小。- 耗时信息:垃圾回收过程耗时为0.0004745秒。- user=0.00 表示用户态韶光为0秒。- sys=0.00 表示系统态韶光为0秒。- real=0.00 表示实际韶光为0秒。综合阐明,这条 GC 日志记录了一次由“Allocation Failure”引发的垃圾回收事宜,描述了年轻代和全体堆内存的变革情形,以及垃圾回收过程的耗时信息
四、总结JVM的分代机制是为了优化内存管理,将内存分为年轻代和老年代,利用内存池保存不同年事的工具。
大多数工具被分配到年轻代,个中绝大多数工具终极在这一代消亡。当年轻代达到容量时,会触发垃圾网络,只针对年轻代进行垃圾回收;而其他代中的垃圾暂不处理。在每次次要网络时,一部分幸存工具会被移至永久代。永久代终极也会填满,须要对全体堆进行回收,持续的韶光常日年轻代长,由于牵扯的工具数量更多。