详解JAVA线程问题诊断工具ThreadDump
本文分享自华为云社区《调试排错Java线程分析之线程Dump分析云社区华为云》,作者:龙哥手记。
ThreadDump是非常有用的诊断Java应用问题的工具。每一个Java虚拟机都有及时生成所有线程在某一点状态的threaddump的能力,虽然各个Java虚拟机打印的threaddump略有不同,但是大多都提供了当前活动线程的快照,及JVM中所有Java线程的堆栈跟踪信息,堆栈信息一般包含完整的类名及所执行的方法,如果可能的话还有源代码的行数。ThreadDump特点能在各种操作系统下使用;能在各种Java应用服务器下使用;能在生产环境下使用而不影响系统的性能;能将问题直接定位到应用程序的代码行上;ThreadDump抓取
一般当服务器挂起,崩溃或者性能低下时,就需要抓取服务器的线程堆栈(ThreadDump)用于后续的分析。在实际运行中,往往一次dump的信息,还不足以确认问题。为了反映线程状态的动态变化,需要接连多次做threaddump,每次间隔1020s,建议至少产生三次dump信息,如果每次dump都指向同一个问题,我们才确定问题的典型性。操作系统命令获取ThreadDumppsefgrepjavakill3pid
注意:
一定要谨慎,一步不慎就可能让服务器进程被杀死。kill9命令会杀死进程。JVM自带的工具获取线程堆栈jps或psefgrepjava(获取PID)jstack〔l〕pidteeajstack。log(获取ThreadDump)ThreadDump分析ThreadDump信息头部信息:时间,JVM信息2011110219:05:06FullthreaddumpJavaHotSpot(TM)ServerVM(16。3b01mixedmode):线程INFO信息块:1。Timer0daemonprio10tid0xac190c00nid0xaefinObject。wait()〔0xae77d000〕线程名称:Timer0;线程类型:daemon;优先级:10,默认是5;JVM线程id:tid0xac190c00,JVM内部线程的唯一标识(通过java。lang。Thread。getId()获取,通常用自增方式实现)。对应系统线程id(NativeThreadID):nid0xaef,和top命令查看的线程pid对应,不过一个是10进制,一个是16进制。(通过命令:topHppid,可以查看该进程的所有线程信息)线程状态:inObject。wait();起始栈地址:〔0xae77d000〕,对象的内存地址,通过JVM内存查看工具,能够看出线程是在哪儿个对象上等待;2。java。lang。Thread。State:TIMEDWAITING(onobjectmonitor)3。atjava。lang。Object。wait(NativeMethod)4。waitingon0xb3885f60(ajava。util。TaskQueue)继续wait5。atjava。util。TimerThread。mainLoop(Timer。java:509)6。locked0xb3885f60(ajava。util。TaskQueue)已经locked7。atjava。util。TimerThread。run(Timer。java:462)Javathreadstatcktrace:是上面27行的信息。到目前为止这是最重要的数据,Javastacktrace提供了大部分信息来精确定位问题根源。Javathreadstatcktrace详解:
堆栈信息应该逆向解读:程序先执行的是第7行,然后是第6行,依次类推。locked0xb3885f60(ajava。util。ArrayList)waitingon0xb3885f60(ajava。util。ArrayList)
也就是说对象先上锁,锁住对象0xb3885f60,然后释放该对象锁,进入waiting状态。为啥会出现这样的情况呢?看看下面的java代码示例,就会明白:synchronized(obj){。。。。。。。。。obj。wait();。。。。。。。。。}
如上,线程的执行过程,先用synchronized获得了这个对象的Monitor(对应于locked)。当执行到obj。wait(),线程即放弃了Monitor的所有权,进入waitset队列(对应于waitingon)。
在堆栈的第一行信息中,进一步标明了线程在代码级的状态,例如:java。lang。Thread。State:TIMEDWAITING(parking)
解释如下:blockedThisthreadtriedtoenterasynchronizedblock,butthelockwastakenbyanotherthread。Thisthreadisblockeduntilthelockgetsreleased。blocked(onthinlock)Thisisthesamestateasblocked,butthelockinquestionisathinlock。waitingThisthreadcalledObject。wait()onanobject。Thethreadwillremainthereuntilsomeotherthreadsendsanotificationtothatobject。sleepingThisthreadcalledjava。lang。Thread。sleep()。parkedThisthreadcalledjava。util。concurrent。locks。LockSupport。park()。suspendedThethreadsexecutionwassuspendedbyjava。lang。Thread。suspend()oraJVMTIagentcall。Thread状态分析
线程的状态是一个很重要的东西,因此threaddump中会显示这些状态,通过对这些状态的分析,能够得出线程的运行状况,进而发现可能存在的问题。线程的状态在Thread。State这个枚举类型中定义:publicenumState{Threadstateforathreadwhichhasnotyetstarted。NEW,Threadstateforarunnablethread。AthreadintherunnablestateisexecutingintheJavavirtualmachinebutitmaybewaitingforotherresourcesfromtheoperatingsystemsuchasprocessor。RUNNABLE,Threadstateforathreadblockedwaitingforamonitorlock。Athreadintheblockedstateiswaitingforamonitorlocktoenterasynchronizedblockmethodorreenterasynchronizedblockmethodaftercalling{linkObjectwait()Object。wait}。BLOCKED,Threadstateforawaitingthread。Athreadisinthewaitingstateduetocallingoneofthefollowingmethods:ulli{linkObjectwait()Object。wait}withnotimeoutlili{linkjoin()Thread。join}withnotimeoutlili{linkLockSupportpark()LockSupport。park}liulpAthreadinthewaitingstateiswaitingforanotherthreadtoperformaparticularaction。Forexample,athreadthathascalledttObject。wait()ttonanobjectiswaitingforanotherthreadtocallttObject。notify()ttorttObject。notifyAll()ttonthatobject。AthreadthathascalledttThread。join()ttiswaitingforaspecifiedthreadtoterminate。WAITING,Threadstateforawaitingthreadwithaspecifiedwaitingtime。Athreadisinthetimedwaitingstateduetocallingoneofthefollowingmethodswithaspecifiedpositivewaitingtime:ulli{linksleepThread。sleep}lili{linkObjectwait(long)Object。wait}withtimeoutlili{linkjoin(long)Thread。join}withtimeoutlili{linkLockSupportparkNanosLockSupport。parkNanos}lili{linkLockSupportparkUntilLockSupport。parkUntil}liulTIMEDWAITING,Threadstateforaterminatedthread。Thethreadhascompletedexecution。TERMINATED;}NEW:
每一个线程,在堆内存中都有一个对应的Thread对象。ThreadtnewThread();当刚刚在堆内存中创建Thread对象,还没有调用t。start()方法之前,线程就处在NEW状态。在这个状态上,线程与普通的java对象没有什么区别,就仅仅是一个堆内存中的对象。RUNNABLE:
该状态表示线程具备所有运行条件,在运行队列中准备操作系统的调度,或者正在运行。这个状态的线程比较正常,但如果线程长时间停留在在这个状态就不正常了,这说明线程运行的时间很长(存在性能问题),或者是线程一直得不得执行的机会(存在线程饥饿的问题)。BLOCKED:
线程正在等待获取java对象的监视器(也叫内置锁),即线程正在等待进入由synchronized保护的方法或者代码块。synchronized用来保证原子性,任意时刻最多只能由一个线程进入该临界区域,其他线程只能排队等待。WAITING:
处在该线程的状态,正在等待某个事件的发生,只有特定的条件满足,才能获得执行机会。而产生这个特定的事件,通常都是另一个线程。也就是说,如果不发生特定的事件,那么处在该状态的线程一直等待,不能获取执行的机会。比如:
A线程调用了obj对象的obj。wait()方法,如果没有线程调用obj。notify或obj。notifyAll,那么A线程就没有办法恢复运行;如果A线程调用了LockSupport。park(),没有别的线程调用LockSupport。unpark(A),那么A没有办法恢复运行。TIMEDWAITING:
J。U。C中很多与线程相关类,都提供了限时版本和不限时版本的API。TIMEDWAITING意味着线程调用了限时版本的API,正在等待时间流逝。当等待时间过去后,线程一样可以恢复运行。如果线程进入了WAITING状态,一定要特定的事件发生才能恢复运行;而处在TIMEDWAITING的线程,如果特定的事件发生或者是时间流逝完毕,都会恢复运行。TERMINATED:
线程执行完毕,执行完run方法正常返回,或者抛出了运行时异常而结束,线程都会停留在这个状态。这个时候线程只剩下Thread对象了,没有什么用了。关键状态分析Waitoncondition:Thethreadiseithersleepingorwaitingtobenotifiedbyanotherthread。
该状态说明它在等待另一个条件的发生,来把自己唤醒,或者干脆它是调用了sleep(n)。
此时线程状态大致为以下几种:java。lang。Thread。State:WAITING(parking):一直等那个条件发生;java。lang。Thread。State:TIMEDWAITING(parking或sleeping):定时的,那个条件不到来,也将定时唤醒自己。WaitingforMonitorEntry和inObject。wait():Thethreadiswaitingtogetthelockforanobject(someotherthreadmaybeholdingthelock)。Thishappensiftwoormorethreadstrytoexecutesynchronizedcode。Notethatthelockisalwaysforanobjectandnotforinpidualmethods。
在多线程的JAVA程序中,实现线程之间的同步,就要说说Monitor。Monitor是Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者Class的锁。每一个对象都有,也仅有一个Monitor。下面这个图,描述了线程和Monitor之间关系,以及线程的状态转换图:
如上图,每个Monitor在某个时刻,只能被一个线程拥有,该线程就是ActiveThread,而其它线程都是WaitingThread,分别在两个队列EntrySet和WaitSet里等候。在EntrySet中等待的线程状态是Waitingformonitorentry,而在WaitSet中等待的线程状态是inObject。wait()。
先看EntrySet里面的线程。我们称被synchronized保护起来的代码段为临界区。当一个线程申请进入临界区时,它就进入了EntrySet队列。对应的code就像:synchronized(obj){。。。。。。。。。}
这时有两种可能性:该monitor不被其它线程拥有,EntrySet里面也没有其它等待线程。本线程即成为相应类或者对象的Monitor的Owner,执行临界区的代码。该monitor被其它线程拥有,本线程在EntrySet队列中等待。
在第一种情况下,线程将处于Runnable的状态,而第二种情况下,线程DUMP会显示处于waitingformonitorentry。如下:Thread0prio10tid0x08222eb0nid0x9waitingformonitorentry〔0xf927b000。。0xf927bdb8〕attestthread。WaitThread。run(WaitThread。java:39)waitingtolock0xef63bf08(ajava。lang。Object)locked0xef63beb8(ajava。util。ArrayList)atjava。lang。Thread。run(Thread。java:595)
临界区的设置,是为了保证其内部的代码执行的原子性和完整性。但是因为临界区在任何时间只允许线程串行通过,这和我们多线程的程序的初衷是相反的。如果在多线程的程序中,大量使用synchronized,或者不适当的使用了它,会造成大量线程在临界区的入口等待,造成系统的性能大幅下降。如果在线程DUMP中发现了这个情况,应该审查源码,改进程序。
再看WaitSet里面的线程。当线程获得了Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(一般就是被synchronized的对象)的wait()方法,放弃Monitor,进入WaitSet队列。只有当别的线程在该对象上调用了notify()或者notifyAll(),WaitSet队列中线程才得到机会去竞争,但是只有一个线程获得对象的Monitor,恢复到运行态。在WaitSet中的线程,DUMP中表现为:inObject。wait()。如下:Thread1prio10tid0x08223250nid0xainObject。wait()〔0xef47a000。。0xef47aa38〕atjava。lang。Object。wait(NativeMethod)waitingon0xef63beb8(ajava。util。ArrayList)atjava。lang。Object。wait(Object。java:474)attestthread。MyWaitThread。run(MyWaitThread。java:40)locked0xef63beb8(ajava。util。ArrayList)atjava。lang。Thread。run(Thread。java:595)综上,一般CPU很忙时,则关注runnable的线程,CPU很闲时,则关注waitingformonitorentry的线程。JDK5。0的Lock
上面提到如果synchronized和monitor机制运用不当,可能会造成多线程程序的性能问题。在JDK5。0中,引入了Lock机制,从而使开发者能更灵活的开发高性能的并发多线程程序,可以替代以往JDK中的synchronized和Monitor的机制。但是,要注意的是,因为Lock类只是一个普通类,JVM无从得知Lock对象的占用情况,所以在线程DUMP中,也不会包含关于Lock的信息,关于死锁等问题,就不如用synchronized的编程方式容易识别。关键状态示例显示BLOCKED状态packagejstack;publicclassBlockedState{privatestaticObjectobjectnewObject();publicstaticvoidmain(String〔〕args){RunnabletasknewRunnable(){Overridepublicvoidrun(){synchronized(object){longbeginSystem。currentTimeMillis();longendSystem。currentTimeMillis();让线程运行5分钟,会一直持有object的监视器while((endbegin)5601000){}}}};newThread(task,t1)。start();newThread(task,t2)。start();}}
先获取object的线程会执行5分钟,这5分钟内会一直持有object的监视器,另一个线程无法执行处在BLOCKED状态:FullthreaddumpJavaHotSpot(TM)ServerVM(20。12b01mixedmode):DestroyJavaVMprio6tid0x00856c00nid0x1314waitingoncondition〔0x00000000〕java。lang。Thread。State:RUNNABLEt2prio6tid0x27d7a800nid0x1350waitingformonitorentry〔0x2833f000〕java。lang。Thread。State:BLOCKED(onobjectmonitor)atjstack。BlockedState1。run(BlockedState。java:17)waitingtolock0x1cfcdc00(ajava。lang。Object)atjava。lang。Thread。run(Thread。java:662)t1prio6tid0x27d79400nid0x1338runnable〔0x282ef000〕java。lang。Thread。State:RUNNABLEatjstack。BlockedState1。run(BlockedState。java:22)locked0x1cfcdc00(ajava。lang。Object)atjava。lang。Thread。run(Thread。java:662)
通过threaddump可以看到:t2线程确实处在BLOCKED(onobjectmonitor)。waitingformonitorentry等待进入synchronized保护的区域。显示WAITING状态packagejstack;publicclassWaitingState{privatestaticObjectobjectnewObject();publicstaticvoidmain(String〔〕args){RunnabletasknewRunnable(){Overridepublicvoidrun(){synchronized(object){longbeginSystem。currentTimeMillis();longendSystem。currentTimeMillis();让线程运行5分钟,会一直持有object的监视器while((endbegin)5601000){try{进入等待的同时,会进入释放监视器object。wait();}catch(InterruptedExceptione){e。printStackTrace();}}}}};newThread(task,t1)。start();newThread(task,t2)。start();}}FullthreaddumpJavaHotSpot(TM)ServerVM(20。12b01mixedmode):DestroyJavaVMprio6tid0x00856c00nid0x1734waitingoncondition〔0x00000000〕java。lang。Thread。State:RUNNABLEt2prio6tid0x27d7e000nid0x17f4inObject。wait()〔0x2833f000〕java。lang。Thread。State:WAITING(onobjectmonitor)atjava。lang。Object。wait(NativeMethod)waitingon0x1cfcdc00(ajava。lang。Object)atjava。lang。Object。wait(Object。java:485)atjstack。WaitingState1。run(WaitingState。java:26)locked0x1cfcdc00(ajava。lang。Object)atjava。lang。Thread。run(Thread。java:662)t1prio6tid0x27d7d400nid0x17f0inObject。wait()〔0x282ef000〕java。lang。Thread。State:WAITING(onobjectmonitor)atjava。lang。Object。wait(NativeMethod)waitingon0x1cfcdc00(ajava。lang。Object)atjava。lang。Object。wait(Object。java:485)atjstack。WaitingState1。run(WaitingState。java:26)locked0x1cfcdc00(ajava。lang。Object)atjava。lang。Thread。run(Thread。java:662)
可以发现t1和t2都处在WAITING(onobjectmonitor),进入等待状态的原因是调用了inObject。wait()。通过J。U。C包下的锁和条件队列,也是这个效果,大家可以自己实践下。显示TIMEDWAITING状态packagejstack;importjava。util。concurrent。TimeUnit;importjava。util。concurrent。locks。Condition;importjava。util。concurrent。locks。Lock;importjava。util。concurrent。locks。ReentrantLock;publicclassTimedWaitingState{java的显示锁,类似java对象内置的监视器privatestaticLocklocknewReentrantLock();锁关联的条件队列(类似于object。wait)privatestaticConditionconditionlock。newCondition();publicstaticvoidmain(String〔〕args){RunnabletasknewRunnable(){Overridepublicvoidrun(){加锁,进入临界区lock。lock();try{condition。await(5,TimeUnit。MINUTES);}catch(InterruptedExceptione){e。printStackTrace();}解锁,退出临界区lock。unlock();}};newThread(task,t1)。start();newThread(task,t2)。start();}}FullthreaddumpJavaHotSpot(TM)ServerVM(20。12b01mixedmode):DestroyJavaVMprio6tid0x00856c00nid0x169cwaitingoncondition〔0x00000000〕java。lang。Thread。State:RUNNABLEt2prio6tid0x27d7d800nid0xc30waitingoncondition〔0x2833f000〕java。lang。Thread。State:TIMEDWAITING(parking)atsun。misc。Unsafe。park(NativeMethod)parkingtowaitfor0x1cfce5b8(ajava。util。concurrent。locks。AbstractQueuedSynchronizerConditionObject)atjava。util。concurrent。locks。LockSupport。parkNanos(LockSupport。java:196)atjava。util。concurrent。locks。AbstractQueuedSynchronizerConditionObject。await(AbstractQueuedSynchronizer。java:2116)atjstack。TimedWaitingState1。run(TimedWaitingState。java:28)atjava。lang。Thread。run(Thread。java:662)t1prio6tid0x280d0c00nid0x16e0waitingoncondition〔0x282ef000〕java。lang。Thread。State:TIMEDWAITING(parking)atsun。misc。Unsafe。park(NativeMethod)parkingtowaitfor0x1cfce5b8(ajava。util。concurrent。locks。AbstractQueuedSynchronizerConditionObject)atjava。util。concurrent。locks。LockSupport。parkNanos(LockSupport。java:196)atjava。util。concurrent。locks。AbstractQueuedSynchronizerConditionObject。await(AbstractQueuedSynchronizer。java:2116)atjstack。TimedWaitingState1。run(TimedWaitingState。java:28)atjava。lang。Thread。run(Thread。java:662)
可以看到t1和t2线程都处在java。lang。Thread。State:TIMEDWAITING(parking),这个parking代表是调用的JUC下的工具类,而不是java默认的监视器。案例分析问题场景CPU飙高,load高,响应很慢一个请求过程中多次dump;对比多次dump文件的runnable线程,如果执行的方法有比较大变化,说明比较正常。如果在执行同一个方法,就有一些问题了;查找占用CPU最多的线程使用命令:topHppid(pid为被测系统的进程号),找到导致CPU高的线程ID,对应threaddump信息中线程的nid,只不过一个是十进制,一个是十六进制;在threaddump中,根据top命令查找的线程id,查找对应的线程堆栈信息;CPU使用率不高但是响应很慢
进行dump,查看是否有很多threadstruck在了io、数据库等地方,定位瓶颈原因;请求无法响应
多次dump,对比是否所有的runnable线程都一直在执行相同的方法,如果是的,恭喜你,锁住了!死锁
死锁经常表现为程序的停顿,或者不再响应用户的请求。从操作系统上观察,对应进程的CPU占用率为零,很快会从top或prstat的输出中消失。
比如在下面这个示例中,是个较为典型的死锁情况:Thread1prio5tid0x00acc490nid0xe50waitingformonitorentry〔0x02d3f000。。0x02d3fd68〕atdeadlockthreads。TestThread。run(TestThread。java:31)waitingtolock0x22c19f18(ajava。lang。Object)locked0x22c19f20(ajava。lang。Object)Thread0prio5tid0x00accdb0nid0xdecwaitingformonitorentry〔0x02cff000。。0x02cff9e8〕atdeadlockthreads。TestThread。run(TestThread。java:31)waitingtolock0x22c19f20(ajava。lang。Object)locked0x22c19f18(ajava。lang。Object)
在JAVA5中加强了对死锁的检测。线程Dump中可以直接报告出Java级别的死锁,如下所示:FoundoneJavaleveldeadlock:Thread1:waitingtolockmonitor0x0003f334(object0x22c19f18,ajava。lang。Object),whichisheldbyThread0Thread0:waitingtolockmonitor0x0003f314(object0x22c19f20,ajava。lang。Object),whichisheldbyThread1热锁
热锁,也往往是导致系统性能瓶颈的主要因素。其表现特征为:由于多个线程对临界区,或者锁的竞争,可能出现:频繁的线程的上下文切换:从操作系统对线程的调度来看,当线程在等待资源而阻塞的时候,操作系统会将之切换出来,放到等待的队列,当线程获得资源之后,调度算法会将这个线程切换进去,放到执行队列中。大量的系统调用:因为线程的上下文切换,以及热锁的竞争,或者临界区的频繁的进出,都可能导致大量的系统调用。大部分CPU开销用在系统态:线程上下文切换,和系统调用,都会导致CPU在系统态运行,换而言之,虽然系统很忙碌,但是CPU用在用户态的比例较小,应用程序得不到充分的CPU资源。随着CPU数目的增多,系统的性能反而下降。因为CPU数目多,同时运行的线程就越多,可能就会造成更频繁的线程上下文切换和系统态的CPU开销,从而导致更糟糕的性能。
上面的描述,都是一个scalability(可扩展性)很差的系统的表现。从整体的性能指标看,由于线程热锁的存在,程序的响应时间会变长,吞吐量会降低。
那么,怎么去了解热锁出现在什么地方呢?
一个重要的方法是结合操作系统的各种工具观察系统资源使用状况,以及收集Java线程的DUMP信息,看线程都阻塞在什么方法上,了解原因,才能找到对应的解决方法。JVM重要线程
JVM运行过程中产生的一些比较重要的线程罗列如下:
点击下方,第一时间了解华为云新鲜技术
华为云博客大数据博客AI博客云计算博客开发者中心华为云
2023年养老金上涨有好信号!企事业单位退休分别能涨多少钱?好消息来了!2023年养老金上涨迎来了积极信号,企事业单位退休人员今年都能涨多少钱?算给你看春节临近,时间也来到2023年1月,目前退休人员最关心的应该就是2023年养老金调整了,
今年春节档总票房破30亿2023年1月22日,中国农历兔年正月初一,不少民众来到位于贵州省遵义市务川仡佬族苗族自治县丹砂街道的一家电影院观影,度过春节假期。中新社记者瞿宏伦摄2023年1月24日8时23分
里海之滨的春节坚守对于中国人来说,春节是一年当中最重要的节日。本应万家团圆的日子里,远在哈萨克斯坦的中国建设者照常坚守在油田现场,1年365天,1天24小时,一刻也不能停歇。王帅是中国石化国勘哈萨克
爆料!梅西或像巴萨一样被巴黎圣日耳曼踢出球队!不续签另有内幕1月25日最新消息,据法甲巴黎圣日耳曼跟队记者爆料,梅西迟迟还没没有跟巴黎签约是另有原因!跟队记者透露,目前的巴黎圣日耳曼有些比较严重的财政问题,问题的来源是由于球队一心要扶持姆巴
喝酒脸红的朋友注意了喝酒前我喝酒脸红,会过敏,不能喝酒!酒桌上没事!喝酒脸红的人能喝!喝酒后好难受,我要死了!不能喝酒的朋友们是不是每次听到这句话都特别的无奈关于这件事,我是深有体会,本人就是这种情况
大年初二回娘家的朋友圈文案,句句暖心1不管多大,回了娘家,永远都是孩子。2每一次归途都是一种享受,每一次回家都是一种惊喜。3回娘家的路,是世界上最幸福的路。4回娘家的路,风都是暖的,天上的云朵也是棉花糖做的。5世界上
入选世界文化遗产,是我国最古老的西式炮台建筑群之一,就在澳门有人说登高望远,不是为了让整个世界看见,而是为了看见整个世界。深远的内涵我们暂且不说,单就表面意思也是如此。作为一个狂热的旅游爱好者,我也是十分喜欢那种极目而迥望,一览天地宽的景致
南方早班车广东预计正月初五起迎春节假期返程高峰广东预计正月初五起迎返程高峰广州地铁发布运营调整日历春节假期前三天,全国查获酒驾醉驾1万余起我国新能源汽车产销连续8年全球第一乌克兰政府同意多名官员的辞职申请满江红票房反超流浪地球
Netflix扩大与龙腾世纪赦免制片人韩国赤犬文化之家的合作Netflix和龙腾世纪赦免制作公司RedDogCultureHouse通过一项新的制作协议扩大了他们目前的关系,以制作更多的动画系列总部位于韩国首尔的RedDogCultureH
日本科学家组团投奔中国,引起日本国内一片哗然,他们为何来中国如果您喜欢这篇作品,欢迎点击右上方关注。感谢您的鼓励与支持,希望能给您带来舒适的阅读体验。到底是个人愿望重要,还是家国情怀重要,这是一个难题,古今中外有很多人都面临着这样的选择,他
吉利汽车将推中高端新能源系列1月22日早间,吉利汽车集团官方微博发布了一条视频,宣布吉利品牌将在2023年推出一个全新的中高端新能源系列。据悉,该系列会由多款全新纯电插混增程产品构成,将搭载最新智能技术和全新