深解JVM 3-内存调优

前言

JVM基本概念

深解JVM 1-Java虚拟机基本原理 - chch213 - 博客园 (cnblogs.com)

JVM内存结构

深解JVM 2-内存结构 - chch213 - 博客园 (cnblogs.com)

前面我们讲了JVM一些常识性的东西,以及垃圾回收机制主要针对的堆的内存回收。本文我们主要介绍下JVM调优的一些基本知识。

 

下图文JVM的内存模型

 

从图中我们可以看到,

1、堆内存结构,轻代(YoungGen),年老代(Old Memory),及持久代(Perm,在Java8中被取消,我们不做深入介绍  JDK1.6之前,永久代,常量池是在方法区;JDK1.7,去永久代,常量池在堆中;JDK1.8之后,无永久代,常量池在元空间;)。

2、垃圾回收GC,分为2种,一是Minor GC,可以可以称为YGC,即年轻代GC,当Eden区,还有一种称为Major GC,又称为FullGC。

3、GC原理:

我们可以看到年轻代包括Eden区(对象刚被new出来的时候,放到该区),S0和S1,是幸存者1区和幸存者2区,从名字可以看出,是当发生YGC,没有被任何其他对象所引用的对象将会从内存中被清除,还被其他对象引用的则放到幸存者区。当发生多次YGC,在S0、S1区多次没有被清楚的对象,则会被移到老年代区域。当老年代区域被占满的时候,则会发送FullGC。

无论是YGC或是FullGC,都会导致stop-the-world,即整个程序停止一些事务的处理,只有GC进程允许以进行垃圾回收,因此如果垃圾回收时间较长,部分web或socket程序,当终端连接的时候会报connetTimeOut或readTimeOut异常,

4、从JVM调优的角度来看,我们应该尽量避免发生YGC或FullGC,或者使得YGC和FullGC的时间足够的短。

 

JVM调优详解

一、对JVM内存的系统级的调优主要目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。特别要关注Full GC,因为它会对整个堆进行整理,Full GC一般由以下几个原因导致:

1、老年代空间不足
调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在老年代创建对象

2、持久代;空间不足
增大持久代空间,避免太多静态对象

3、System.gc()被显示调用
垃圾回收不要手动触发,尽量依靠JVM自身的机制

二、调优手段
调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现,下面来看看各部分比例不良设置会导致什么后果:

1、新生代设置过小
新生代GC次数非常频繁,增大系统消耗
导致大对象直接进入老年代,占据了老年代剩余空间,诱发Full GC
2、新生代设置过大
新生代设置过大会导致老年代过小,从而诱发Full GC
新生代GC耗时大幅度增加
一般来说新生代占整个堆1/3比较合适

3、保留空间Survivor设置过小
导致对象从使用空间eden直接到达老年代,降低了在新生代的存活时间

4、保留空间Survivor设置过大
导致eden过小,增加了GC频率

另外,通过-XX:MaxTenuringThreshold=n来控制新生代存活时间,尽量让对象在新生代被回收

JVM提供两种较为简单的GC策略的设置方式

(1)吞吐量优先
JVM以吞吐量为指标,自行选择相应的GC策略及控制新生代与老年代的大小比例,来达到吞吐量指标。这个值可由-XX:GCTimeRatio=n来设置。

(2)暂停时间优先
JVM以暂停时间为指标,自行选择相应的GC策略及控制新生代和老年代的大小比例,尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成,这个值可由-XX:MaxGCPauseRatio=n来设置。

Runtime类解析

 

 

 

 当然上面内存比例可以通过参数修改设置,如下:-Xms1024m  -Xmx1024m -XX:+PrintGCDetails

(37条消息) JVM学习之路(七)——JVM配置参数_zj_daydayup的博客-CSDN博客_jvm参数配置

运行后其中一部分显示如下:

 

OOM示例

上面讲述了如何看我们目前堆内存的分配大小,可以通过参数设置,那么我们就调整最小为8M,然后写一个简单的案例看它OOM具体报错信息:

-Xms10m  -Xmx10m -XX:+PrintGCDetails

案例:

public class JVMDemo1 {

    public static void main(String[] args) {
        // 虚拟机最大内存
        System.out.println(Runtime.getRuntime().maxMemory() / 1024 / 1024 + "MB");
        // 总内存
        System.out.println(Runtime.getRuntime().totalMemory() / 1024 / 1024 + "MB");
        String str = "JVM1HELLOJAVA";
        while (true) {
            str += str + new Random().nextInt(888888888) + new Random().nextInt(999999999);
        }
    }
}

运行报错:

 

看到报错是不是一脸懵,看着GC回收都有点晕,不急我们一点点熟悉下:

看报错之前我们先要知道我们目前程序JVM设定的初始化值:PSYoungGen 2048K   ParOldGen 7168K

[GC (Allocation Failure) [PSYoungGen: 2446K->504K(2560K)] 3569K->2030K(9728K), 0.0012055 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

GC (Allocation Failure) : 发生了一次垃圾回收,若前面有Full则表明是Full GC,没有Full的修饰表明这是一次Minor GC 。注意它不表示只GC新生代,括号里的内容是gc发生的原因,这里的Allocation Failure的原因是年轻代中没有足够区域能够存放需要分配的数据而失败。
PSYoungGen: 使用的垃圾收集器的名字。
2446K->504K(2560K):垃圾收集前后的年轻代内存使用情况,其中前面的2446K为gc之前的使用量,504K为gc之后的内存使用量。括号里的2560K为该内存区域的总量。年轻代减少:2446-504=1942k
3569K->2030K(9728K): 垃圾收集前后整个堆内存的使用情况,括号里的为整个可以的堆内存的容量。 Heap堆内存减少:3569-2030=1539K       1942-1539=403K 说明该次共有403K内存从年轻代移到了老年代
0.0012055 secs:整个GC过程持续时间
[Times: user=0.00 sys=0.00, real=0.00 secs] :分别表示用户态耗时,内核态耗时和总耗时。也是对gc耗时的一个记录。

使用JProfiler工具分析OOM原因

安装插件:

 

下载客户端-自定义存储一键安装(注意不要有中文文件名)

官网

Java Profiler - JProfiler (ej-technologies.com)

 

生成heapdump文件

1、使用 jmap 命令生成:

    jmap 命令是JDK提供的用于生成堆内存信息的工具,切换到JDK_HOME/bin目录下后,执行下面的命令生成Heap Dump:

windows环境:

jmap -dump:live,format=b,file=heap.hprof <pid>

linux环境:

./jmap -dump:live,format=b,file=heap.hprof <pid>

其中pid是JVM进程的id,heap.hprof是生成的heap dump文件,在执行命令的目录下面。推荐此种方法。

2、使用jprofiler工具
在JVM的配置参数中可以添加 -XX:+HeapDumpOnOutOfMemoryError 参数,当应用抛出 OutOfMemoryError 时自动生成dump文件。
示例:-Xms10m  -Xmx10m  -XX:+HeapDumpOnOutOfMemoryError
运行后同时在项目的根目录下生成dump文件:

 

 如何解决这些OOM异常?

1、要解决OOM异常或heap space的异常,一般的手段是首先通过内存映像分析工具(如Eclipse Memory Analyzer)对dump 出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。

2、如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots 的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots 相关联并导致垃圾收集器无法自动回收它们的。掌握了泄漏对象的类型信息,以及GC Roots 引用链的信息,就可以比较准确地定位出泄漏代码的位置。

3、如果不存在内存泄漏,换句话说就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(-Xmx 与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。

 

posted @ 2022-05-30 23:43  chch213  阅读(93)  评论(0编辑  收藏  举报