性能测试常见的内存溢出问题: JVM 内存溢出如何调优?

针对java项目做性能测试的时候,很多同学都见过一个报错,就是OOM【Out Of MemoryError】;那出现这种报错就是项目发生了内存溢出的问题,这是比较严重的性能问题。所以,作为一个性能测试工程师,我们要能够分析JVM内存的问题以及理解其中的原理,才能更好的给JVM内存出现的性能瓶颈问题进行调优。

JVM概念

要学习JVM内存问题分析和调优之前,我们先来了解一下什么是JVM?

JVM【java virtual machine】: java虚拟机,是Java程序运行所需要的一台虚拟机器。

在操作系统上运行一个java程序的过程中,也就是通过“java -jar ” 启动一个java进程的同时就会启动一个java虚拟机。java虚拟机是在操作系统之上的程序,JVM直接和操作系统进行交互,不跟硬件直接交互。

但是java虚拟机可以管理自己的进程和线程,有自己独立的内存,管理自己的内存,这个叫做JVM内存。

图片

JVM的优点

可以实现一次编写,到处运行 ,并支持一个包在Linux和windows mac等不同的平台都可以直接兼容运行;

有自动内存管理和垃圾回收机制,这是JVM非常重要的一个关键特性;

这个机制叫做:GC 【Garbage Collection】,代码写完对象,不需要开发人员手动释放内存,而是自动回收内存,这是java应用程序的JVM内存的独有机制 ,相比于其他的语言 C是不存在自动回收的,需要手动删除。

覆盖广:因为现在市面上java语言的项目很多很普遍,所以只要是java项目都是基于jvm实现的。

JVM内存模型

现在的java程序都是基于1.8版本,因为java1.8是目前稳定主流的版本,企业基本都兼容1.8及其以上的版本,所以我们学习1.8java版本的内存模型就可以了。

注意:不同版本之间JVM内存存在一定差异,比如只有1.8的版本及之后才有元空间的概念,之前的jvm没有元空间。

jvm运行时有5块数据区,分别如下:

程序计数器:线程私有

虚拟机栈:线程私有

本地方法栈:线程私有

方法区:线程共享

在这里插入图片描述

五个部分分别来详细介绍一下:

1、程序计数器:线程私有,用于存储指向下一条指令的地址,是一块很小的空间,一般不会有内存问题,也不会进行垃圾回收。所以做性能测试的时候我们不太需要关注。

作用:用于存储指向下一条指令的地址

特点

很小的内存区域,读取速度很快

每个线程独有,线程之间不会相互干扰

JVM中唯一一个没有Out Of Memory Error的区域,也没有用到垃圾回收

2、虚拟机栈:线程私有,随着线程创建而创建,随着线程消失而销毁。

作用:每个线程在创建时都会创建一个虚拟机栈,所以线程独有;其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用

这个线程每次调用方法、调用函数等都会进行一个栈帧的入栈,调用的方法函数越多嵌套越深,栈帧就会越多;方法执行完了,就会把栈帧丢出去,叫做出栈;

特点:线程独有

JVM对虚拟机栈的两个操作:方法执行—>进栈;方法结束—>出栈。

栈并不存在垃圾回收的概念,因为方法调用完成栈帧就丢出去了,不需要jvm控制垃圾回收,但是可能会存在栈溢出。

• 如果栈帧的数量过多,不停的入栈,超过了虚拟机栈的容量,或者某些栈帧过大会引发栈溢出:SOE(StackOverflowError) ,比如代码出现死循环等。

• 如果某些方法的参数或者返回值结果等太大, 会导致栈帧过大,超过了虚拟机栈的容量;也会出现栈溢出。

3、本地方法栈:与虚拟机栈类似,是线程私有的 ,发生性能问题的概率很低,所以不需要太多关注。

4、方法区【元空间】:是线程共享的,共享的意思就是随着程序的启动而启动,除非进程关掉才会消失,不会因为线程而消失。比如一个方法里比如有10个线程,共享方法区里面的类、常量、静态变量等信息,不会每个线程启动单独开辟一个空间给它,而是大家共享这个方法区。

在java1.8版本里,方法区就是元空间,在1.7版本,叫做永久代。不过现在主流都是1.8版本,所以我们就了解元空间即可。

元空间用来保存程序被编译完成后被虚拟机加载到内存的一些 类信息、常量、静态变量以及即时编译器编译后的代码等数据,一般是一些不怎么会篡改和变动的数据会存在元空间。

所以程序启动后,元空间的大小基本不会变化。

元空间的大小可以通过参数进行配置,如果我要存的类信息、常量、静态变量信息很多,超过分配的元空间的大小,就可能导致 内存溢出,抛出错误:OutOfMemoryError 。

**5、堆:**这是线程共享的 ,jvm内存最大最重要的区域,性能问题出现比较多的地方,一定要重点掌握。

在虚拟机启动时创建,java -jar命令启动程序后,内存大小就分配好了。

存放对象实例,几乎所有的对象实例都在这里分配内存 ; 比如代码里new 一个对象会存在这里,new的对象越多 ,就会存在内存越多。

当堆中没有内存分配给对象实例时,会抛出内存溢出的报错信息:OutOfMemoryError 。

堆里划分为:新生代、老年代 ,注意:java1.8版本里只有这两个,没有永久代了

在这里插入图片描述

新生代:用于存储一些存储时间短的对象,主要包括三个部分:

1)Eden: 叫做伊甸区 ,用于存放JVM刚分配的对象数据;

2)From Survivor【存活1区】

3)To Survivor【存活2区】

• 存活1区和存活2区两个空间一样大,Eden中未被GC【垃圾回收】的对象,会在这两个区间来回copy,默认拷贝超过15次还没有被GC的对象就被移入年老代 。

老年代:大对象或者多次被GC后还存在的对象就会存到老年代,它的空间比新生代大很多;

在老年代快满的时候会触发一次FGC,FGC是需要很长时间的触发一次的,full GC发生的时候,老年代和年轻代都会发生一次GC

当新生代和老年代都占满了,GC也释放不出来更多内存了,如果此时还在产生一些新的对象,那么就会发生内存溢出【OOM】的错误,如下图所示:

图片

JVM的参数设置

了解了JVM的组成部分,那么在启动项目的时候为了调整性能我们就可以针对JVM内存的大小的设置和调整来优化项目的性能。常见的参数有:

图片

如何设置这些参数呢?我们来结合项目实际案例给大家讲解这些参数的设置:

1、启动某JAVA项目进程,ps -ef 查看java进程id:

图片

2、使用jmap -heap 1727 命令查看这个java进程的 jvm的内存设置,这些是在代码里写好的默认初始值:

图片

3、还可以看到堆内存的使用情况:

图片

4、如果要修改和调整这些大小,就可以去修改项目的配置文件。

比如如果你们的是基于tomcat的,那么可以修改catalina.bat 或者 catalina.sh 配置文件来调整这些参数的大小,如下图,在配置文件里加一行设置参数大小,并保存文件:

图片

5、配置完成后,重新shutdown tomcat的服务 再次启动这个服务:

查看一下启动的进程:ps -ef | grep java 可以看到启动进程的命令里参数配置成功了

图片

常见JVM内存的面试题

1、JVM里哪些内存会被回收【GC】?

1)是否已死:使用的是是引用计数算法,被引用的计数等于0时会被回收。

注意引用计数为0 会放在这个堆里,只有发生了GC的时候,会检测到计数为0 才会回收;不会计数为0 立马被回收了。

2)可达性算法:没有引用链的对象内存会被回收,这个会出现频率高一些

链表里存储谁调用了这个对象信息,就是引用链指向这个被引用者。每个对象维护一个引用链。如果引用链指向着为空了,说明没有人调用这个对象,那么就可以被回收了。

2、JVM内存会在什么时候被回收?

有两种场景会触发GC操作:

1)分配的JVM内存空间不足的时候才会执行回收

2)也可以设置定时回收。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

  • 22
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值