JVM 性能调优 - 四种引用(4)

本文详细介绍了Java虚拟机中的四种引用类型:强引用、软引用、弱引用和虚引用。通过实例展示了它们在内存管理、垃圾回收以及缓存策略中的作用。强引用是最常见的引用类型,即使内存不足也不会回收;软引用在内存充足时保留,内存不足时回收,常用于缓存;弱引用比软引用更弱,只要有垃圾回收就会回收;虚引用不直接访问对象,主要用于系统通知和后续处理。理解这些引用类型有助于进行高效的JVM性能调优。

为什么会有四种引用

我们先回顾下在 Java 虚拟机内存体系(1) 中提到了的垃圾回收算法

1、引用计数法

原理:给对象添加一个引用计数器,每当有一个地方引用它,计数器的值就加一。每当有一个引用失效,计数器的值就减一。当计数器值为零时,这个对象被认为没有其他对象引用,可当作垃圾回收。

缺点:需要维护引用计数器,有一定的消耗。且较难处理循环引用的问题。(现在基本没有地方使用这种算法了,了解即可)。

2、可达性分析算法

先想象一下生活中的场景,假设张三这个人有一条狗,狗被系着一条绳子。如下图所示:

如果张三把狗遗弃了,那么这条狗和绳子就没有关联关系了,狗会被抓狗队抓起来送到宠物收容所。这里可以把张三当作 GC Roots,张三和狗之间断了联系,那么狗和绳子都会被回收。如下图所示:

可达性分析算法原理:通过一系称为 GC Roots 的对象作为起始点,从 GC Roots 的对象出发,向下搜索,如果找到的对象和 GC Roots 有直接引用或间接引用关系,则说明这个对象不是垃圾,否则,这个对象就是垃圾。

Java 中对引用的传统定义:如果 reference 类型的数据中存储的数值代表是另外一块内存的起始地址,就称 reference 数据是代表某块内存、某个对象的引用。

但是这种定义比较简单,只有被引用未被引用 两种状态。如果有些对象没什么用,但是又不想丢弃,就让它留在内存中,当内存紧张时再回收,这样有个好处,当想再用它的时候,就不用再创建一个新的出来,这个就是我们常说的将对象缓存起来。很多系统的缓存功能都符合这样的应用场景。

强引用

Cat cat = new Cat()

我们可以看到等号的右边,创建了一个对象,该对象存储在堆中,而等号的左边是这个对象的引用,存储在栈中。而这种引用关系,就被称为强引用。

当 JVM 开始回收的时候,对于强引用,即使内存不足也不会回收被强引用的对象。因此强引用是造成 Java 内存泄漏的主要原因之一。

而如果我们显示地将强引用赋值为 null(比如 cat = null),则可以认为之前的对象会被垃圾收集器回收。

编写脚本

package com.test;

public class TestStrongReference {
    public static void main(String[] args) {
        // 这样定义的默认就是强应用
        Object obj1 = new Object();

        // 使用第二个引用,指向刚刚创建的 Object 对象
        Object obj2 = obj1;

        // 将 obj1 引用设置为 null
        obj1 = null;

        // 模拟 OOM 自动 GC
        try {
            // 创建 20M 的大对象
            byte[] bytes = new byte[20 * 1024 * 1024];
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println(obj1);
            System.out.println(obj2);
        }
    }
}

运行程序

$ java -Xms5m -Xmx5m -XX:+PrintGCDetails com.test.TestStrongReference
[GC (Allocation Failure) [PSYoungGen: 918K->496K(1536K)] 918K->740K(5632K), 0.0016865 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 496K->504K(1536K)] 740K->756K(5632K), 0.0019124 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 504K->0K(1536K)] [ParOldGen: 252K->641

<think>我们正在讨论JVM性能,根据用户的问题,他想要获取JVM性能的相关信息,包括方法技巧。同时,用户提供了几个引用,但这些引用是关于JVM性能的概述性内容,我们需要结合这些引用以及我们对JVM的知识来回答。 注意:用户之前的问题是关于Java类加载机制,现在转向了JVM性能,因此我们要专注于部分。 回答结构: 1. 引言:简要介绍JVM性能的重要性。 2. 目标:明确的目标指标(如吞吐量、延迟、内存占用等)。 3. 方法:详细描述的常用方法技巧(包括参数配置、监控工具、代码化等)。 4. 实战步骤:给出一个典型的步骤(参考引用[4]中的步骤)。 5. 注意事项:中需要注意的问题。 6. 总结。 同时,根据要求,我们需要在回答末尾生成相关问题。 由于引用中提供了一些信息,我们可以适当引用,但主要依靠我们的知识库来构建详细内容。 注意:引用标记[^1]、[^2]等需要放在引用内容的后面,并且自然。 </think>### JVM性能方法与技巧详解 JVM性能是提升Java应用效率的关键,核心目标包括降低GC频率、减少STW时间、内存使用提高吞吐量。以下是系统化的方法实战技巧: --- #### 一、核心目标 1. **内存化** - 减少堆内存碎片Full GC次数 - 平衡新生代(Young Gen)老年代(Old Gen)比例 - 示例参数: ```bash -Xms4g -Xmx4g # 堆内存初始值=最大值,避免动态扩容开销 -XX:NewRatio=2 # 新生代:老年代=1:2 ``` 2. **延迟化** - 缩短GC暂停时间(STW),尤其是CMS/G1的并发阶段 - 关键指标:`GC Pause Time < 200ms`(视业务敏感度整) 3. **吞吐量化** - 提高应用有效工作时间占比 - 目标:`Throughput = (总运行时间 - GC时间) / 总运行时间 > 95%` [^4] --- #### 二、方法与技巧 ##### 1. **内存参数** | 参数 | 作用说明 | 场景 | |---------------------|----------------------------------|----------------------------| | `-XX:MaxTenuringThreshold` | 对象晋升老年代年龄阈值 | 减少过早晋升导致的Full GC | | `-XX:SurvivorRatio=8` | Eden与Survivor区比例(默认8:1:1)| 对象存活率高的应用 | | `-XX:+UseG1GC` | 启用G1垃圾收集器 | 大堆(>4GB)或低延迟需求 | **实战技巧**: - 若Minor GC频繁,增加`-Xmn`(新生代大小) - 若Full GC频繁,增大老年代或降低`-XX:MaxTenuringThreshold` ##### 2. **GC日志分析与监控** - **启用详细GC日志**: ```bash -XX:+PrintGCDetails -Xloggc:/path/to/gc.log ``` - **关键分析工具**: - `jstat -gcutil <pid> 1000`:实时监控GC各分区使用率 - `jmap -dump:format=b,file=heapdump.hprof <pid>`:生成堆转储文件 - **可视化工具**:GCViewer、MAT(Memory Analyzer)分析内存泄漏[^3] ##### 3. **垃圾收集器选择策略** | 收集器 | 适用场景 | 重点 | |----------------|----------------------------------|----------------------------| | **Parallel GC** | 高吞吐量应用(如批处理) | `-XX:ParallelGCThreads` | | **CMS** | 低延迟Web服务(JDK8及之前) | `-XX:CMSInitiatingOccupancyFraction` | | **G1** | 大内存+均衡吞吐/延迟(JDK9+默认) | `-XX:MaxGCPauseMillis=200` | > **案例**:某电商平台通过G1替换CMS,将99% GC暂停从500ms降至50ms[^3]。 ##### 4. **代码层化** - **减少短命对象**:避免循环内创建临时对象 - **软/弱引用管理**:缓存使用`WeakHashMap`防止内存泄漏 - **堆外内存控制**:Netty等框架需限制`-XX:MaxDirectMemorySize` --- #### 三、标准化流程 [^4] 1. **基线分析**:无参数运行,采集GC日志性能指标 2. **瓶颈定位**: - 高频Full GC → 检查老年代内存或对象泄漏 - 长暂停时间 → 化GC算法或整`MaxGCPauseMillis` 3. **参数迭代**:每次仅修改1-2个参数,对比测试 4. **监控验证**:使用APM工具(如SkyWalking)跟踪后效果 --- #### 四、注意事项 1. **避免过度**:化代码架构,JVM参数为最后手段 2. **版本差异**:JDK11+的ZGC/Shenandoah适用于超低延迟场景(暂停<10ms) 3. **监控持续性**:生产环境需持续监控`jstat`GC日志[^2] > **本质**:在内存、吞吐量、延迟三者间寻找平衡点,公式表达: > $$\text{系统效率} = \frac{\text{有效计算时间}}{\text{总时间}} = 1 - \frac{\text{GC时间} + \text{其他开销}}{\text{总时间}}$$ --- ### 总结 JVM需结合监控数据、GC日志业务场景迭代推进。先通过`jstat`/堆转储定位瓶颈,再针对性内存分区或GC算法。对于多数应用,G1+合理堆大小配置即可满足需求,复杂场景可探索ZGC[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

magic_kid_2010

你的支持将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值