草庐IT

简单记录一次远古版本dubbo发生的PermGen space异常

java小新人 2023-04-17 原文

  环境介绍: dubbo的版本是比较旧的版本,  肯定是小于2.5的, jdk版本是1.7, 默认使用的是HotSpot虚拟机

  前提说明: dubbo版本应该就是最原始的2.x的版本, 由于在这个基础上公司还经过了自己的自定义封装, 所以升级的话肯定是没戏的, 其次, 也是由于某些模块很少使用到, 所以一直没暴露出来问题

  生产环境oom现象: 生产上刚启动一段时间内是可以正常使用的,  几天之后服务就挂了, 必须重启之后才能重新对外提供服务, 通过日志可以发现报错:OutOfMemoryError  PermGen  space,  这种情况用脚都能猜出来是内存泄露,  也是jvm中永久代内存有些一直没有被回收, 而且还不断的往永久代中新增东西

  网上的解决方案: 使用-XX:MaxPermSize调整一下永久代的最大空间!  尼玛, 这就很离谱, 这不是治标不治本么, 这种方法顶多就是你的系统一个星期oom变为了两三个星期再oom一次了, 如果在oom之前又有新的项目上线重启一下服务,  都可以苟活一段时间了

  下面就简单介绍一下我这次出现的问题吧

1. 啥是永久代

  首先要知道,  方法区是jvm规范, 而永久代是方法区的实现, 他们就类似于接口和实现类的关系, 所以下面我把方法区和永久代看作是等价的

  在我们java程序要启动的时候, 就需要加载很多的类, 可以把每个类看作是class文件,   通过类加载器加载进了永久代, 我们就把永久代中数据看作是类的元数据, 其中包含了常量池, 字段, 方法等信息

  再深入一点, 我们知道java之中还有一个Class对象, 这个Class对象就是根据永久代中的元数据生成的, 这放在java堆中;

  实例化对象的时候有两种方式:

  方式一: 根据元数据来进行实例化的,  下图所示, Class对象对于同一个类加载器加载的,  只能有一个,  和方法区中元数据一一对应,而实例可以有多个

  方式二: 使用反射根据Class对象进行实例化对象

  我们常用的获取Class对象有三种方式: 

  (1)Class.forName("ClassName"):通过类的元数据中的Class对象引用获得Class对象

  (2)object.getClass():通过实例对象中保存的对类的元数据的引用获取类的元数据,再通过元数据中对Class对象的引用获取Class对象

  (3)ClassName.class:通过类的元数据中的Class对象引用获得class对象

 

 

 

 2. 永久代有大小么?

  从上面的图中可以看到永久代其实也是属于堆中一部分,  可以在启动的时候设置永久代的容量和最大的容量,  例如: -XX:PermSize=64m,-XX:MaxPermSize=128m

  那么问题来了, 永久代如果设置太小了怎么办? 结果就是java程序启动的时候,  都会报永久代oom,  或者项目启动了之后需要动态加载第三方jar包的时候, 发生oom

  永久代进行gc的条件: 

  (1) 该类的实例都被回收 

  (2) 加载该类的classLoader已经被回收

  (3) 该类不能通过反射访问到其方法,而且该类的java.lang.class没有被引用 当满足这3个条件时,是可以回收,但回不回收还得看jvm

  如果你的服务器上oom了, 第一反应不是重启, 而是最快时间拷贝一份堆栈快照, 可以使用jmap -dump:live, format=b,file=dumpxxx.hprof pid,  其中dump表示要导出一份堆栈信息文件, live表示要把活的对象导出,  format=b, 文件格式是二进制;  file表示要导出的文件保存的全路径;  pid表示进程id

 

3. OOM具体原因和解决方案

  这里就不放堆栈信息了, 公司内部的东西,  反正就是MAT工具进行一顿猛分析, 发现里面那种ClassLoader$ApplicantClassLoader比较多, 推测加载的元数据信息到永久代中很多, 然后其他的信息也看不出来啥, 水平比较菜o(╥﹏╥)o

  然后我尝试在本地搭建了环境, 试试能不能复现出来, 调整了一下堆栈参数, 使用jmeter压测了几个小时之后, 还真的复现了

  因为以前是没有出现过的这个问题,  先检查代码, 都是业务代码, 没有涉及到cglib动态代理这种的使用!

  肯定是这次上线的版本新功能有哪里涉及到了,  经过排查,  这次新的东西就是多使用到了一个框架层次的工具类, 是对缓存的抽象, 通过生成缓存的代理类, 去操作redis, 猜测就是这个类的影响

  最简单直接的排查方式就是自己手动写一个redis的工具类, 然后统统替换掉那个工具类, 然后压测一段时间, 就没有这个问题了

  通过手动debug的方式, 最后到了一个Proxy的工具类中,  就是这里涉及到了cglib动态代理, 不断的拼接java类字符串, 然后加载到方法区中, 生成class对象, 然后通过class对象反射生成实例, 可能就是这里的原因, 由于这个类看的不是很懂, 我就去github上的dubbo的issue搜了一下oom,看有没有相类似的问题,  还真的被我搜出来了, 

 

  继续点进去发现了一些很有意思的东西

  (1) proxy instance cause a PermSpace OOM #6742  

  因为 org.apache.dubbo.common.bytecode.Proxy 中使用的Proxy对象缓存导致。PROXY_CACHE_MAP 缓存的Proxy实例,使WeakReference,full GC 后会释放该Proxy实例再次申请对象实例时,Proxy会重新创建Proxy的Class对象,最终导致PermGen space内存溢出。应该修改为缓存该Proxy Class,而不是 Proxy 对象实例。

  (2) commit记录

  最大的改变其实就是增加了这么一个Map, 用于缓存生成代理类的Class对象,  每次先去PROXY_CACHE_MAP看看实例对象有没有, 没有的话, 再去PROXY_CLASS_MAP中找到对应的Class对象, 如果还是没有才会去拼接java类, 然后加载到永久代中, 然后再缓存Class对象,  以后就不需要再加载了

  而由于我这里dubbo项目比较老,  每次都要去加载类的信息到永久代中, 时间久了, 永久代就挂了

 

  既然找到了问题所在, 改的话, 也就简单了, 直接打开dubbo的源码抄就好了, 毕竟我可是ctrl+c  ctrl+v的高手, 这点我还是蛮有自信的

  这个问题只有在dubbo2.7的版本才被修复,  可以打开2.7.x版本的org.apache.dubbo.common.bytecode.Proxy类的代码, 抄一抄就解决了

 

4. 总结

   一个oom的问题涉及的东西是真的多,  首先涉及到要很了解java类的加载机制, 以及jvm内存结构,  cglib动态代理, 在服务器端使用jmap导出堆栈信息,  MAT内存分析工具的使用,  jmeter性能压测,  dubbo源码阅读以及调试,  github查找相关问题,  真尼玛的麻烦o(╥﹏╥)o

  而且这次真的是体会到了一活跃的开源社区的强大之处, 要是一个使用的人都比较少的框架,  都没几个人, 靠自己很难发现问题的所在之处, 并不是每个人都有修改源码的能力的, 害

  再一次提醒我们要多提高技术, 有时间就关注开源社区的一些问题以及最新动向

有关简单记录一次远古版本dubbo发生的PermGen space异常的更多相关文章

  1. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  2. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  3. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  4. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  5. ruby-on-rails - 在 ruby​​ .gemspec 文件中,如何指定依赖项的多个版本? - 2

    我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这

  6. ruby-on-rails - Rails - 乐观锁定总是触发 StaleObjectError 异常 - 2

    我正在学习Rails,并阅读了关于乐观锁的内容。我已将类型为integer的lock_version列添加到我的articles表中。但现在每当我第一次尝试更新记录时,我都会收到StaleObjectError异常。这是我的迁移:classAddLockVersionToArticle当我尝试通过Rails控制台更新文章时:article=Article.first=>#我这样做:article.title="newtitle"article.save我明白了:(0.3ms)begintransaction(0.3ms)UPDATE"articles"SET"title"='dwdwd

  7. ruby - 简单获取法拉第超时 - 2

    有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

  8. ruby - #之间? Cooper 的 *Beginning Ruby* 中的错误或异常 - 2

    在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee

  9. ruby-on-rails - Rails 5 Active Record 记录无效错误 - 2

    我有两个Rails模型,即Invoice和Invoice_details。一个Invoice_details属于Invoice,一个Invoice有多个Invoice_details。我无法使用accepts_nested_attributes_forinInvoice通过Invoice模型保存Invoice_details。我收到以下错误:(0.2ms)BEGIN(0.2ms)ROLLBACKCompleted422UnprocessableEntityin25ms(ActiveRecord:4.0ms)ActiveRecord::RecordInvalid(Validationfa

  10. ruby-on-rails - 如果我将 ruby​​ 版本 2.5.1 与 rails 版本 2.3.18 一起使用会怎样? - 2

    如果我使用ruby​​版本2.5.1和Rails版本2.3.18会怎样?我有基于rails2.3.18和ruby​​1.9.2p320构建的rails应用程序,我只想升级ruby的版本,而不是rails,这可能吗?我必须面对哪些挑战? 最佳答案 GitHub维护apublicfork它有针对旧Rails版本的分支,有各种变化,它们一直在运行。有一段时间,他们在较新的Ruby版本上运行较旧的Rails版本,而不是最初支持的版本,因此您可能会发现一些关于需要向后移植的有用提示。不过,他们现在已经有几年没有使用2.3了,所以充其量只能让更

随机推荐