JVM唠嗑(一)—— 堆栈 内存溢出 OOM 堆溢出 dump visualVM

文章目录

前言——关于JVM调优的看法

JVM调优,听起来是个玄幻高深的技术,java程序员里面你说自己会jvm调优,就感觉,同是九年义务教育,为什么你这么秀:)所以有些人似乎特别急切学JVM调优,希望看那些大部头的书来“精通它”,

但实际上,经过一系列调查,实际用JVM调优,需要有以下前提:

  • 要求很高 最大限度发挥硬件效能
  • 极端的业务环境,比如大数据计算引擎 要求堆区足够大,吞吐量足够高等
  • 特殊业务场景出现性能问题 查漏补缺找BUG
  • 代码本身质量本身要ok

有些人代码习惯不好,比如构造了很多用完即扔的对象,比如嵌套着循环调接口,查数据库,造成不必要的性能过度浪费,又盲目去调优补救。其实如果通过观察jvm相关信息,找到bug所在,那我们学jvm目的也达到了,但是如果只是跟着网上教程调调堆大小,换换GC算法,然后一个个试,只知其一不知其二,那就是舍本逐末了,最后心态崩溃,认为不是我错了,是世界错了(bushi)

所以我认为,不能为了调优而调优,一方面,努力提升自己代码质量,另一方面,对这方面学习不能心急,一点一点学习,万丈高楼平地起,然后工作中特殊业务场景出现问题,可以学以致用,利用JVM监控信息去debug,一点点积累调参经验,慢慢的就会了。

聊回本笔记,本系列的笔记主要是科普性质,说实话也不够系统,我们可能从操作系统聊到JVM基础,可能看看JVM的内存模型,也可能聊聊常见的OOM问题,然后看看所谓调优工具是什么样子的。没吃过猪肉,得见过猪跑,我们遇到问题后有个大致的思路,能够看得懂大佬在说什么,能面试造火箭航母的时候问到jvm不懵逼,我觉得足矣,毕竟一上来给自己太大的心理负担——我要精通JVM调优,我觉得学习效果不一定好

JVM JRE JDK

名词解释一下 省的一开始就劝退

缩略语

全程

中文

JDK

JAVA Development Kit

JAVA开发工具包

JRE

JAVA Runtime Environment

JAVA运行环境

JVM

JAVA Virtual Machine

JAVA虚拟机

这张java se的架构图 稍微看看就好
在这里插入图片描述

另外,经常听大佬说什么Hotspot VM板块划分HotSpot 虚拟机 啥玩意儿啊?

其实JVM 种类有很多,比如 Oralce-Sun,也就是Java官方的 Hotspot虚拟机,当然别的虚拟机也有,算是想在前人肩膀上改进?,比如 Oralce JRockit, IBM J9, Taobao JVM(真的是中国那个淘宝)等等。当然,Hotspot主流,必学。

堆 栈

面向对象,我们操作的对象实例 大部分空间 存储的位置就在堆Heap里面

而我们栈Stack,因为其先入后出的特性,特别适合存方法的调用的相关上下文context

问题一 啥是上下文,你可以简单理解,比如方法里面的局部变量,存状态,存标志位,存数据的那些信息,只要方法不执行结束(return),他这些上下文信息必须存在栈里面,否则凉凉,

为啥?举个不那么恰当的例子,假设这个方法A执行途中调用别的方法B,比方说循环调用,

int i=LEN;
for(; i>=0; i--) {
	B();
}

B是需要A存储的变量i的,否则执行多少次他自己都不知道。。所以只要A不执行结束则其上下文必须存着,存在栈里。

问题二 为什么方法context适合用栈存

我们再看看经典的递归
为什么?你想,递归意思,方法自我调用,一层一层的调用自己,每次调用都要用栈存这个方法相关的context,那么最终,当最后一次被调用的方法开始执行,我们知道,这个方法的context是存在栈顶的,他是最后进栈的(context最后存到栈里边的),这时栈的深度(存进去的context数量)是最大的,

好,最后进栈的那个方法,他执行完了,要return返回了,那么,返回以后我们自然不需要存它的context了——他执行完已经没用了,这时发生出栈(弹栈 pop)操作,换言之,最后进栈的最先出栈

所以这个栈结构,因为他后入先出,先入后出,所以特别适合存方法调用,我们可能不递归,但是一定会有方法调用方法的情况,这时也同理。

接下来先不讲深入的、例外的知识,我们就用IDEA简单做个实验——想办法堆栈溢出

堆溢出

只要我们JVM,他作为一个应用程序,向OS申请的内存够大,我们OS就能内存爆炸:)
同样的,只要我们的变量够大,还不让JVM内存回收,JVM就必须腾位置给我们,我们就能让堆爆炸,艺术就是爆炸~ 诶,玩,就是玩

爆炸,也就是溢出,堆溢出(HeapOverflow)

之前我们说不要盲目改JVM配置参数,那有哪些配置参数?
这里我们学两个JVM:Xms Xmx

Xms 是指设定JVM堆启动时占用内存大小,JVM应用程序,在启动就会直接分配的,是JVM实打实占用的内存大小,如果大于电脑内存,会造成内存溢出(存不下就爆炸)

Xmx 是指设定程序运行期间 最大可占用的内存大小,这只是个上限,有保护OS的意味,如果用不了那么多,实际上OS并不会给JVM分配这么大的内存,因为没必要。
如果如果程序实际运行需要占用的内存,超出了这个设置值,就会抛出OutOfMemory异常。

其实Xms Xmx有全称:-XX:InitialHeapSize -XX:MaxHeapSize,但是太长了我们就用简写吧

另外,默认配置一般是,初始Xms=物理内存/64,Xmx=物理内存/4(如服务器内存是32G,即JVM最大内存可为8G)

好了,那既然要实现堆溢出,我们怎么设置JVM配置参数呢?如下:

-Xms10m -Xmx10m -Xlog:gc*

其中-Xlog:gc*就是用来打印堆使用情况的,-XX:+PrintGCDetails已经废弃了 别用了

好的
那我们写个这样的代码:

public class HeapOverFlow {
    public static void main(String[] args) {
        List<Object> listObj = new ArrayList<Object>();
        for(int i=1; i<=10; i++){
            // 每次注入1M
            Byte[] bytes = new Byte[1024 * 1024];
            listObj.add(bytes);
            System.out.println("向堆内注入"+i+"M");
        }
        System.out.println("堆被注满了QAQ");
    }
}

然后他会打印出一大堆我们看不懂的东西,如图
在这里插入图片描述
这里我们先知道之基本的信息

  • heap回收(GC)的最小单位region是 1M
  • GC回收使用的算法是G1

好的其他我们先不管,看末尾打印:
在这里插入图片描述
很明显,确实我们实现了内存溢出(OOM),准确的说是堆溢出(java.lang.OutOfMemoryError: Java heap space)
我们之前说的应验了
在这里插入图片描述
另外,在这行也说明了 我们heap大小为 10240K=10M
在这里插入图片描述
这里的“garbage-first”就是指“G1”

另外,我们注意到其实heap没有被用满就报了OutOfMemory(OOM),为啥?实际上它每次申请内存,如果计算出,申请后已经超限,超Xmx设定的限制,就会报错了 反正分配了内存后也必炸

问题一
我开始预想的是,一个Byte元素占一个Byte(8位 8b),因此每次注入1024*1024B 也就是1MB 那么我们分配了10MB 按理来说能注入10次,实际上注入一次就发生了OOM,Why?

我认为有以下几个原因:

  • 首先就是 实际上Byte数组在内存(或者说堆)的存储结构并非直白的就放在里边,实际的结构会更加复杂,层层封箱(后面会讲)会导致占用的空间比实际要存的数据要
  • 堆空间不是铁板一块,而是具有分区的,新生代分区(young,我们后面会细讲)存储新鲜出炉new出来的对象实例,之后很快被GC,而剩下没被GC的转移到survivor区,后面还会转到old区。。。总之是区与区之间有复制,转移的过程,因此不可能直接占满整个堆
  • G1这个GC算法的设计细节,可能它很有前瞻性 已经算出来即将发生OOM 于是提前终止,省的麻烦OS做真正的内存分配

问题二
这里确实能看到heap宏观的一些信息,但是实际上我们如果想要排错debug,我们还是不知道是哪方面因素导致了OOM,这时dump文件派上用场。

dump visualVM 工具基本使用

我们在之前JVM配置参数的宏的基础上加点东西:

-Xms10m -Xmx10m -Xlog:gc* -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:/dumps/

-XX:+HeapDumpOnOutOfMemoryError意思,发生OOM就生成dump文件 这相当于一个触发器
-XX:HeapDumpPath=E:/dumps/设置dump文件放置的位置

dump文件类似一个快照,保留事故现场,方便我们时候推测当时的原因。

分析dump文件的工具有很多,我还是喜欢可视化的visualVM

咋弄呢?官网先下载,http://visualvm.github.io/
在这里插入图片描述
然后我们使用IDEA插件与之构建关联
在setting plugin里边 像vscode一样 在市场marketPlace搜索 安装即可
在这里插入图片描述
第一次启动会让你配置一下visual VM的位置,实际上你可以专门弄个文件夹放这些工具 比如maven tomcat等,如图
在这里插入图片描述
然后这里就是刚刚官网下载的文件
在这里插入图片描述
用这个目录配置即可

然后打开visualVM File->载入Load 我们的生成的dump文件:
在这里插入图片描述
然后生成分析报告 如下
在这里插入图片描述
Heap那边显示了 当时有多少类classes 有多少对象实例在里边 有多少classloader(我们后边会讲到),还有总大小Size,可以看到实际堆用到的空间

问题一
既然Xms都定死了是10MB为啥这里的Heap Size是127227659B=12.1MB>10MB?
为什么偏大?这里显示的heap size应该是实际用到的大小 我们说理想与现实有差距,你JVM配置的堆大小,只是申请,请求OS来真正执行这个内存分配,但还是可能存在偏差,差了一点可以接受,
这样也说明了为啥Xms你不能设计,刚刚好为实际内存,否则计算机要不直接内存溢出(因为实际占用内存偏大),要不,可能依靠虚拟内存,当然这会更加消耗CPU,导致运行变慢。

问题二
怎么找到罪魁祸首?、
其实很简单,我们看下面几个栏目,找百分比最大的那个,一般超过30%就很值得注意了
比如,在 Size of instances
在这里插入图片描述
66%接近 这就很明显有问题,

当然另外一个栏目 instance by sizes也同样,这两个其实差不多,一般看前者
在这里插入图片描述
我们来详细看看classes by size of instances
在这里插入图片描述
这里有1048576 = 1024*1024 B 的Byte实例 这与之前我们观察到的,只是注满了一次的结果吻合
在这里插入图片描述
而且这些Byte实例都是NULL 也很符合我们代码,毕竟都是new出来的还没初始化。

到此,我们用visualVM找罪魁祸首的实验算是成功了。

后记

下一篇会做栈溢出相关的实验

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 1. Spark中JVM内存使用及配置详情: Spark中的JVM内存使用主要包括堆内存和非堆内存。堆内存用于存储对象实例,而非堆内存用于存储类信息、方法信息等。在Spark中,可以通过以下参数来配置JVM内存使用: - spark.driver.memory:用于配置Driver进程的堆内存大小,默认为1g。 - spark.executor.memory:用于配置Executor进程的堆内存大小,默认为1g。 - spark.driver.extraJavaOptions:用于配置Driver进程的非堆内存大小和其他JVM参数。 - spark.executor.extraJavaOptions:用于配置Executor进程的非堆内存大小和其他JVM参数。 2. Spark报错与调优: 在Spark运行过程中,可能会出现各种报错,如内存溢出、任务失败等。针对这些报错,可以采取以下调优措施: - 内存溢出:增加Executor进程的堆内存大小、减少每个任务的数据量、使用缓存等方式来减少内存使用。 - 任务失败:增加Executor进程的数量、减少每个任务的数据量、调整任务的并行度等方式来提高任务的执行效率。 3. Spark内存溢出OOM异常: Spark内存溢出OOM异常是指Executor进程的堆内存不足以存储当前任务所需的数据,导致任务执行失败。可以通过增加Executor进程的堆内存大小、减少每个任务的数据量、使用缓存等方式来减少内存使用,从而避免内存溢出异常的发生。 ### 回答2: Spark中JVM内存使用及配置详情: Spark使用JVM来执行任务,其中一个非常重要的参数是堆内存(Heap Memory)的大小。堆内存用于存储对象实例和方法调用的信息。在使用Spark时,可以通过spark.driver.memory和spark.executor.memory参数来配置JVM内存的大小,默认情况下,它们都是1g。需要根据具体的任务需求和集群资源情况来进行调整。如果遇到内存不足的情况,可以增加堆内存的大小,但是需要保证集群资源充足。 Spark报错与调优: 在使用Spark过程中,常见的报错有内存溢出、数据倾斜、任务运行时间过长等问题。对于这些问题,可以采取一些调优策略进行处理。例如,在遇到内存溢出(Out of Memory)异常时,可以通过增加堆内存大小或者减少数据量来解决;对于数据倾斜的情况,可以考虑数据重分区或者使用一些聚合策略来优化;对于任务运行时间过长的情况,可以考虑增加Spark任务的并行度或者使用缓存机制来加速计算等。 Spark内存溢出OOM)异常: Spark中的内存溢出异常通常是由于使用的内存超过了配置的阈值引起的。在配置Spark应用程序时,可以设置spark.driver.memory和spark.executor.memory参数来调整JVM内存的大小。如果内存不足,则需要增加内存配置或者优化代码逻辑。另外,可以通过设置spark.memory.offHeap.enabled参数来开启堆外内存,将一部分内存放到堆外,从而减少对JVM内存的占用。此外,还可以通过设置spark.memory.fraction参数来调整JVM内存的分配比例,更好地利用内存资源。如果调整参数后仍然出现内存溢出问题,还可以考虑调整Spark任务的并行度或者增加集群资源。 ### 回答3: Spark是一个基于内存的数据处理框架,能够高效地处理大规模数据集。在Spark中,JVM内存的使用及配置对于保证程序的稳定和性能的提升非常重要。 首先,Spark的JVM内存分为堆内存和非堆内存两部分。堆内存是用来存储对象实例的,而非堆内存则用来存储JVM本身的运行时数据。为了合理配置JVM内存,可以通过配置spark.driver.memory和spark.executor.memory参数来设置堆内存的大小。根据集群的硬件配置和任务的需求情况,可以根据具体情况来调整这两个参数的数值。 其次,在Spark运行过程中,经常会遇到各种报错。常见的报错有内存溢出(OutOfMemoryError)、任务失败(TaskFail)等。当遇到内存溢出错误时,可以尝试以下几种方法来调优: 1. 增加可用内存:可以通过增加executor内存或调整任务分区大小来扩大可用内存。 2. 减少数据规模:可以通过过滤数据、采样数据或者使用压缩算法来减少数据的大小。 3. 优化代码:可以优化代码逻辑和算法,减少内存使用。 4. 调整缓存策略:可以通过手动控制缓存的数据量,及时释放不再使用的缓存。 最后,Spark的内存溢出OOM)异常通常是由于数据量过大,超出了可用内存的限制而导致的。当出现内存溢出异常时,可以参考上述的调优方法来解决问题。 总之,合理配置JVM内存、及时处理报错、避免内存溢出异常是保证Spark程序稳定与性能的关键。希望以上回答对您有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值