JVM Bug:多个线程持有一把锁

JVM线程dump Bug描述

在JAVA语言中,当同步块(Synchronized)被多个线程并发访问时,JVM中会采用基于互斥实现的重量级锁。JVM最多只允许一个线程持有这把锁,如果其它线程想要获得这把锁就必须处于等待状态,也就是说在同步块被并发访问时,最多只会有一个处于RUNNABLE状态的线程持有某把锁,而另外的线程因为竞争不到这把锁而都处于BLOCKED状态。然而有些时候我们会发现处于BLOCKED状态的线程,它的最上面那一帧在打印其正在等待的锁对象时,居然也会出现-locked的信息,这个信息和持有该锁的线程打印出来的结果是一样的(请看下图),但是对比其他BLOCKED态的线程却并没有都出现这种情况。当我们再次dump线程时又可能出现不一样的结果。测试表明这可能是一个偶发的情况,本文就是针对这种情况对JVM内部的实现做了一个研究以寻找其根源。

jstack命令的整个过程

上面提到了线程dump,那么就不得不提执行线程dump的工具---jstack,这个工具是Java自带的工具,和Java处于同一个目录下,主要是用来dump线程的,或许大家也有使用kill -3的命令来dump线程,但这两者最明显的一个区别是,前者的dump内容是由jstack这个进程来输出的,目标JVM进程将dump内容发给jstack进程(注意这是没有加-m参数的场景,指定-m参数就有点不一样了,它使用的是serviceability agent的api来实现的,底层通过ptrace的方式来获取目标进程的内容,执行过程可能会比正常模式更长点),这意味着可以做文件重定向,将线程dump内容输出到指定文件里;而后者是由目标进程输出的,只会产生在目标进程的标准输出文件里,如果正巧标准输出里本身就有内容的话,看起来会比较乱,比如想通过一些分析工具去分析的话,要是该工具没有做过滤操作,很可能无法分析。因此一般情况我们尽量使用jstack,另外jstack还有很多实用的参数,比如jstack pid >thread_dump.log,该命令会将指定pid的进程的线程dump到当前目录的thread_dump.log文件里。

jstack是使用Java实现的,它通过给目标JVM进程发送一个threaddump的命令,目标JVM的监听线程(attachListener)会实时监听传过来的命令(其实attachListener线程并不是一启动就创建的,它是lazy创建启动的),当attachListener收到threaddump命令时会调用thread_dump的方法来处理dump操作(方法在attachListener.cpp里)。

static jint thread_dump(AttachOperation* op, outputStream* out) {
  bool print_concurrent_locks = false;
  if (op->arg(0) != NULL && strcmp(op->arg(0), "-l") == 0) {
    print_concurrent_locks = true;
  }

  // thread stacks
  VM_PrintThreads op1(out, print_concurrent_locks);
  VMThread::execute(&op1);

  // JNI global handles
  VM_PrintJNI op2(out);
  VMThread::execute(&op2);

  // Deadlock detection
  VM_FindDeadlocks op3(out);
  VMThread::execute(&op3);

  return JNI_OK;
}

从上面的方法可以看到,jstack命令执行了三个操作:

VM_PrintThreads:打印线程栈

VM_PrintJNI:打印JNI

VM_FindDeadlocks:打印死锁

三个操作都是交给VMThread线程去执行的,VMThread线程在整个JAVA进程有且只会有一个。可以想象一下VMThread线程的简单执行过程:不断地轮询某个任务列表并在有任务时依次执行任务。任务执行时,它会根据具体的任务决定是否会暂停整个应用,也就是stop the world,这是不是让我们联想到了我们熟悉的GC过程?是的,我们的ygc以及cmsgc的两个暂停应用的阶段(init_mark和remark)都是由这个线程来执行的,并且都要求暂停整个应用。其实上面的三个操作都是要求暂停整个应用的,也就是说jstack触发的线程dump过程也是会暂停应用的,只是这个过程一般很快就结束,不会有明显的感觉。另外内存dump的jmap命令,也是会暂停整个应用的,如果使用了-F的参数,其底层也是使用serviceability agent的api来dump的,但是dump内存的速度会明显慢很多。

VMThread执行任务的过程

VMThread执行的任务称为vm_opration,在JVM中存在两种vm_opration,一种是需要在安全点内执行的(所谓安全点,就是系统处于一个安全的状态,除了VMThread这个线程可以正常运行之外,其他的线程都必须暂停执行,在这种情况下就可以放心执行当前的一系列vm_opration了),另外一种是不需要在安全点内执行的。而这次我们讨论的线程dump是需要在安全点内执行的。

以下是VMThread轮询的逻辑:

void VMThread::loop() {
  assert(_cur_vm_operation == NULL, "no current one should be executing");

  while(true) {
    ...
    //已经获取了一个vm_operation
    if (_cur_vm_operation->evaluate_at_safepoint()) {
        //如果该vm_operation需要在安全点内执行
        _vm_queue->set_drain_list(safepoint_ops);
        SafepointSynchronize::begin();//进入安全点
        evaluate_operation(_cur_vm_operation);
        do {
          _cur_vm_operation = safepoint_ops;
          if (_cur_vm_operation != NULL) {
            do {
              VM_Operation* next = _cur_vm_operation->next();
              _vm_queue->set_drain_list(next);
              evaluate_operation(_cur_vm_operation);
              _cur_vm_operation = next;
              if (PrintSafepointStatistics) {
                SafepointSynchronize::inc_vmop_coalesced_count();
              }
            } while (_cur_vm_operation != NULL);
          }
          if (_vm_queue->peek_at_safepoint_priority()) {
            MutexLockerEx mu_queue(VMOperationQueue_lock,
                                     Mutex::_no_safepoint_check_flag);
            safepoint_ops = _vm_queue->drain_at_safepoint_priority();
          } else {
            safepoint_ops = NULL;
          }
        } while(safepoint_ops != NULL);
        _vm_queue->set_drain_list(NULL);
        SafepointSynchronize::end();//退出安全点
      } else {  // not a safepoint operation
        if (TraceLongCompiles) {
          elapsedTimer t;
          t.start();
          evaluate_operation(_cur_vm_operation);
          t.stop();
          double secs = t.seconds();
          if (secs * 1e3 > LongCompileThreshold) {
            tty->print_cr("vm %s: %3.7f secs]", _cur_vm_operation->name(), secs);
          }
        } else {
          evaluate_operation(_cur_vm_operation);
        }
        _cur_vm_operation = NULL;
      }
    }
    ...
  }

在这里重点解释下在安全点内执行的vm_opration的过程,VMThread通过不断循环从_vm_queue中获取一个或者几个需要在安全点内执行的vm_opertion,然后在准备执行这些vm_opration之前先通过调用SafepointSynchronize::begin()进入到安全点状态,在执行完这些vm_opration之后,调用SafepointSynchronize::end(),退出安全点模式,恢复之前暂停的所有线程让他们继续运行。对于安全点这块的逻辑挺复杂的,仅仅需要记住在进入安全点模式的时候会持有Threads_lock这把线程互斥锁,对线程的操作都需要获取到这把锁才能继续执行,并且还会设置安全点的状态,如果正在进入安全点过程中设置_state为_synchronizing,当所有线程都完全进入了安全点之后设置_state为_synchronized状态,退出的时候设置为_not_synchronized状态。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索安全
, 线程
, 进程
, vm
, jstack
, dump
, 状态
, jstack日志
vm.set
jvm下载、jvm下载官方下载、jvm原理、深入理解java虚拟机、jvm 调优,以便于您获取更多的相关知识。

时间: 2024-11-03 08:31:06

JVM Bug:多个线程持有一把锁的相关文章

jvm(13)-线程安全与锁优化(转)

0.1)本文部分文字转自"深入理解jvm", 旨在学习 线程安全与锁优化 的基础知识: 0.2)本文知识对于理解 java并发编程非常有用,个人觉得,所以我总结的很详细: [1]概述 [2]线程安全 1)线程安全定义:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的:(干货--线程安全定义) [2.1]java 语言中的线程安全(干货-

Java多线程同步问题的探究(二、给我一把锁,我能创造一个规矩)

在上一篇中,我们讲到了多线程是如何处理共享资源的,以及保证他们对资源进行互斥访问所依赖的重要机制:对象锁. 本篇中,我们来看一看传统的同步实现方式以及这背后的原理. 很多人都知道,在Java多线程编程中,有一个重要的关键字,synchronized.但是很多人看到这个东西会感到困惑:"都说同步机制是 通过对象锁来实现的,但是这么一个关键字,我也看不出来Java程序锁住了哪个对象阿?" 没错,我一开始也是对这个问题感到困惑和不解.不过还好,我们有下面的这个例程: 1 public cla

JVM BUG: Internal Error (classFileParser.cpp:3161), pid=1116, tid=3912 Error: ShouldNotReachHere()

涉及到线程安全的部分出现了JVM BUG--不解,Eclipse返回的信息如下, # # A fatal error has been detected by the Java Runtime Environment: # #  Internal Error (classFileParser.cpp:3161), pid=1116, tid=3912 #  Error: ShouldNotReachHere() # # JRE version: 6.0_21-b07 # Java VM: Jav

嵌入式防火墙给每道门配一把锁

传统防火墙只防周边 传统的边缘防火墙只对企业网络的周边提供保护.这些边缘防火墙会在流量从外部的互联网进入企业内部局域网时进行过滤和审查.但是,他们并不能确保企业局域网内部的安全访问.这就好比给一座办公楼的大门加上一把锁,但办公楼内的每个房间却房门大开一样,一旦有人通过了办公楼的大门,便可以随意出入办公楼内任何一个房间.这类网络非常容易受到有目的的攻击.例如,黑客入侵一台已经接入了企业局域网的计算机,一旦获得这台计算机的控制权,他们便可以利用这台机器作为入侵其他系统的跳板. 嵌入式防火墙给每道门配

一把钥匙开一把锁

一把钥匙开一把锁是生活中的一种常识,连三岁大的小孩子都懂得.而且,一把钥匙开一把锁同时还是一种可以推广的理论,也就是教师常说的举一反三的道理,这就不是一般人能掌握的,所以值得一说. 记得去年我还在工地搬砖,一次喝酒过后,我们宿舍的老张头说起一个故事,单位单身宿舍楼是前年夏天装修的,资金控制在牛处长手里,牛处长找马老板接手这一项工程,马老板找一个没有施工资质的野鸡施工队伍把活儿干完了,从工程竣工那一天起,问题就不断,一会儿是门窗垮了,一会儿卫生间漏水,去年我们就多次打电话给马老板让他派人来维修,他

深入JVM剖析Java的线程堆栈_java

在这篇文章里我将教会你如何分析JVM的线程堆栈以及如何从堆栈信息中找出问题的根因.在我看来线程堆栈分析技术是Java EE产品支持工程师所必须掌握的一门技术.在线程堆栈中存储的信息,通常远超出你的想象,我们可以在工作中善加利用这些信息. 我的目标是分享我过去十几年来在线程分析中积累的知识和经验.这些知识和经验是在各种版本的JVM以及各厂商的JVM供应商的深入分析中获得的,在这个过程中我也总结出大量的通用问题模板. 那么,准备好了么,现在就把这篇文章加入书签,在后续几周中我会给大家带来这一系列的专

JVM:如何分析线程堆栈

英文原文:JVM: How to analyze Thread Dump 在这篇文章里我将教会你如何分析JVM的线程堆栈以及如何从堆栈信息中找出问题的根因.在我看来线程堆栈分析技术是Java EE产品支持工程师所必须掌握的一门技术.在线程堆栈中存储的信息,通常远超出你的想象,我们可以在工作中善加利用这些信息. 我的目标是分享我过去十几年来在线程分析中积累的知识和经验.这些知识和经验是在各种版本的JVM以及各厂商的JVM供应商的深入分析中获得的,在这个过程中我也总结出大量的通用问题模板. 那么,准

手工打造一把锁

接上篇(https://yq.aliyun.com/articles/59034 ),我们知道了lock的意义.回到之前的多线程加法操作,你也可以通过pthread提供的互斥锁来保证结果是正确的.但互斥锁本身是如何保证原子性的呢?当然首先获得锁的操作需要是一个指令,而不能用加载-比对-存储这种类似的三条指令:其次,如果是多核系统上,这个指令本身还需要加上lock前缀.x86提供了cmpxchg指令(http://x86.renejeschke.de/html/file_module_x86_id

苹果iOS10公测版Beta1现重大bug:用户ID可能被锁

 7月12日消息,IT之家此前报道了苹果iOS10公测版Beta1推送的消息,如今有用户在Reddit论坛发帖称遇到了苹果ID被锁的bug,这可能导致该ID关联的所有苹果设备无法使用. 当用户启用双重认证选项时,苹果的密码重置功能就会出现问题.也就是说,当用户遇到设备被锁的问题,想要找回密码时就会遇到困难. 帖子简要内容如下: 在刷了苹果iOS10公测版Beta1后,我所有的苹果设备突然要求我在系统设置中输入密码,在我输入密码时,设备显示"由于安全问题,你的设备已被锁定".在我访问iF