相关文章推荐
闷骚的黑框眼镜  ·  如何查看github ...·  1 年前    · 
礼貌的金鱼  ·  javascript - ...·  1 年前    · 

1. JVM crash了

产品发来一份crash report, 什么是crash report请参考我的前期博客( http://blog.csdn.net/raintungli/article/details/7642575 ),下面是截取了crash report的部分,用于分析:
# Problematic frame:
# V  [libjvm.so+0x5bbf05]  instanceKlass::oop_follow_contents(ParCompactionManager*, oopDesc*)+0x2c5
Stack 信息:
Stack: [0x00007fa9482b3000,0x00007fa9483b4000],  sp=0x00007fa9483b2a10,  free space=1022k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V  [libjvm.so+0x5bbf05]  instanceKlass::oop_follow_contents(ParCompactionManager*, oopDesc*)+0x2c5
V  [libjvm.so+0x87504c]  ParCompactionManager::follow_marking_stacks()+0x1ec
V  [libjvm.so+0x85c138]  MarkFromRootsTask::do_it(GCTaskManager*, unsigned int)+0x78
V  [libjvm.so+0x55813f]  GCTaskThread::run()+0x12f
V  [libjvm.so+0x821ca8]  java_start(Thread*)+0x108

  • 看到里面的栈信息是GCTaskThread线程,初步判断在执行GC的时候发生了crash,代码段在0x5bbf05,函数是instanceKlass::oop_follow_content。
  • InstanceKlass 就是我们常说的class对象,因为是在GC的时候出现问题,具体的代码段通常是在GC部分并不能容易的判断发生了什么,而我们更需要知道的是GC的时候在处理哪个对象出了问题

2. GC 的参数

JVM在GC的控制参数中,有一个GC前进行校验的参数,在校验过程中当发生地址异常的化会打印出异常的地址,并且让JVM crash,因为这个参数每一次GC都要检查,包括新生代的GC,影响一定的性能,并不适合在产品环境中使用,但对发现GC中的对象问题,却非常有帮助。
-XX:+VerifyBeforeGC -XX:+VerifyAfterGC
产品的日志打印出了异常的对象地址:
Failed: 0x000000079ac5fe30 -> 0x0000000410bc55c0

3. SA 工具之CLHSDB

知道错误的对象地址,需要分析core dump知道哪个对象出了问题,在Linux上通常会用GDB,但是这并不适合分析我们初学者,尤其是我们并不是非常清楚对象的结构和布局,我们需要利用JMV提供的SA工具
JVM提供的HSDB工具是一款非常好的工具,通过工具能查看和分析运行中的JVM的heap对象,当然也可以常看core dump, 但问题是HSDB是有UI界面的,我们在linux系统中通常没有UI界面,用过HSDB工具,可以发现当我们启动命令控制台的时候,实际上HSDB是把CLHSDB嵌入在了HSDB的图形界面里,那我们可以使用CLHSDB来通过命令行的方式进行dump分析,关于如何使用HSDB工具,可参考 博客

3.1 如何启动CLHSDB

java -cp .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB
Attach 一个core dump:
java -cp .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB $JAVA_HOME/bin/java 99083
这里有几个注意点:
  • 版本问题,如果产品上装了多个JVM环境的化,注意core dump要和JVM的分析的版本一致
  • SA环境需要root权限

3.2 分析对象

在前面提到的日志中,错误的对象地址是:
Failed: 0x000000079ac5fe30 -> 0x0000000410bc55c0

先扫描一下0x000000079ac5fe30附近的地址的对象



可以看到0x000000079ac5fe30地址最近的对象的地址0x000000079ac5fe08这是一个MemberName对象,继续查看地址0x000000079ac5fe30的内容



查看一下地址0x0000000782178ab8的对象,就是一个method的对象


这样我们就能构建了地址的 0x000000079ac5fe30 对象
  • 地址0x000000079ac5fe30 是属于0x000000079ac5fe08地址的对象的成员,也就是MemberName对象的成员
  • 通过0x0000000782178ab8的地址分析,这是一个reinvokeTarget的method的地址
我们在来看MemberName的对象结构
 final class More ...MemberName implements Member, Cloneable {
73      private Class<?> clazz;       // class in which the method is defined
74      private String   name;        // may be null if not yet materialized
75      private Object   type;        // may be null if not yet materialized
76      private int      flags;       // modifier bits; see reflect.Modifier
77      //@Injected JVM_Method* vmtarget;
78      //@Injected int         vmindex;
79      private Object   resolution;  // if null, this guy is resolved
}
无论从0x0000000782178ab8的地址对象反向分析,还是从0x000000079ac5fe08地址位移分析,我们都可以很准确的判定,0x000000079ac5fe30对应的是vmtarget的对象。(在JVM里经常会内部修改一些类的内部结构用于记录状态,但是又不能被Java应用修改)

但是有点不对,刚才不是地址是 0x0000000410bc55c0 ,怎么现在变成了0x0000000782178ab8?
要知道这两个地址为何不一样,我们先要对应代码段,地址 0x0000000410bc55c0 是怎么获取到的?Crash report里会有堆栈信息
crash report就不贴了,最后调用的是VerifyFieldColsure:do_oop
class VerifyFieldClosure: public OopClosure {
 protected:
  template <class T> void do_oop_work(T* p) {
    guarantee(Universe::heap()->is_in_closed_subset(p), "should be in heap");
    oop obj = oopDesc::load_decode_heap_oop(p);
    if (!obj->is_oop_or_null()) {
      tty->print_cr("Failed: " PTR_FORMAT " -> " PTR_FORMAT, p, (address)obj);
      Universe::print();
      guarantee(false, "boom");
 public:
  virtual void do_oop(oop* p)       { VerifyFieldClosure::do_oop_work(p); }
  virtual void do_oop(narrowOop* p) { VerifyFieldClosure::do_oop_work(p); }
};

日志里打印的
Failed: 0x000000079ac5fe30 -> 0x0000000410bc55c0
就是这个函数打印出来的,在代码里obj的地址很明显的调用了函数load_decode_heap_oop(p)
inline oop oopDesc::load_decode_heap_oop_not_null(oop* p)       { return *p; }
inline oop oopDesc::load_decode_heap_oop_not_null(narrowOop* p) {
  return decode_heap_oop_not_null(*p);
}
在oop和narrowOop的情况下是不一样的获取地址方式

3. 指针的压缩

在继续分析下去之前,我们先要介绍oop, narrowOop的背景

在JVM 1.6后面为了节省heap的堆内存会使用压缩指针地址的设计,因为对象结构里指向别的对象是指针引用oop,这个地址是保存在Heap中的,保存Bit 64的地址太浪费Heap空间,所以JVM里保存了一个以heap的基地址为基本地址,计算对象真实地址和基本地址差值并且通过位移(shift)来节省空间,该指针定义为narrow_oop而不同于常见的oop
一个小坑:虽然使用了narrow_oop,当指定的heap的地址空间低于一个阀值的情况下会将narrow_oop的基地址和shift都设置为0,也就是不压缩指针可以通过设置参数:-XX:+PrintCompressedOopsMode 打印来判断narrowoop的base和shift

0x0000000410bc55c0 是个无效地址,而0x0000000782178ab8却是个有效地址,对应的是method instance同时也能匹配上MemberName.vmtarget,我们可以认为0x0000000782178ab8的地址是有效的,为何JVM通过decode地址是0x0000000410bc55c0确实个无效地址,非常有可能存在JVM并没有把压缩后的地址保存在vmtarget中,而是直接把真实的地址赋给了vmtarget,为了猜测是否有效,我们来看jvm的代码
void java_lang_invoke_MemberName::adjust_vmtarget(oop mname, oop ref) {
mname->address_field_put(_vmtarget_offset, (address)ref);
}
果然保存的是实际地址,并没有进行地址压缩后保存

4. MethodHandler

虽然我们找到了JVM crash问题的根因,但我们还需要继续深入的找到谁才是罪魁祸首,就是JVM为何会调整vmtarget的值
分析谁调用了adjust_vmtarget函数即可
 void MemberNameTable::adjust_method_entries(methodOop* old_methods, methodOop* new_methods,
                                             int methods_length, bool *trace_name_printed) {
   assert(SafepointSynchronize::is_at_safepoint(), "only called at safepoint");
-  // search the MemberNameTable for uses of either obsolete or EMCP methods
+  // For each redefined method
   for (int j = 0; j < methods_length; j++) {
     methodOop old_method = old_methods[j];
     methodOop new_method = new_methods[j];
-    oop mem_name = find_member_name_by_method(old_method);
-    if (mem_name != NULL) {
-      java_lang_invoke_MemberName::adjust_vmtarget(mem_name, new_method);
-      if (RC_TRACE_IN_RANGE(0x00100000, 0x00400000)) {
-        if (!(*trace_name_printed)) {
-          // RC_TRACE_MESG macro has an embedded ResourceMark
-          RC_TRACE_MESG(("adjust: name=%s",
-                         Klass::cast(old_method->method_holder())->external_name()));
-          *trace_name_printed = true;
-        }
-        // RC_TRACE macro has an embedded ResourceMark
-        RC_TRACE(0x00400000, ("MemberName method update: %s(%s)",
-                              new_method->name()->as_C_string(),
-                              new_method->signature()->as_C_string()));
-      }

很幸运,只有methodhandles.cpp调用,而函数adjust_method_entries,只在redefineclass的时候调用就是在instrument的时候,目前比较红火的RASP技术的核心关键,关于instrument的博客请参考本人的instrument的系列博客:( http://blog.csdn.net/raintungli/article/details/51593269

5. 如何修复?

既然问题出现在地址压缩上,那么修复就变的非常简单,只要压缩地址后保存就可以了
mname->address_field_put(_vmtarget_offset, (address)ref);
改成
mname->obj_field_put(_vmtarget_offset, new_method);

如果你不想修改代码?
  • 一种方法比较简单,就是instrument的时候不修改methodhandle的类就好
  • 既然问题出在压缩指针上,不压缩不就没问题了么?JVM提供了环境参数可以控制是否压缩指针
 -XX:+UseCompressedOops

这样一个完成的通过JVM crash 日志和core dump进行JVM的问题定位和分析结束了,希望能对你有所帮助。





案例分享:如何通过JVM crash 的日志和core dump定位和分析Instrument引起的JVM crashhttps://docs.oracle.com/javase/7/docs/we... 来自: weixin_33733810的博客 一般会有hs_err_pidxxxxx.log这么个文件,里面记录了core dump文件在哪 在分析jvm crash 产生的core dump文件需要注意几点:1、jdk必须使用与crash所处... 来自: lydawen的专栏 3.core dump分析 有了core dump文件,接下来要做的就是通过命令去解析此文件,定位具体问题了,主要有以下三个命令:(1)先执行gdb$JAVA_HOME$/bin/javacore-j... 来自: weixin_30540691的博客 其实通过上一篇所讲的core dump方法可以比较方便找到jvm crash问题所在,但这种方法适合crash比较频繁,容易获取到core dump文件,如果没有这个文件,那怎么可以查到问题呢。现在回... 来自: 海阔天空 1.core dump介绍        程序异常退出(crash)时会自动生成一个core文件,包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理等信息,也就是把程序当时工作的状态存储成一个文件... 来自: qfzhangwei的专栏 java heap dump和java thread dump是用来分析jvm两种snapshoot。java heap dump是jvm heap部分的镜像,包含了所有object的信息,ibm的h... 来自: bkhh 声明:该博文转自http://maping930883.blogspot.com,热爱java,热爱生活 WebLogic Server是运行在JVM当中的,当Server Crash后,通常会产生一... 来自: tingyun163的博客 JVM crash分析Java程序运行的时候,遇到了coredump的现象。最后定位到时JIT导致的bug。http://www.oracle.com/technetwork/java/javase/... 来自: wy674396380的专栏 Failedtowritecoredump.MinidumpsarenotenabledbydefaultonclientversionsofWindows什么是Minidump?为什么notenab... 来自: qq_36083215的博客 默认情况下所有的操作系统都会在操作系统状态发生变化,或者说发生了异常时,会生成系统级别的崩溃日志,对应的JVM也创造了类似的机制。一般情况下,JVM会创造两种类型的崩溃文件,即文本形式的、二进制形式的... 来自: qq_34446716的博客 当jvm出现致命错误时,会生成一个错误文件 hs_err_pid.log,其中包括了导致jvm crash的重要信息,可以通过分析该文件定位到导致crash的根源,从而改善以保证系统稳定。当出现cra... 来自: Viking的博客 声明:该博文转自http://maping930883.blogspot.com,热爱java,热爱生活Server Crash时,JVM强行退出,并产生一个 server core 文件。该文件是 ... 来自: tingyun163的博客 原文链接:https://www.jianshu.com/p/0d41057ed973今天,刚部署的应用上,随便点点,系统崩溃了,看日志这个是jvm虚拟机崩溃日志下载下来:查(Crash in [li... 来自: weixin_30549657的博客 本文首发于公众号:javaadu简单介绍构建高性能的Java应用过程中,必然会遇到各种各样的问题,像CPU飙高、内存泄漏、应用奔溃,以及其他疑难杂症,这时可以使用Serviceability Agen... 来自: weixin_30597269的博客 前些天,搞JNI的时候,报了个JVM崩溃的错。错误信息如下:## An unexpected error has been detected by HotSpot Virtual Machine:##... 来自: weixin_34163553的博客 大学四年,看课本是不可能一直看课本的了,对于学习,特别是自学,善于搜索网上的一些资源来辅助,还是非常有必要的,下面我就把这几年私藏的各种资源,网站贡献出来给你们。主要有:电子书搜索、实用工具、在线视频... 来自: 帅地 简介在程序开发过程中,在参数传递,函数返回值等方面,越来越多的使用JSON。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,同时也易于机器解析和生成、易于理... 来自: 一枕江风 作者 | Rocky0429来源 | Python空间大家好,我是 Rocky0429,一个喜欢在网上收集各种资源的蒟蒻…网上资源眼花缭乱,下载的方式也同样千奇百怪,比如 BT 下载,磁力链接,网... 来自: Rocky0429 周末了,人一旦没有点事情干,心里就瞎想,而且跟几个老男人坐在一起,更容易瞎想,我自己现在也是 30 岁了,也是无时无刻在担心自己的职业生涯,担心丢掉工作没有收入,担心身体机能下降,担心突...... 来自: 嵌入式Linux 这是《计算机网络》系列文章的第二篇文章我们第一篇文章讲述了计算机网络的基本概念,互联网的基本名词,什么是协议以及几种接入网以及网络传输的物理媒体,那么本篇文章我们来探讨一下网络核心、交换网络、时延、丢... 来自: c旋儿的博客 近日闲来无事,总有一种无形的力量萦绕在朕身边,让朕精神涣散,昏昏欲睡。可是,像朕这么有职业操守的社畜怎么能在上班期间睡瞌睡呢,我不禁陷入了沉思。。。。突然旁边的IOS同事问:‘嘿,兄弟,我发现一个网站... 来自: vio小黑 【前言】  收到一封来信,赶上各种事情拖了几日,利用今天要放下工作的时机,做个回复。  2020年到了,就以这一封信,作为开年标志吧。【正文】  您好,我是一名现在有很多困惑的大二学生。有一些问题想要... 来自: 迂者-贺利坚的专栏 从Java 9开始,Java版本的发布就让人眼花缭乱了。每隔6个月,都会冒出一个新版本出来,Java 10 , Java 11, Java 12, Java 13, 到2020年3月份,...... 来自: 码农翻身 【CSDN编者按】1月2日,阿里巴巴发布《达摩院2020十大科技趋势》,十大科技趋势分别是:人工智能从感知智能向认知智能演进;计算存储一体化突破AI算力瓶颈;工业互联网的超融合;机器间大规模协作成为可... 来自: CSDN资讯 首先介绍下在本文出现的几个比较重要的概念:函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算... 来自: 阿里云云栖号 周末躺在床上看《拯救大兵瑞恩》周末在闲逛的时候,发现了两个优秀的 IDE 插件,据说可以提高代码的质量,我就安装了一下,试了试以后发现,确实很不错,就推荐给大家。01、Alibaba Java 代码规... 来自: 沉默王二 目录1、导入库文件2、设计GUI3、调用摄像头4、实时图像处理4.1、阈值二值化4.2、边缘检测4.3、轮廓检测4.4、高斯滤波4.5、色彩转换4.6、调节对比度5、退出系统初学OpenCV图像处理的... 来自: 不脱发的程序猿 人才需求一线城市共发布岗位38115个,招聘120827人。其中beijing 22805guangzhou 25081shanghai 39614shenzhen 3... 来自: juwikuang的专栏 相信大家时不时听到程序员猝死的消息,但是基本上听不到产品经理猝死的消息,这是为什么呢?我们先百度搜一下:程序员猝死,出现将近700多万条搜索结果:搜索一下:产品经理猝死,只有400万条的搜索结果,从搜... 来自: 曹银飞的专栏 声明:本文以jdk1.8为主!搞定HashMap作为一个Java从业者,面试的时候肯定会被问到过HashMap,因为对于HashMap来说,可以说是Java集合中的精髓了,如果你觉得自己对它掌握的还不... 来自: 编码之外的技术博客 前言:你所做的事情,也许暂时看不到成果。但不要灰心,你不是没有成长,而是在扎根。程序员圈经常流行的一句话:“不要重复造轮子”。在计算机领域,我们将封装好的组件、库,叫做轮子。因为它可以拿来直接用,直接... 来自: 启舰 每天都会收到很多读者的私信,问我:“二哥,有什么推荐的学习网站吗?最近很浮躁,手头的一些网站都看烦了,想看看二哥这里有什么新鲜货。”今天一早做了个恶梦,梦到被老板辞退了。虽然说在我们公司,只有我辞退老... 来自: 沉默王二 Windows可谓是大多数人的生产力工具,集娱乐办公于一体,虽然在程序员这个群体中都说苹果是信仰,但是大部分不都是从Windows过来的,而且现在依然有很多的程序员用Windows。所以,今天我就把我... 来自: 编码之外的技术博客 职场上有很多辛酸事,很多合伙人出局的故事,很多技术骨干被裁员的故事。说来模板都类似,曾经是名校毕业,曾经是优秀员工,曾经被领导表扬,曾经业绩突出,然而突然有一天,因为种种原因,被裁员了,...... 来自: caoz的梦呓 依稀记得,毕业那天,我们导员发给我毕业证的时候对我说“你可是咱们系的风云人物啊”,哎呀,别提当时多开心啦????,嗯,我们导员是所有导员中最帅的一个,真的????不过,导员说的是实话,很多人都叫我大神... 来自: 编码之外的技术博客 上次搬家的时候,发了一个朋友圈,附带的照片中不小心暴露了自己的 Chrome 浏览器插件之多,于是就有小伙伴评论说分享一下我觉得还不错的浏览器插件。我下面就把我日常工作和学习中经常用到的一些 Chr... 来自: 不忘初心