程序死锁的测试方法、装置和设备与流程

文档序号:17489055发布日期:2019-04-23 20:16阅读:248来源:国知局
程序死锁的测试方法、装置和设备与流程

本公开涉及计算机技术领域,尤其是涉及程序死锁的测试方法、装置和设备。



背景技术:

java是目前最流行的编程语言之一,在全球有非常多的大型服务程序(如web程序)采用java作为主要编程语言。java语言编写的程序被编译为字节码后,由jvm(javavirtualmachine,java虚拟机)负责解释执行。

java语言支持多线程机制,一般大型服务程序的开发者在书写java程序时,都会编写代码启动多个不同的线程来并发地完成任务。如果程序开发者编写的代码,采用了不恰当的线程间同步机制,例如参见图1,如果线程t1已加锁lock1(lock1为锁对象标识),然后再请求加锁lock2(lock2为锁对象标识),然后才能执行一个事务;此时如果lock2已被线程t2占用,线程t1就会停止执行,等待线程t2释放lock2;如果线程t2在持有锁lock2期间,期望加锁lock1,就必须等待线程t1释放lock1,此时线程t2就会停止执行,等待t1释放lock1;从上述描述看,t1和t2都会停止执行,且永远没有时机再恢复执行,从而导致死锁现象。

死锁现象一旦发生,服务程序就有很大可能无法继续正常提供服务。现有技术中通常在程序发布前,对程序进行评审以检查程序中的死锁问题,由于大型程序的设计和编码都比较复杂,存在隐患的代码的调用关系也非常庞大,通过肉眼发现死锁隐患的概率较低。



技术实现要素:

有鉴于此,本公开的目的在于提供一种程序死锁的测试方法、装置和设备,以实现死锁隐患的自动检测,有效提高发现死锁隐患的概率。

为了实现上述目的,本公开采用的技术方案如下:

第一方面,本公开提供了一种程序死锁的测试方法,包括:在被测试程序启动后,侦听被测试程序中的线程的处理指令;如果侦听到该线程的锁指令,记录该线程的锁对象信息序列;其中,该锁指令包括加锁指令和解锁指令;该锁对象信息序列包括:该加锁指令对应的加锁对象信息和该解锁指令对应的解锁对象信息;当被测试程序停止运行时,获取上述各个线程的锁对象信息序列中的最简加锁序列;其中,该最简加锁序列为带有嵌套关系的加锁对象序列;将各个线程的最简加锁序列中相同的最简加锁序列合并;绘制合并后的最简加锁序列的有向图;基于绘制出的有向图确定被测试程序的死锁信息。

第二方面,本公开提供了一种程序死锁的测试装置,包括:指令侦听模块,用于在被测试程序启动后,侦听被测试程序中的线程的处理指令;信息记录模块,用于当侦听到线程的锁指令时,记录该线程的锁对象信息序列;其中,该锁指令包括加锁指令和解锁指令;该锁对象信息序列包括:该加锁指令对应的加锁对象信息和该解锁指令对应的解锁对象信息;序列获取模块,用于当被测试程序停止运行时,获取上述各个线程的锁对象信息序列中的最简加锁序列;其中,该最简加锁序列为带有嵌套关系的加锁对象序列;序列合并模块,用于将各个线程的最简加锁序列中相同的最简加锁序列合并;绘制模块,用于绘制合并后的最简加锁序列的有向图;死锁确定模块,用于基于绘制出的有向图确定被测试程序的死锁信息。

第三方面,本公开实施方式提供了一种程序死锁的测试设备,包括处理器和机器可读存储介质,所述机器可读存储介质存储有能够被所述处理器执行的机器可执行指令,所述处理器执行所述机器可执行指令以实现上述方法。

第四方面,本公开实施方式提供了一种机器可读存储介质,所述机器可读存储介质存储有机器可执行指令,所述机器可执行指令在被处理器调用和执行时,所述机器可执行指令促使所述处理器实现上述方法。

上述程序死锁的测试方法、装置、设备和机器可读存储介质,在被测试程序启动后,通过侦听被测试程序中线程的处理指令的方法,记录各个线程的用于表征该线程加锁与解锁流程的锁对象信息序列,并在被测试程序停止运行后,获取各个线程的锁对象信息序列包含的带有嵌套关系的最简加锁序列,然后根据该最简加锁序列绘制有向图,从而根据该有向图确定被测试程序的死锁信息。该方式通过自动收集被测试程序运行中死锁隐患的相关数据,以该相关数据为依据利用有向图确定相应程序的死锁信息,从而实现了死锁隐患的自动检测,有效提高了发现死锁隐患的概率。

本公开的其他特征和优点将在随后的说明书中阐述,或者,部分特征和优点可以从说明书推知或毫无疑义地确定,或者通过实施本公开的上述技术即可得知。

为使本公开的上述目的、特征和优点能更明显易懂,下文特举较佳实施方式,并配合所附附图,作详细说明如下。

附图说明

为了更清楚地说明本公开具体实施方式或现有技术中的技术方案,下面将对具体实施方式或现有技术描述中所需要使用的附图作简单地介绍,显而易见地,下面描述中的附图是本公开的一些实施方式,对于本领域普通技术人员来讲,在不付出创造性劳动的前提下,还可以根据这些附图获得其他的附图。

图1为现有技术提供的一种程序运行过程中死锁现象示意图;

图2为本公开实施方式提供的一种程序死锁的测试方法的流程示意图;

图3为本公开实施方式提供的另一种程序死锁的测试方法的流程示意图;

图4为本公开实施方式提供的一种有向图绘制的流程示意图;

图5为本公开实施方式提供的一种扩展后的jvm进行死锁检测的示意图;

图6为本公开实施方式提供的一种具体实现实例中各线程的执行指令的时序图;

图7为本公开实施方式提供的一种被测试程序启动后映射表中记录的数据示意图;

图8为本公开实施方式提供的一种第一定时器超时归并处理后的映射表中记录的数据示意图;

图9为本公开实施方式提供的一种第二定时器超时归并处理后的映射表中记录的数据示意图;

图10为本公开实施方式提供的有向图t1的示意图;

图11为本公开实施方式提供的有向图t2的示意图;

图12为本公开实施方式提供的一种程序死锁的测试装置的结构示意图;

图13为本公开实施方式提供的另一种程序死锁的测试装置的结构示意图;

图14为本公开实施方式提供的另一种程序死锁的测试装置的结构示意图;

图15为本公开实施方式提供的一种程序死锁的测试设备的结构示意图。

具体实施方式

为使本公开实施方式的目的、技术方案和优点更加清楚,下面将结合附图对本公开的技术方案进行清楚、完整地描述,显然,所描述的实施方式是本公开一部分实施方式,而不是全部的实施方式。基于本公开中的实施方式,本领域普通技术人员在没有做出创造性劳动前提下所获得的所有其他实施方式,都属于本公开保护的范围。

需要说明的是,上述各方法实施方式均采用递进的方式描述,每个实施方式重点说明的都是与其他实施方式的不同之处,各个实施方式之间相同相似的部分互相参见即可。

目前jvm在解释执行java字节码时,可能会在不同的cpu(centralprocessingunit,中央处理器)上并行执行。在这种并行处理的过程中,如果程序开发者编写的代码,采用了不恰当的线程间同步机制,则可能会导致死锁,基于此,本公开实施例提供了一种程序死锁的测试方法、装置、设备及机器可读存储介质,以实现了死锁隐患的自动检测,有效提高了发现死锁隐患的概率。

本公开实施例提供的程序死锁的测试技术,可以对java和其它类似程序进行死锁检测,如果是java程序,可以但不限于在openjdk的基础上,定制开发jvm,如在现有jvm进行扩展,增加上述程序死锁的测试方法的对应功能模块,然后利用该扩展后的jvm执行上述程序死锁的测试方法。

参见图2所示的一种程序死锁的测试方法的流程示意图,该方法应用于程序测试阶段,具体应用于运行被测试程序的设备,该设备可以是服务器、路由器、交换机或者其它设备。如图2所示,该方法包括如下步骤:

步骤s201,在被测试程序启动后,侦听该被测试程序中的线程的处理指令。

本公开实施例中,侦听的处理指令可以包括线程启动指令、锁指令(例如:加锁指令和解锁指令)及线程结束指令。

步骤s202,如果侦听到上述线程的锁指令,记录上述线程的锁对象信息序列。

考虑到被测试程序运行过程中,启动的线程非常多,同一线程还可能在不同的时间启动。为了更好的区分各个线程,可以基于线程标识和线程启动时间生成线程的身份标识,后续以该线程的身份标识记录该线程的锁对象信息序列。其中,线程的身份标识可以是线程id与线程启动时间的组合,例如:线程t1的启动时间为1:01,则该线程t1的身份标识可以是:t1,1:01,或者是1:01,t1,也可以是t1+1:01等形式。当然也可以基于线程id与线程启动时间进行运算,重新生成新的字符串,例如对线程id与线程启动时间进行哈希运算等,将运算结果作为线程的身份标识。

为了便于记录各个线程的锁对象信息序列,本实施例中采用在映射表中以链表形式记录,该映射表可以包括关键字(key)和映射值(value)两个字段,其中,关键字用于记录线程的身份标识,映射值用于记录该线程的锁对象信息序列。具体地,锁对象信息序列以链表形式记录表示锁对象信息按照侦听到的加锁指令和解锁指令发生的先后顺序,有序记录。例如:侦听到的锁指令的顺序为:加锁a,加锁b,解锁b,解锁a;则链表形式的锁对象信息序列可以是:加锁a的信息-加锁b的信息-解锁b的信息-解锁a的信息。

步骤s203,当上述被测试程序停止运行时,获取各个线程的锁对象信息序列中的最简加锁序列。

其中,最简加锁序列为带有嵌套关系的加锁对象序列。

在对被测试程序进行测试过程中,可以根据测试进度手动结束测试,也可以设置定时器结束测试,测试结束后,被测试程序将停止运行。

如果线程t1的锁对象信息序列为加锁a-加锁b,线程t2的锁对象信息序列为加锁b-加锁a,那么如果线程t1和线程t2启动时间相同或相差很小,则线程t1完成加锁a之后,期望加锁b,此时线程t2已经加锁b,期望加锁a,则线程t1和线程t2相互等待,持续下去形成死锁。为了能够检测出死锁隐患,本实施例对各个线程的锁对象信息序列进行分析,考虑到锁对象信息序列中既有加锁对象,也有解锁对象,而导致死锁现象为加锁对象相互嵌套引起,因此本实施例获取的最简加锁序列为带有嵌套关系的加锁对象序列。

带有嵌套关系的加锁对象序列中的嵌套关系指在同一个加锁对象序列中,存在一个加锁对象的基础上,又出现另一个加锁对象(锁对象指的就是线程锁)。例如线程t1的锁对象信息序列:加锁a-加锁b-加锁c,其嵌套关系为加锁对象a嵌套加锁对象b、加锁对象a和加锁对象b嵌套加锁对象c,则线程t1的最简加锁序列为加锁a-加锁b-加锁c;再如线程t2的锁对象信息序列:加锁a-加锁b-加锁c-解锁b-加锁e,因为在加锁e之前存在解锁b,所以第一个嵌套关系为:加锁对象a嵌套加锁对象b、加锁对象a和加锁对象b嵌套加锁对象c,该第一个嵌套关系对应的最简加锁序列为:加锁a-加锁b-加锁c;第二个嵌套关系为加锁对象a嵌套加锁对象c,加锁对象a和加锁对象c嵌套加锁对象e,该第二个嵌套关系对应的最简加锁序列为加锁a-加锁c-加锁e;因此线程t2的最简加锁序列为两个,分别是:加锁a-加锁b-加锁c和加锁a-加锁c-加锁e。

以线程t3的锁对象信息序列:加锁a-加锁b-加锁c-解锁c-加锁e-解锁e-解锁b-解锁a为例,其对应的最简加锁序列包括序列1:加锁a-加锁b-加锁c;和序列2:加锁a-加锁b-加锁e。

上述最简加锁序列可以在被测试程序运行过程中生成并记录于映射表中,例如:当有线程结束时,基于该线程的锁对象信息序列生成该线程的最简加锁序列,并记录于映射表中。或者,在被测试程序运行过程中,每隔一段时间,基于映射表中已经结束的线程的锁对象信息序列生成该线程的最简加锁序列。

上述最简加锁序列也可以在被测试程序停止运行后统一生成。实际应用中,可以根据需要选择在被测试程序运行过程中,或被测试程序运行结束后生成线程的最简加锁序列,本公开实施例对此不进行限定。

步骤s204,将各个线程的最简加锁序列中相同的最简加锁序列合并。

其中,最简加锁序列中相同指的是两个线程包含的最简加锁序列一致,如果线程1包含的最简加锁序列为序列1和序列2;线程2包含的最简加锁序列也为序列1和序列2,则线程1和线程2的最简加锁序列合并为一个线程的最简加锁序列,合并后可以仅记录线程1的最简加锁序列,删除线程2的最简加锁序列。

步骤s205,绘制合并后的最简加锁序列的有向图。

为了描述方便,最简加锁序列中包含的各个加锁对象信息均可以称为最简加锁序列中的元素。在绘制有向图时,可以以最简加锁序列中的元素为该有向图的节点,以各个元素之间的相邻关系为依据绘制节点之间的连线,并为各个连线配置对应的线程信息,其中该线程信息可以但不限于包括线程的身份标识。

步骤s206,基于绘制出的有向图确定被测试程序的死锁信息。

因为本公开实施例中基于最简加锁序列生成有向图,如果有向图中有环路,则被测试程序可能存在死锁现象,因此生成死锁信息,为了便于查找到引起死锁的代码位置,可以在死锁信息中包含环路上的节点信息。

上述方法,在被测试程序启动后,通过侦听被测试程序中线程的处理指令的方法,记录各个线程的用于表征该线程加锁与解锁流程的锁对象信息序列,并在被测试程序停止运行后,获取各个线程的锁对象信息序列包含的带有嵌套关系的最简加锁序列,然后根据该最简加锁序列绘制有向图,从而根据该有向图确定被测试程序的死锁信息。该方式通过自动收集被测试程序运行中死锁隐患的相关数据,以该相关数据为依据利用有向图确定相应程序的死锁信息,从而实现了死锁隐患的自动检测,有效提高了发现死锁隐患的概率。

考虑到被测试程序后续还用于实际应用中,为了避免在该程序实际运行中,也执行上述测试方法,可以为被测试程序设置一个启动参数(例如:dla-enable),如果将该启动参数设置为1,表示该程序运行时需要执行上述测试方法,即此时被测试程序开启了死锁检测功能。如果将该启动参数设置为0,表示该程序作为正常运行程序,不需要执行上述测试方法,默认情况下,该启动参数设置为0。

在具体实现时,上述侦听被测试程序中的线程的处理指令的步骤,具体还可以包括:如果侦听到该被测试程序有线程启动,根据该线程的标识和启动时间生成该线程的身份标识,并在映射表的关键字中添加线程的身份标识,继续侦听该线程的处理指令。基于此,上述步骤s202包括:如果侦听到上述线程的锁指令,在上述映射表的关键字中查找上述线程的身份标识;按照锁指令发生的先后顺序,将该锁指令的锁对象信息添加于上述线程的身份标识对应的映射值中,得到链表形式的锁对象信息序列。这种链表形式的锁对象信息序列,可以有序地记录锁指令对应的锁对象信息,便于后续死锁现象的查找,提升了测试效率。

为了便于定位死锁,可以对锁指令对应的信息进行记录。同时,考虑到记录信息的有效性,本公开实施例中对加锁指令和解锁指令对象的信息区分记录。基于此,上述锁对象信息序列包括:加锁指令对应的加锁对象信息和解锁指令对应的解锁对象信息。该加锁对象信息包括:加锁对象的内存引用信息、加锁对象的名称和加锁指令的方法调用栈信息;解锁对象信息包括解锁对象的内存引用信息。其中内存引用信息包含锁对象对应的内存地址。

相应地,该加锁对象信息对应的数据结构lock_record如下:

该解锁对象信息对应的数据结构unlock_record如下:

unlock_record:

{

lock_ref:锁对象的内存引用;

}

基于上述加锁对象信息和解锁对象信息包含的内容,上述将锁指令的锁对象信息添加于上述线程的身份标识对应的映射值中的步骤,可以具体包括:(1)如果锁指令为加锁指令,将加锁指令的加锁对象信息添加于该线程的身份标识对应的映射值中;(2)如果锁指令为解锁指令,将解锁指令的解锁对象信息添加于该线程的身份标识对应的映射值中。

考虑到随着被测试程序的运行,大量线程不断的创建和消亡,所记录的信息越来越多,因此导致很大的空间占用,进而影响程序运行速度。基于此在上述实施例的基础上,上述程序死锁的测试方法还包括:

(1)如果被测试程序启动,启动第一定时器;

(2)如果第一定时器计时停止,查找已结束的线程。

(3)从查找到的已结束的线程的各个锁对象信息序列中,分别提取出具有嵌套关系的加锁对象序列,得到已结束的线程的最简加锁序列。

(4)清空已结束的线程的锁对象信息序列,并启动第一定时器重新计时。

其中,上述第一定时器计时的作用是为了定时对映射表中的锁对象信息序列进行简化,以节省存储空间,并简化后续有向图的绘制过程。该第一定时器的定时时长可以是1个小时-5个小时等,定时时长的长短可以根据需要调整,本实施方式对此不进行限定。上述第一定时器为循环定时器,当该定时器达到定时时长时计时停止,在简化锁对象信息序列后,即在处理完序列归并动作后,清零并自动重新计时,以在下一周期重新进行上述操作。

为了自动结束被测试程序,上述方法还可以增加第二定时器,上述程序死锁的测试方法还包括:如果被测试程序启动,启动第二定时器。如果第二定时器计时停止,停止被测试程序的运行,停止上述第一定时器的计时,将未结束的线程设置为结束。

其中,第二定时器运行过程中,将持续侦听被测试程序的线程,第二定时器计时停止后,停止被测试程序的运行,结束侦听线程的过程。考虑到被测试程序中的一些线程为常驻线程,即启动后不会退出,为实现对所有的线程均进行死锁检测,在第二定时器计时停止后,停止上述第一定时器的计时,并将映射表中未结束的线程设置为结束,这样可以按照上述方法,获取到这些结束线程的最简加锁序列。通常第二定时器的计时时长可以是5-10天等,该时长也可以根据实际需要调整。

因此,当上述第一定时器的计时时长为5小时,第二定时器的计时时长为3天(72小时)时,第一定时器需要循环执行上述(2)-(4)的步骤14次。

基于上述两种定时器机制,参见图3提供的另一种程序死锁的测试方法的流程图,该方法还包括:

步骤s301,在被测试程序启动后,启动第一定时器及第二定时器,并侦听该被测试程序中的线程的处理指令。

步骤s302,如果侦听到上述线程的锁指令,在映射表中以链表形式记录上述线程的锁对象信息序列。

步骤s303,如果第一定时器计时停止,从查找到的已结束的线程的各个锁对象信息序列中,分别提取出具有嵌套关系的加锁对象序列,得到已结束的线程的最简加锁序列,并启动该第一定时器重新计时。

为了便于后续对已结束线程的查找,可以设置相应的记录结束标识(例如:record_lock_flag),通过该记录结束标识确定当前线程是否运行结束。例如,如果将该记录结束标识设置为1,表示当前线程运行结束。如果将该启动参数设置为0,表示当前线程正在运行中。

为了有序提取具有嵌套关系的加锁对象序列,有效提高测试效率,上述步骤s303具体可以包括:对查找到的已结束的线程的每个锁对象信息序列,均按照元素排列的顺序读取当前线程的锁对象信息序列中的元素。其中为了描述方便,锁对象信息序列中包含的各个锁对象信息均可以称为锁对象信息序列中的元素。

对于上述读取到的元素均执行以下步骤:

(a)检查读取到的元素是否为加锁对象信息。

例如可以通过上述记录结束标识确定相应的线程是否结束。对于已结束的线程的每个锁对象信息序列中的元素,均按照此前进行记录的先后顺序进行读取。其中锁对象信息序列可以但不限于表示为record_list,其包含的元素为lock_record或者unlock_record;元素的判断方式有多种,如可以根据读取到的元素的锁对象名称,或者该元素对应的数据结构。

(b)如果是加锁对象信息,将上述加锁对象信息添加至锁对象信息序列的当前最简序列。

其中,该当前最简序列的初始态为空,可以但不限于表示为cur_seq。如果当前不存在该cur_seq,则创建一个空的cur_seq。

(c)如果为解锁对象信息,则:

(c1)复制当前最简序列,将复制的当前最简序列作为下一个最简序列,从该下一个最简序列中删除该解锁对象信息对应的加锁对象信息;

假设读取到的元素为解锁对象信息b,当前最简序列为(a,b,c,b)(当前最简序列中均是加锁对象信息),则复制该当前最简序列后,得到下一个最简序列(可以表示为next_seq)为(a,b,c,b);然后从(a,b,c,b)中的尾部开始查找该解锁对象信息b对应的加锁对象信息b的标识b,删除查找到的第一个加锁对象信息b,即从下一个最简序列(a,b,c,b)中删除最后面的加锁对象信息b,得到最终的下一个最简序列为(a,b,c)。需要说明的是,上述序列中的元素均以锁对象的标识表示,实际应用中,其包含该标识对应的加锁对象信息。

(c2)对当前最简序列中的重复元素进行去重处理,检查所述当前线程的最简序列集合中是否存在与去重后的当前最简序列相互包含的最简序列;

其中,该最简序列集合的初始态为空,可以但不限于表示为simple_set。如果当前不存在该simple_set,则创建一个空的simple_set。

仍以上述当前最简序列(a,b,c,b)为例,对(a,b,c,b)进行去重处理,删除序列中的重复元素b,得到去重后的当前最简序列(a,b,c)。

(c3)如果去重后的当前最简序列包含有或被包含在当前线程的最简序列集合中的最简序列中,则在所述最简序列集合中保留相互包含的最简序列中较长的序列;

(c4)如果在当前线程的最简序列集合中不存在与去重后的当前最简序列具有相互包含或被包含关系的序列,将去重后的当前最简序列加入至该最简序列集合中;

如果当前线程的最简序列集合中仅包含最简序列(a,b,e),若去重后的当前最简序列为(a,b,c),则二者相互不包含,将该(a,b,c)加入至当前线程的最简序列集合中。至此,当前线程的最简序列集合中包括序列{(a,b,e),(a,b,c)}。

如果当前线程的最简序列集合中包含最简序列{(a,b,c,d)},若去重后的当前最简序列为(a,b,c),则(a,b,c)被包含在该当前线程的最简序列集合中,则依然在最简序列集合中保存最简序列(a,b,c,d),即删除该去重后的当前最简序列(a,b,c)。

(c5)将删除加锁对象信息后的下一个最简序列作为当前最简序列。

即,使cur_seq=next_seq。

(d)如果锁对象信息序列中的元素未遍历完,继续读取锁对象信息序列中的下一个元素,重复上述步骤(a)-(c);

(e)如果锁对象信息序列中的元素均遍历完,读取当前线程的下一个锁对象信息序列中的元素,重复上述步骤(a)-(d);

(f)如果当前线程的所有锁对象信息序列均遍历完,将下一个已结束的线程作为新的当前线程,读取新的所述当前线程的锁对象信息序列中的元素,重复上述步骤(a)-(e);

(g)如果查找到的已结束的线程的每个锁对象信息序列均遍历完,将每个已结束的线程的最简序列集合作为已结束的线程的最简加锁序列。

循环上述步骤(a)-(f),直至查找到的已结束的线程的每个锁对象信息序列均遍历完,将每个已结束的线程的最简序列集合作为已结束的线程的最简加锁序列。

除了上述最简加锁序列的获取方式,还可以对已结束的线程的每个锁对象信息序列进行如下操作得到:对锁对象信息序列进行转换,得到多个子序列;其中,每个子序列均为从该锁对象信息序列的第一个元素开始到后续任意一个元素截止所构成的序列;

删除每个子序列中的解锁对象信息和该解锁对象信息对应的加锁对象信息,得到具有嵌套关系的加锁序列;

对各个加锁序列进行子集去重操作(即保留相互具有包含关系的加锁序列中的最长加锁序列),得到已结束的线程具有嵌套关系的最简加锁序列。

在得到最简加锁序列后,第一定时器清零并自动重新计时,以在下一周期重新进行上述操作。

上述第一定时器定时的方式,提供了一种定时归并机制,以实现对线程的锁对象信息序列的定期归并处理,定期删除无用的记录,仅保留有效信息,且可以对数据进行初步处理,便于后续分析。

步骤s304,如果上述第二定时器计时停止,停止被测试程序的运行,停止第一定时器的计时,将映射表中未结束的线程设置为结束。

步骤s305,获取映射表中各个线程的最简加锁序列。

如果第二定时器计时停止时,相应的归并程序正在进行,即正在执行上述步骤(a)-(g),则在归并完成后,再停止第一定时器。并再次触发对已结束的所有线程进行归并处理,以确保后续可以获取映射表中各个线程的最简加锁序列。

在获取到每个已结束的线程的最简加锁序列后,清空该线程的锁对象信息序列,从而释放占用的空间。

步骤s306,将各个线程的最简加锁序列中相同的最简加锁序列合并。

为了便于后期数据处理,简化死锁信息的确定流程,在可能的实施例中,对上述各个线程的最简加锁序列进行去重处理。上述步骤s306包括:检查各个线程的最简加锁序列中是否存在相同的最简加锁序列;如果存在相同的最简加锁序列,保留相同的最简加锁序列中的一个,为保留的最简加锁序列设置重复标识。

其中,重复标识用于表示是否存在其他线程与该线程的最简加锁序列一致,可以但不限于表示为multi_flag。如果将该重复标识设置为1,表示存在其他线程与该线程的最简加锁序列一致;如果将该重复标识设置为0,表示不存在其他线程与该线程的最简加锁序列一致。

例如,

线程t1对应的最简加锁序列包括序列1:加锁a-加锁b-加锁c;和序列2:加锁a-加锁b-加锁e。

线程t2对应的最简加锁序列包括序列3:加锁a-加锁b-加锁c;和序列4:加锁a-加锁b-加锁e。

由于序列1与序列3相同,序列2与序列4相同,由此可知,线程t1的最简加锁序列与线程t2的最简加锁序列完全相同。此时,将线程t1和线程t2的最简加锁序列合并,如仅保留线程t1的最简加锁序列,并设置该最简加锁序列(包括序列1和序列2)对应的重复标识。

需要说明的是,上述各个线程的最简加锁序列进行去重处理步骤,可以在每次第一定时器计时停止后,对其触发生成的最简加锁序列进行去重处理;也可以在第二定时器触发被测试程序运行结束后,对获取的所有的最简加锁序列进行去重处理。实际应用中,可以根据需要进行选择,本实施例对此不作限定。

步骤s307,绘制合并后的最简加锁序列的有向图。有向图的具体绘制过程可以参见后续的图4,这里暂不详述。

步骤s308,基于绘制出的有向图确定被测试程序的死锁信息。

当上述合并后的每个最简加锁序列中的元素均处理完成后,得到最终的有向图。通过上述绘制有向图的方式,可以清楚的显示各个线程之间的关联,便于对死锁位置的定位处理。

为了有序绘制有向图,提高绘制效率,参见图4,上述步骤s307可以包括:对于合并后的每个最简加锁序列,均执行如下步骤:

步骤s401,读取最简加锁序列中的第一个元素,判断是否存在包含该第一个元素的已创建的有向图。

假设启动时间为00:10的线程t1的最简加锁序列s1的第一个元素为加锁对象a的信息(以下简称元素a),则判断已创建的有向图中是否包含该元素a。

如果不存在包含该第一个元素的已创建的有向图,则执行步骤s402;如果存在包含该第一个元素的已创建的有向图,则执行步骤s403。

步骤s402,创建以上述第一个元素为首节点的有向图作为当前图。

步骤s403,将包含上述第一个元素的已创建的有向图作为当前图。

接续上述示例,如果不存在包含元素a的已创建的有向图,则创建以元素a为首节点的有向图作为当前图;如果不存在包含元素a的已创建的有向图,则将包含上述元素a的已创建的有向图作为当前图。

步骤s404,逐一读取上述最简加锁序列中的后续元素,判断是否存在包含该后续元素的已创建的有向图。

假设读取到最简加锁序列s1的后续元素为加锁对象c的信息(以下称元素c),其前一个元素为元素b。判断是否存在包含该元素c的已创建的有向图。

如果不存在包含该后续元素的已创建的有向图,执行步骤s405;如果存在包含该后续元素的已创建的有向图,执行步骤s406。

步骤s405,在当前图中增加该后续元素以及该后续元素的前一个元素指向该后续元素的连线,并为该连线配置对应的线程信息。

其中,该线程信息包括线程的身份信息,还包括重复标识。通过该线程信息增加可读性,便于相关人员对死锁定位。

接续上述示例,如果不存在包含元素c的已创建的有向图,在将该元素c作为节点,添加至当前图。且增加该元素c指向其前一个元素(元素b)的连线(该连线也可以称为有向图的弧)。并且为该连线配置线程信息:线程的身份信息(t1,00:10)及重复标识。

步骤s406,判断是否存在该后续元素的前一个元素指向该后续元素的连线。

例如,如果存在包含元素c的已创建的有向图,则判断是否存在元素c的前一个元素(元素b)指向该元素c的连线。

如果不存在后续元素的前一个元素指向该后续元素的连线,则执行步骤s407;如果存在后续元素的前一个元素指向该后续元素的连线,则执行步骤s408。

步骤s407,在当前图中增加该后续元素的前一个元素指向该后续元素的连线。

步骤s408,为该后续元素的前一个元素指向该后续元素的连线配置对应的线程信息。

如果不存在元素b指向元素c的连线,则在当前图中增加元素b指向元素c的连线;如果存在元素b指向元素c的连线,则为该连线配置对应的线程信息:线程的身份信息及重复标识。

因此,基于上述绘制的有向图,上述步骤s308可以具体包括:

(1)判断上述有向图中是否存在环路。

例如可以通过拓扑排序、dfs(depthfirstsearch,深度优先搜索)或者floyd算法,检索上述有向图中是否存在环路。如果存在环路,确定存在死锁隐患,执行以下步骤(2);如果不存在环路,则确定不存在死锁隐患。

(2)提取上述环路中包含的各个元素的加锁对象信息及各个连线对应的线程信息。

(3)根据提取的加锁对象信息和线程信息,确定被测试程序的死锁信息。

如由元素a-连线a-元素b-连线b-元素c-连线c-元素a构成的回路,提取该各个连线上配置的线程信息(如线程t1和线程t2的线程信息),以及连线连接的各个元素:元素a、元素b及元素c,对应的加锁对象信息。根据该加锁对象信息和线程信息,可以准确的定位死锁发生在线程t1和线程t1的锁对象a、锁对象b及锁对象c之间。

为了更直观的显示死锁现象,可以将上述发生死锁现象有向图通过可视化的方式展示,并作为bug(漏洞)进行报告。其中有向图中的各个节点中包含各个锁对象对应的加锁对象信息。

需要说明的是,上述从有向图中是否存在环路的角度判断是否存在死锁隐患的方式,可能存在误报问题:假设线程t1的最简加锁序列为{(a,b,c,d),(b,e),(e,a)}(在此序列中的每个元素均以加锁对象信息的锁对象的标识表示),如果按照上述方法,则存在环路a-b-e-a,确定存在死锁隐患,而对于一个线程t1自身导致的环路,实际不会出现死锁,只是一种设计错误。但是如果存在多个不同线程的最简加锁序列均相同时,则可能会真的出现死锁,继续接续前面的例子,假设线程t2和t3的最简加锁序列也为{(a,b,c,d),(b,e),(e,a)},当线程t1持有锁a,请求锁b,线程t2持有锁b,请求锁e,线程t3持有锁e,请求锁a时,就会导致死锁。基于此,本申请实施例中引入了步骤s306中提到的重复标识。在具体判断是否存在死锁隐患时,如果有向图出现环路,还可以进一步检查该环路的重复标识是否为真,如果重复标识为真,则确定可能有多个线程会并行的进行上述加锁中的任意一种组合,存在死锁隐患。如果重复标识为假,在不考虑与其它线程的锁对象信息序列的组合判断影响下,该环路实际上不会导致死锁现象。但是这种情况即使不会真的死锁,也属于一种设计错误,即也应该作为问题上报。因此此处重复标识仅作为一项参考信息,在上报bug时体现,并不用于排除误报。

换言之,上述重复标识可用于提醒相关工作人员,例如当该重复标识为真时,若相应线程的最简加锁序列为有闭环情况属于真正的死锁情况,而为该重复标识为假时,则属于设计不合理,不是属于死锁。

在可能的实施例中,将上述线程对应的记录结束标识、重复标识及最简加锁序列,与相应锁对象信息序列(可以但不限于表示为record_list),以对象(可以但不限于表示为thread_lock_info)的形式关联存储至映射表的映射值中,以便于后续的数据提取,提高数据处理效率。相应的,该对象对应的数据结构thread_lock_info如下:

在具体实现过程中,上述实施例中描述的方法可以通过在jvm中,增加一个扩展的死锁检测模块实现,参见图5所示的扩展后的jvm进行死锁检测的示意图,其中该死锁检测模块包括记录子模块和分析子模块。该记录子模块负责记录,得到各个线程的最简加锁序列,并存储于映射表中;然后由分析子模块从映射表中读取该最简加锁序列,得到最终的有向图,并生成死锁检索报告以上报bug。

为了便于对上述实施例的理解,在上述实施例的基础上,本公开实施例提供了一种程序死锁的测试方法的具体实现实例。具体描述如下:

假设某程序运行时,分别启动了5个线程,线程的标识分别为:t1、t2、t3、t4_1、t4_2。该5个线程按照时间顺序先后启动,t2、t3为常驻线程(启动后不退出),t4_1和t4_2为执行同类任务的线程(加锁解锁过程相同)。

5个线程执行过程中,共涉及10把不同的锁对象,假设他们的名称分别为locka、lockb、…、lockj,对应的内存地址分别为a、b、…、j,本实施例中将该内存地址作为各个锁对象的标识。该五个线程执行的指令如图6所示。

对于上述场景,本实施例检测程序死锁的具体步骤如下:

(一)程序启动

假设此时时间为00:00;启动jvm,开启死锁检测功能,例如将dla-enable设置为1。

接收测试人员设置的定时器的定时参数,若测试人员未进行定时器的定时参数设置,也可以采用定时器默认的定时参数。本公开实施例以第一定时器的计时时长为1小时;第二定时器的计时时长为1.5小时为例进行说明。

(二)初始化

启动第一定时器和第二定时器,创建一个空的映射表map,该映射表的结构包括用于记录线程的身份信息的索引信息(关键字key)和用于记录上述锁对象thread_lock_info的映射值value。

(三)启动死锁检测功能测试

例如可以由测试人员启动功能测试,各线程稍后会依次启动;或者在完成初始化后,各线程稍后会依次启动。

(四)记录子模块记录数据

在第一定时器计时过程中,记录子模块一直基于侦听到的指令,在映射表中记录各个线程的信息。

假设:00:10,线程t1启动;记录子模块在映射表中添加线程t1的key为线程的身份标识t1和线程t1的启动时间00:10;如果记录子模块侦听到线程t1执行加锁指令,在映射表中线程t1的value中添加加锁对象信息(包括:加锁对象的内存引用信息,加锁对象的名称和加锁指令的方法调用栈信息)。如果记录子模块侦听到线程t1执行解锁指令,在映射表中线程t1的value中添加解锁对象信息(包括:解锁对象的内存引用信息)。如果记录子模块侦听到线程t1的结束指令,将record_end_flag置为ture。上述加锁对象信息和解锁对象信息以执行的先后顺序链式存储在映射表中。00:11,线程t1退出,期间按照示例中描述的顺序加锁解锁。

同理:00:12,线程t2启动;00:13,线程t3启动;这两个线程启动后均按照描述的顺序加锁解锁,但线程并未退出;00:15线程t4_1启动,00:16线程t4_1退出;期间按照描述的顺序加锁解锁;00:17线程t4_2启动,00:18线程t4_2退出;期间按照描述的顺序加锁解锁。此时,map中的数据如图7所示,图中record_list中的元素包含的锁对象信息仅以锁对象标识表示,实际应用中,可以包含上述锁对象信息的全部信息。

(五)第一定时器超时进行数据归并处理

由于t1、t4_1、t4_2的record_end_flag标记为ture(即线程t1、t4_1、t4_2目前已经结束),因此需要归并这三个线程的数据。由于t4_1和t4_2的simple_set相同,因此归并后仅保留t4_1,不再需要t4_2的数据,t4_1的multi-flag被标记为ture。

以t1对应的record_list为例描述归并处理过程,为了描述方便,将上述t1对应的record_list简写为:a+→b+→c+→b+→b-→c-→b-→a-→b+→d+→e+→e-→d-→b-。其中,“+”表示加锁,“-”表示解锁。其中,record_list中的每个元素均包含了对应的thread_lock_info中的信息。具体的归并处理过程如下:

(d1)开始创建一个空的最简序列()作为当前最简序列cur_seq,读取record_list中的元素,首先读到a+,因为该记录为加锁对象信息,直接将a+加入到cur_seq中,此时cur_seq表示为(a);继续读取record_list中下一个的元素,依次读到b+、c+和b+,因为这些元素均为加锁对象信息,依次添加至cur_seq中,得到cur_seq为(a,b,c,b)。

(d2)继续读取record_list中下一个的元素,读到b-,因为b-为解锁对象信息,所以复制上面的cur_seq,即(a,b,c,b)作为下一个最简序列next_seq,此时next_seq为(a,b,c,b),从next_seq尾部开始查找该解锁对象b-对应的加锁对象信息b+的标识b,删除查找到的第一个b,即从(a,b,c,b)中删除最后面的b,next_seq变为(a,b,c)。然后将cur_seq去重,即将(a,b,c,b)中的最后一个b删除,得到cur_seq为(a,b,c),将(a,b,c)加入simple_set中,因为simple_set开始为空,加入cur_seq后,simple_set为{(a,b,c)}。再设置cur_seq=next_seq,此时cur_seq为(a,b,c)。

(d3)继续读取record_list中下一个的元素,读到c-,与上述步骤(d2)的处理类似,再复制cur_seq作为next_seq,即(a,b,c),从next_seq的尾部开始查找c-对应的加锁记录c+的标识c,删除查找到的第一个c,next_seq变为(a,b)。然后将cur_seq(a,b,c)加入simple_set中时,因为simple_set非空,检查simple_set中已经存在的序列(a,b,c),与cur_seq的序列(a,b,c)相同,则simple_set中仍为序列(a,b,c)。再设置cur_seq=next_seq,此时cur_seq为(a,b)。

以此类推,后续读取到b-和a-,经过步骤(d2)和(d3)的操作后,cur_seq为空,simple_set中仍只保留序列(a,b,c)。

(d4)继续读取record_list中的后续元素,与步骤(d1)类似,依次读到b+、d+和e+,均逐一加到cur_seq,至此cur_seq为(b,d,e)。

(d5)继续读取record_list中下一个的元素,读到e-,按照步骤(d2)中的操作,simple_set为{(a,b,c),(b,d,e)},cur_seq为(b,d)。

(d6)继续读取record_list中的后续元素,将依次读取到d-和b-,按照步骤(d3)中的操作后,simple_set为{(a,b,c),(b,d,e)},cur_seq为空。

至此t1的record_list中的元素均被读出,record_list为空,释放其占用的空间;t1的simple_set为{(a,b,c),(b,d,e)},结束t1的record_list的归并处理操作。

其余线程的操作与之类似。最终各个线程的最简加锁序列如下:

t1的simple_set为{(a,b,c),(b,d,e)};

t2的simple_set为{(b,f),(b,e),(c,e)};

t3的simple_set为{(e,a),(g,h,j)};

t4_1的simple_set为{(j,h),(g,i)};

t4_2的simple_set为{(j,h),(g,i)}。

为了描述方便,上述序列中的元素均以锁对象的标识表示,实际应用中,序列中的每个元素均包含了对应的lock_record或者unlock_record中的信息。所有record_end_flag为true的record_list处理完后,执行去重处理,即如果多个record_end_flag为true的simple_set内容相同(simple_set中的所有的最简序列均相同),则仅保留其中一个,将保留的线程的thread_lock_info中的multi_flag置为true。本例中,t4_1和t4_2的simple_set为内容相同,因此仅保留t4_1的simple_set即可,将t4_1的thread_lock_info中的multi_flag置为true。

归并完成后,map中的数据如图8所示。

(六)第二定时器超时停止数据记录并完成最后的归并处理

此时,停止第二定时器,并停止记录,然后将t2和t3的record_end_flag标记为ture。然后对t2和t3的数据进行归并。

归并完成后,map中的数据如图9所示,具体地归并处理过程可以参照上述(五)中的介绍,在此不赘述。

(七)分析子模块对归并后的数据进行分析

接续上例,t1的simple_set为{(a,b,c),(b,d,e)};t2的simple_set为{(b,f),(b,e),(c,e)};t3的simple_set为{(e,a),(g,h,j)};t4_1的simple_set为{(j,h),(g,i)};针对这些最简加锁序列生成有向图的过程可以参照图4所示的流程图,具体包括:首先遍历所有simple_set中的序列,对于每一个被遍历到序列均执行如下操作:

(e1)首先读取到t1的simple_set中的第一个序列(a,b,c)中第一个元素a,判断是否存在包含元素a的已创建的有向图;因为是首次读取到元素a,之前还没有创建任何有向图,所以判断的结果为否(即已创建的有向图中不包含节元素a),则创建以元素a为首节点的有向图作为第一个序列(a,b,c)的当前图,为了描述方便,将该图命名为有向图t1;

(e2)对于第一个序列(a,b,c)的后续元素,先读取到元素b,在有向图t1中增加元素b以及元素a指向元素b的连线ab,在连线ab上设置“线程的身份标识+multi_flag”信息(即线程信息),本例中,连线ab上的信息为t1,00:10,false。该连线ab的起点为a,终点为b。同理,在有向图t1中添加元素c以及元素b指向元素c的连线bc,在连线bc上设置信息t1,00:10,false。至此,第一个序列(a,b,c)遍历完。

(e3)继续遍历第二个序列(b,d,e),同步骤(e1)相同,先读取到元素b,判断是否存在包含元素b的已创建的有向图,检查到有向图t1中包含元素b,则将有向图t1作为第二个序列(b,d,e)的当前图。因为有向图t1已经存在元素b,所以本次不需要在当前图中增加元素b。

(e4)继续读取第二个序列(b,d,e)的下一个元素,即读取到元素d,按照上述步骤(e2)中的方式,因为有向图t1中没有元素d,因此在有向图t1中增加元素d以及元素b指向元素d的连线bd,在该连线bd上设置信息t1,00:10,false。同理,读取元素e,在有向图t1中增加元素e以及元素d指向元素e的连线de,在该连线de上设置信息t1,00:10,false。

至此,t1的两个最简序列均遍历完,接着遍历t2的最简序列,即(b,f)和(b,e),下面分别描述如下:

(e5)读取到第三个序列(b,f)中的元素b,和步骤(e3)类似,判断是否存在包含元素b的已创建的有向图,检查到有向图t1中包含元素b,则将有向图t1作为第三个序列(b,f)的当前图。因为有向图t1已经存在元素b,所以本次不需要在图中增加元素b。

(e6)继续读取到第三个序列(b,f)中的元素f,和步骤(e4)类似,因为有向图t1中没有元素f,因此在有向图t1中增加元素f以及元素b指向元素f的连线bf,在该连线bf上设置信息t2,00:12,false。

(e7)第四个序列(b,e)的处理和第三个序列(b,f)的处理类似,不同之处在于,读取到第四个序列(b,e)中的元素e时,因为元素e已包含在有向图t1中,因此仅需要建立元素b指向元素e的连线be,设置元素be的信息为t2,00:12,false。

继续遍历t3的simple_set,即第五个序列(e,a)和第六个序列(g,h,j)。

(e8)读取第五个序列(e,a)中的元素e,因为元素e已包含在有向图t1中,将有向图t1作为当前图,继续读取到元素a,元素a在该有向图t1中,建立元素e指向元素a的连线ea,设置连线ea的信息为t3,00:13,false。

(e9)读取第六个序列(g,h,j)中的元素g,因为元素g在已创建的有向图t1中不存在,因此创建一个以元素g为首节点的有向图作为当前图,记为有向图t3。后续元素h和元素j的处理过程与上述步骤(e2)中的元素b和元素c的处理类似,这里不再赘述。

(e10)t4_1的simple_set的(j,h)和(g,i)的处理过程类似于上述步骤(e4),这里不再赘述,最终的有向图t1和t3分别参见图10和图11。

上述两个有向图中均存在环路,因此可以报告两个死锁bug,具体参见上述实施例的描述。

需要说明的时,本公开中描述的具体步骤适用于对非计数锁对象(如synchronized、lock等)的死锁检测,其它可计数的锁对象(如计数信号量、latch、读写锁等)与可以使用大致相同的步骤进行检测,在具体的数据记录内容及分析方式上进行一些改变即可,本公开不做进一步描述。

综上,本实施例提出的程序死锁的测试方法,能够自动收集死锁隐患数据,并对隐患数据进行分析,然后进行死锁报告。具有死锁隐患的程序,只要被先后执行到,就可以检测出死锁问题,并进行报告,有效降低了人力成本,且不需要死锁问题实际触发,因此检测到死锁隐患的概率大幅增加。

对应于上述程序死锁的测试方法的实施方式,参见图12所示的一种程序死锁的测试装置的结构示意图,该装置包括如下模块:

指令侦听模块11,用于在被测试程序启动后,侦听该被测试程序中的线程的处理指令;

信息记录模块12,用于当侦听到线程的锁指令时,记录该线程的锁对象信息序列;其中,锁指令包括加锁指令和解锁指令;锁对象信息序列包括:加锁指令对应的加锁对象信息和解锁指令对应的解锁对象信息;

序列获取模块13,用于当被测试程序停止运行时,获取各个线程的锁对象信息序列中的最简加锁序列;其中,最简加锁序列为带有嵌套关系的加锁对象序列;

序列合并模块14,用于将各个线程的最简加锁序列中相同的最简加锁序列合并;

绘制模块15,用于绘制合并后的最简加锁序列的有向图;

死锁确定模块16,用于基于绘制出的有向图确定所述被测试程序的死锁信息。

本公开实施方式提供的一种程序死锁的测试装置,在被测试程序启动后,通过侦听被测试程序中线程的处理指令的方法,记录各个线程的用于表征该线程加锁与解锁流程的锁对象信息序列,并在被测试程序停止运行后,获取各个线程的锁对象信息序列包含的带有嵌套关系的最简加锁序列,然后根据该最简加锁序列绘制有向图,从而根据该有向图确定被测试程序的死锁信息。

该方式通过自动收集被测试程序运行中死锁隐患的相关数据,以该相关数据为依据利用有向图确定相应程序的死锁信息,从而实现了死锁隐患的自动检测,有效提高了发现死锁隐患的概率。

上述指令侦听模块还用于:在侦听到被测试程序有线程启动时,根据线程的标识和启动时间生成线程的身份标识,并在映射表的关键字中添加线程的身份标识,继续侦听线程的处理指令;

相应地,上述信息记录模块还用于:当侦听到所述线程的锁指令时,在映射表的关键字中查找线程的身份标识;按照锁指令发生的先后顺序,将锁指令的锁对象信息添加于线程的身份标识对应的映射值中,得到链表形式的锁对象信息序列。

上述信息记录模块还用于:当锁指令为加锁指令时,将加锁指令的加锁对象信息添加于线程的身份标识对应的映射值中;其中,加锁对象信息包括:加锁对象的内存引用信息、加锁对象的名称和加锁指令的方法调用栈信息;当锁指令为解锁指令时,将解锁指令的解锁对象信息添加于线程的身份标识对应的映射值中;其中,解锁对象信息包括解锁对象的内存引用信息。上述序列合并模块还用于:检查各个线程的最简加锁序列中是否存在相同的最简加锁序列;如果存在相同的最简加锁序列,保留相同的最简加锁序列中的一个,为保留的最简加锁序列设置重复标识。

上述绘制模块还用于:对于合并后的每个最简加锁序列,均执行如下步骤:读取最简加锁序列中的第一个元素,如果不存在包含第一个元素的已创建的有向图,则创建以第一个元素为首节点的有向图作为当前图;如果存在包含第一个元素的已创建的有向图,将包含第一个元素的已创建的有向图作为当前图;逐一读取最简加锁序列中的后续元素,判断是否存在包含后续元素的已创建的有向图;如果不存在包含后续元素的已创建的上述有向图,则在当前图中增加后续元素以及后续元素的前一个元素指向后续元素的连线,并为连线配置对应的线程信息,线程信息包括线程的身份标识;如果存在包含后续元素的已创建的有向图,则判断是否存在后续元素的前一个元素指向后续元素的连线;如果不存在上述连线,则在当前图中增加后续元素的前一个元素指向后续元素的连线;如果存在上述连线,为上述连线配置对应的线程信息。

上述死锁确定模块还用于:判断所述有向图中是否存在环路;如果存在,提取所述环路中包含的各个元素的加锁对象信息及各个连线对应的线程信息;根据提取的所述加锁对象信息和所述线程信息,确定所述被测试程序的死锁信息。

在另一种实施方式中,参见图13所示的另一种程序死锁的测试装置的结构示意图,在上述装置的基础上,该装置还包括:

第一定时模块17,用于当被测试程序启动时,启动第一定时器;如果第一定时器计时停止,查找已结束的线程;从查找到的已结束的线程的各个锁对象信息序列中,分别提取出具有嵌套关系的加锁对象序列,得到已结束的线程的最简加锁序列;清空已结束的线程的锁对象信息序列,并启动第一定时器重新计时。

上述第一定时模块还用于:对查找到的所述已结束的线程的每个锁对象信息序列,均按照元素先后顺序读取当前线程的锁对象信息序列中的元素,对于每个读取到的元素均执行如下步骤:

检查上述元素是否为加锁对象信息;如果该元素为加锁对象信息,将加锁对象信息添加至锁对象信息序列的当前最简序列;其中,当前最简序列的初始态为空;如果该元素为解锁对象信息,复制当前最简序列,将复制的当前最简序列作为下一个最简序列,从下一个最简序列中删除解锁对象信息对应的加锁对象信息;对当前最简序列中的重复元素进行去重处理,检查当前线程的最简序列集合中是否存在与去重后的当前最简序列相互包含的最简序列;如果存在,则在最简序列集合中保留相互包含的最简序列中较长的序列;如果不存在,将去重后的当前最简序列加入至最简序列集合中;其中,最简序列集合的初始态为空;将删除上述锁对象信息后的下一个最简序列作为当前最简序列,继续读取锁对象信息序列中的下一个元素;当锁对象信息序列中的元素均遍历完,读取当前线程的下一个锁对象信息序列中的元素,继续返回上述检查所述元素是否为加锁对象信息的步骤;如果当前线程的所有锁对象信息序列均遍历完,将下一个已结束的线程作为新的当前线程,读取新的当前线程的锁对象信息序列中的元素,继续返回检查所述元素是否为加锁对象信息的步骤;如果查找到的已结束的线程的每个锁对象信息序列均遍历完,将每个已结束的线程的最简序列集合作为已结束的线程的最简加锁序列。

在另一种实施方式中,参见图14所示的另一种程序死锁的测试装置的结构示意图,在上述装置的基础上,该装置还包括:

第二定时模块18,用于当被测试程序启动时,启动第二定时器;如果第二定时器计时停止,停止被测试程序的运行,停止第一定时器的计时,将未结束的线程设置为结束。

本实施方式提供了一种与上述方法实施方式相对应的程序死锁的测试设备。图15为该程序死锁的测试设备的结构示意图,如图15所示,该设备包括处理器1201和存储器1202;其中,存储器1202用于存储一条或多条计算机指令,一条或多条计算机指令被处理器执行,以实现上述程序死锁的测试方法。

图15所示的设备还包括总线1203和通信接口1204,处理器1201、通信接口1204和存储器1202通过总线1203连接。该程序死锁测试的测试设备可以是加载有jvm的服务器。

其中,存储器1202可能包含高速随机存取存储器(ram,randomaccessmemory),也可能还包括非不稳定的存储器(non-volatilememory),例如至少一个磁盘存储器。通过至少一个通信接口1204(可以是有线或者无线)实现该系统网元与至少一个其他网元之间的通信连接,可以使用互联网,广域网,本地网,城域网等。总线1203可以是isa总线、pci总线或eisa总线等。所述总线可以分为地址总线、数据总线、控制总线等。为便于表示,图15中仅用一个双向箭头表示,但并不表示仅有一根总线或一种类型的总线。

处理器1201可能是一种集成电路芯片,具有信号的处理能力。在实现过程中,上述方法的各步骤可以通过处理器1201中的硬件的集成逻辑电路或者软件形式的指令完成。上述的处理器1201可以是通用处理器,包括中央处理器(centralprocessingunit,简称cpu)、网络处理器(networkprocessor,简称np)等;还可以是数字信号处理器(digitalsignalprocessor,简称dsp)、专用集成电路(applicationspecificintegratedcircuit,简称asic)、现场可编程门阵列(field-programmablegatearray,简称fpga)或者其他可编程逻辑器件、分立门或者晶体管逻辑器件、分立硬件组件。可以实现或者执行本公开实施方式中的公开的各方法、步骤及逻辑框图。通用处理器可以是微处理器或者该处理器也可以是任何常规的处理器等。结合本公开实施方式所公开的方法的步骤可以直接体现为硬件译码处理器执行完成,或者用译码处理器中的硬件及软件模块组合执行完成。软件模块可以位于随机存储器,闪存、只读存储器,可编程只读存储器或者电可擦写可编程存储器、寄存器等本领域成熟的存储介质中。该存储介质位于存储器1202,处理器1201读取存储器1202中的信息,结合其硬件完成前述实施方式的方法的步骤。

本公开实施方式还提供了一种机器可读存储介质,该机器可读存储介质存储有机器可执行指令,该机器可执行指令在被处理器调用和执行时,机器可执行指令促使处理器实现上述程序死锁的检测方法,具体实现可参见方法实施方式,在此不再赘述。

本公开实施方式所提供的程序死锁的检测装置及设备,其实现原理及产生的技术效果和前述方法实施方式相同,为简要描述,装置实施方式部分未提及之处,可参考前述方法实施方式中相应内容。

在本公开所提供的几个实施方式中,应该理解到,所揭露的装置和方法,也可以通过其它的方式实现。以上所描述的装置实施方式仅仅是示意性的,例如,附图中的流程图和框图显示了根据本公开的多个实施方式的装置、方法和计算机程序产品的可能实现的体系架构、功能和操作。在这点上,流程图或框图中的每个方框可以代表一个模块、程序段或代码的一部分,所述模块、程序段或代码的一部分包含一个或多个用于实现规定的逻辑功能的可执行指令。也应当注意,在有些作为替换的实现方式中,方框中所标注的功能也可以以不同于附图中所标注的顺序发生。例如,两个连续的方框实际上可以基本并行地执行,它们有时也可以按相反的顺序执行,这依所涉及的功能而定。也要注意的是,框图和/或流程图中的每个方框、以及框图和/或流程图中的方框的组合,可以用执行规定的功能或动作的专用的基于硬件的系统来实现,或者可以用专用硬件与计算机指令的组合来实现。

另外,在本公开各个实施方式中的各功能模块或单元可以集成在一起形成一个独立的部分,也可以是各个模块单独存在,也可以两个或两个以上模块集成形成一个独立的部分。

所述功能如果以软件功能单元的形式实现并作为独立的产品销售或使用时,可以存储在一个计算机可读取存储介质中。基于这样的理解,本公开的技术方案本质上或者说对现有技术做出贡献的部分或者该技术方案的部分可以以软件产品的形式体现出来,该计算机软件产品存储在一个存储介质中,包括若干指令用以使得一台计算机设备(可以是个人计算机,服务器,或者网络设备等)执行本公开各个实施方式所述方法的全部或部分步骤。而前述的存储介质包括:u盘、移动硬盘、只读存储器(rom,read-onlymemory)、随机存取存储器(ram,randomaccessmemory)、磁碟或者光盘等各种可以存储程序代码的介质。

最后应说明的是:以上所述实施方式,仅为本公开的具体实施方式,用以说明本公开的技术方案,而非对其限制,本公开的保护范围并不局限于此,尽管参照前述实施方式对本公开进行了详细的说明,本领域的普通技术人员应当理解:任何熟悉本技术领域的技术人员在本公开揭露的技术范围内,其依然可以对前述实施方式所记载的技术方案进行修改或可轻易想到变化,或者对其中部分技术特征进行等同替换;而这些修改、变化或者替换,并不使相应技术方案的本质脱离本公开实施方式技术方案的精神和范围,都应涵盖在本公开的保护范围之内。因此,本公开的保护范围应所述以权利要求的保护范围为准。

当前第1页1 2 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1