Jvm堆内存管理

本文深入解析Java垃圾回收机制中的年轻代和老年代特性,介绍内存分配策略和相关启动参数,通过测试代码展示如何观察和理解JVM进程的堆内存使用情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.概念解释

1.1 年青代=新生代(eden space)+2个survivor

年青代用来存放新近创建的对象,尺寸随堆大小的增大和减小而相应的变化,默认值是保持为堆大小的1/15,可以通过-Xmn参数设置年青代为固定大小,也可以通过-XX:NewRatio来设置年青代与年老代的大小比例,年青代的特点是对象更新速度快,在短时间内产生大量的“死亡对象”。
年轻代的特点是产生大量的死亡对象,并且要是产生连续可用的空间, 所以使用复制清除算法和并行收集器进行垃圾回收.     对年轻代的垃圾回收称作初级回收 (minor gc)

初级回收将年轻代分为三个区域,  一个新生代 , 2个大小相同的复活代,  应用程序只能使用一个新生代和一个复活代, 当发生初级垃圾回收的时候,gc挂起程序, 然后将新生代和复活代中的存活对象复制到另外一个非活动的复活代中,然后一次性清除新生代和复活代,将原来的非复活代标记成为活动复活代.    将在指定次数回收后仍然存在的对象移动到年老代中, 初级回收后,得到一个空的可用的新生代.

1.2
所谓的新生代和老年代是针对于分代收集算法来定义的,新生代又分为Eden和Survivor两个区。加上老年代就这三个区。数据会首先分配到Eden区当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。),当Eden没有足够空间的时候就会触发jvm发起一次Minor GC。如果对象经过一次Minor GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代中了,当然晋升老年代的年龄是可以设置的。
其实新生代和老年代就是针对于对象做分区存储,更便于回收等等。

2.相关的启动参数:年轻代、老年代和永久代的内存分配

如果想观察JVM进程占用的堆内存,可以通过命令工具jmap或者可视化工具jvisualvm.exe。JVM这些启动参数都拥有默认值,如果想了解JVM的内存分配策略,最好手动设置这些启动参数。再通过JDK提供的工具的统计结果,进行对比,就比较容易理解这些内存分配的理论知识。运行环境是win7 32位操作系统,JDK1.7.0_60版本。

测试代码和JVM启动参数如下:

1
2
3
4
5
6
7
8
9
10
11
public class Test
{
     public static void main(String[] args)
     {
         int a = 0 ;
         while ( true )
         {
             a++;
         }
     }
}
1
2
-Xms=200M  -Xmx200M -XX:NewSize=100M -Xmn100M -XX:SurvivorRatio= 8
-XX:OldSize=60M -XX:PermSize=50M -XX:MaxPermSize=50M

运行上述代码,通过jps命令获取到进程pid,然后通过jmap -heap pid就可以查看内存分配和使用情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>jmap -heap 8912
Attaching to process ID 8912 , please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 24.60 -b09
 
using thread-local object allocation.
Mark Sweep Compact GC
 
Heap Configuration:
    MinHeapFreeRatio = 40
    MaxHeapFreeRatio = 70
    MaxHeapSize      = 209715200 ( 200 .0MB)
    NewSize          = 104857600 ( 100 .0MB)
    MaxNewSize       = 104857600 ( 100 .0MB)
    OldSize          = 62914560 ( 60 .0MB)
    NewRatio         = 3
    SurvivorRatio    = 8
    PermSize         = 52428800 ( 50 .0MB)
    MaxPermSize      = 52428800 ( 50 .0MB)

这里显示的堆配置参数,都可以通过JVM启动参数来设置。下面来解释下几个重要参数的含义:


-Xms 和 -Xmx (-XX:InitialHeapSize 和 -XX:MaxHeapSize):指定JVM初始占用的堆内存和最大堆内存。JVM也是一个软件,也必须要获取本机的物理内

存,然后JVM会负责管理向操作系统申请到的内存资源。JVM启动的时候会向操作系统申请 -Xms 设置的内存,JVM启动后运行一段时间,如果发现内存空间

不足,会再次向操作系统申请内存。JVM能够获取到的最大堆内存是-Xmx设置的值。


-XX:NewSize 和 -Xmn(-XX:MaxNewSize):指定JVM启动时分配的新生代内存和新生代最大内存。


-XX:SurvivorRatio:设置新生代中1个Eden区与1个Survivor区的大小比值。在hotspot虚拟机中,新生代 = 1个Eden + 2个Survivor。如果新生代内存是

10M,SurvivorRatio=8,那么Eden区占8M,2个Survivor区各占1M。


-XX:NewRatio:指定老年代/新生代的堆内存比例。在hotspot虚拟机中,堆内存 = 新生代 + 老年代。如果-XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆内存的1/5。在设置了-XX:MaxNewSize的情况下,-XX:NewRatio的值会被忽略,老年代的内存=堆内存 - 新生代内存。老年代的最大内存 = 堆内存 - 新生代 最大内存。


-XX:OldSize:设置JVM启动分配的老年代内存大小,类似于新生代内存的初始大小-XX:NewSize。


-XX:PermSize 和 -XX:MaxPermSize:指定JVM中的永久代(方法区)的大小。可以看到:永久代不属于堆内存,堆内存只包含新生代和老年代


可以发现:堆内存、新生代内存、老年代内存、永久代内存,都有一个初始内存,还有一个最大内存。下面以老年代的初始内存和最大内存为例,看下内存变化的效果,其他的应该类似。测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TurnedTest
{
     private static List<string> list = new ArrayList<string>();
 
     public static void main(String[] args)
     {
         int a = 0 ;
         while ( true )
         {
             a++;
 
             list.add( "demo" );
         }
 
     }
}</string></string>
显然这个程序存在内存泄露,最终会占满整个堆内存,抛出OOM。为了看清楚这个演变的过程,我们在while循环中添加一个断点,设置breakpoint properties中的"hit count"为100000,以debug模式运行上面的程序,然后使用jmap观察内存占用情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
tenured generation:
capacity = 62914560 ( 60 .0MB)
used     = 0 ( 0 .0MB)
free     = 62914560 ( 60 .0MB)
0.0 % used
 
 
tenured generation:
capacity = 62914560 ( 60 .0MB)
used     = 16409080 ( 15 .648918151855469MB)
free     = 46505480 ( 44 .35108184814453MB)
26.08153025309245 % used
 
tenured generation:
capacity = 62914560 ( 60 .0MB)
used     = 53329496 ( 50 .858970642089844MB)
free     = 9585064 ( 9 .141029357910156MB)
84.76495107014973 % used
 
tenured generation:
capacity = 104857600 ( 100 .0MB)
used     = 84217880 ( 80 .3164291381836MB)
free     = 20639720 ( 19 .683570861816406MB)
80.3164291381836 % used
可以发现老年代内存从最开始的60M,扩大到最大值100M。
### ### JVM 堆内存配置最佳实践及调优方法 JVM 堆内存是 Java 应用程序运行过程中用于对象分配和垃圾回收的核心区域。合理的堆内存配置能够显著提升应用性能并减少 GC 带来的停顿时间。 #### ### 初始堆大小与最大堆大小 初始堆大小(`-Xms`)与最大堆大小(`-Xmx`)应设置为相同值,以避免堆动态扩展过程中带来的性能波动。通常建议堆内存大小不超过物理内存的 80%,以确保系统资源的合理利用 [^2]。 示例配置: ```bash java -Xms2g -Xmx2g -jar your-application.jar ``` #### ### 年轻代与年代比例 年轻代通常建议占堆内存的 1/3 到 1/2,具体比例应根据应用的对象生命周期特点进行调整。可以通过 `-Xmn` 明确设置年轻代大小,或者使用 `-XX:NewRatio` 控制年轻代与年代的比例 [^2]。 例如,若堆大小为 2GB,且年轻代占 1/3,则可配置如下: ```bash java -Xms2g -Xmx2g -XX:NewRatio=2 -jar your-application.jar ``` #### ### 新生代中 Eden 区与 Survivor 区比例 新生代内部通常由 Eden 区和两个 Survivor 区组成。Eden 区与 Survivor 区的比例可通过 `-XX:SurvivorRatio` 进行设置,默认值为 8(即 Eden 占 80%,每个 Survivor 占 10%) [^1]。 示例配置: ```bash java -XX:SurvivorRatio=4 -jar your-application.jar ``` #### ### 垃圾回收器选择 根据应用对吞吐量或延迟的不同需求,选择合适的垃圾回收器。G1 垃圾回收器适用于大堆内存和低延迟需求的应用,可以通过 `-XX:+UseG1GC` 启用,并设置最大停顿时间 `-XX:MaxGCPauseMillis`。CMS 垃圾回收器适合对响应时间要求较高的场景,但可能导致 Full GC。Parallel GC 适合吞吐量要求较高的批处理作业 [^2]。 启用 G1 回收器并设置最大停顿时间: ```bash java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar your-application.jar ``` #### ### 监控与分析 启用 GC 日志可以帮助分析垃圾回收行为。使用 `-XX:+PrintGCDetails` 和 `-XX:+PrintGCDateStamps` 打印详细的垃圾回收日志,并将日志输出到文件 `-Xloggc:/path/to/gc.log`。此外,JConsole、VisualVM 或 JProfiler 等工具可用于监控 JVM 内存使用情况和垃圾回收频率 。 启用 GC 日志: ```bash java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/myapp/gc.log -jar your-application.jar ``` #### ### 代码与资源管理优化 避免内存泄漏是 JVM 调优的重要方面。及时释放不再使用的资源,优化算法和数据结构以减少内存占用。对于线程栈大小,可通过 `-Xss` 设置,通常 256KB 足够满足大多数应用需求 [^3]。 设置线程栈大小: ```bash java -Xss256k -jar your-application.jar ``` #### ### 容器化环境中的配置 在容器化环境中,启用 `-XX:+UseContainerSupport` 使 JVM 能够自动感知容器的资源限制,并通过 `-XX:MaxRAMPercentage` 设置 JVM 最大使用内存占容器限制的比例 [^2]。 示例配置: ```bash java -XX:+UseContainerSupport -XX:MaxRAMPercentage=70.0 -jar your-application.jar ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值