Andoird性能优化 - 死锁监控与其背后的小知识

Dumping all threads without appropriate locks held: thread list lock mutator lock报错原因? 以下报错是什么原因呢? 2021-07-16 16:30:15 07-16 16:30:15.825 11812 13216 I HwAudioRecordImpl: sendStateChangedIntent, state=1 2021-07-16 16:30:15 07-16 16:30:15.826 11812 13216 I HwAudioRecordImpl: sendStateChangedIntent, state=1 2021-07-16 16:30:15 07-16 16:3. 阅读详情

前言

“死锁”,这个从接触程序开发的时候就会经常听到的词,它其实也可以被称为一种“艺术”,即互斥资源访问循环的艺术,在Android中,如果主线程产生死锁,那么通常会以ANR结束app的生命周期,如果是两个子线程的死锁,那么就会白白浪费cpu的调度资源,同时也不那么容易被发现,就像一颗“肿瘤”,永远藏在app中。当然,本篇介绍的是业内常见的死锁监控手段,同时也希望通过死锁,去挖掘更加底层的知识,同时让我们更加了解一些常用的监控手段。

我们很容易模拟一个死锁操作,比如

val lock1 = Object()
val lock2 = Object()

Thread ({
    synchronized(lock1){
        Thread.sleep(2000)

        synchronized(lock2){

        }
    }
},"thread222").start()

Thread ({
    synchronized(lock2) {
        Thread.sleep(1000)

        synchronized(lock1) {

        }
    }
},"thread111").start()

因为thread111跟thread222都同时持有着对方想要的临界资源(互斥资源),因此这两个线程都处在互相等待对方的状态。

死锁检测

我们怎么判断死锁:是否存在一个线程所持有的锁被另一个线程所持有,同时另一个线程也持有该线程所需要的锁,因此我们需要知道以下信息才能进行死锁分析:

  1. 线程所要获取的锁是什么
  2. 该锁被什么线程所持有
  3. 是否产生循环依赖的限制(本篇就不涉及了,因为我们知道了前两个就可以自行分析了)

线程Block状态

通过我们对synchronized的了解,当线程多次获取不到锁的时候,此时线程就会进入悲观锁状态,因此线程就会尝试进入阻塞状态,避免进一步的cpu资源消耗,因此此时两个线程都会处于block 阻塞的状态,我们就能知道,处于被block状态的线程就有可能产生死锁(只是有可能),我们可以通过遍历所有线程,查看是否处于block状态,来进行死锁判断的第一步

val threads = getAllThread()
threads.forEach {
    if(it?.isAlive == true && it.state == Thread.State.BLOCKED){
       进入死锁判断
    }
}

获取所有线程

private fun getAllThread():Array<Thread?>{
    val threadGroup = Thread.currentThread().threadGroup;
    val total = Thread.activeCount()
    val array = arrayOfNulls<Thread>(total)
    threadGroup?.enumerate(array)
    return array

}

通过对线程的判断,我们能够排除大部分非死锁的线程,那么下一步我们要怎么做呢?如果线程发生了死锁,那么一定拥有一个已经持有的互斥资源并且不释放才有可能造成死锁对不对!那么我们下一步,就是要检测当前线程所持有的锁,如果两个线程同时持有对方所需要的锁,那么就会产生死锁

获取当前线程所请求的锁

虽然我们在java层没有相关的api提供给我们获取线程当前想要请求的锁,但是在我们的native层,却可以轻松做到,因为它在art中得到更多的支持。

ObjPtr<mirror::Object> Monitor::GetContendedMonitor(Thread* thread) {
    // This is used to implement JDWP's ThreadReference.CurrentContendedMonitor, and has a bizarre
    // definition of contended that includes a monitor a thread is trying to enter...
    ObjPtr<mirror::Object> result = thread->GetMonitorEnterObject();
    if (result == nullptr) {
        // ...but also a monitor that the thread is waiting on.
        MutexLock mu(Thread::Current(), *thread->GetWaitMutex());
        Monitor* monitor = thread->GetWaitMonitor();
        if (monitor != nullptr) {
            result = monitor->GetObject();
        }
    }
    return result;
}

其中第一步尝试着通过thread->GetMonitorEnterObject()去拿

mirror::Object* GetMonitorEnterObject() const REQUIRES_SHARED(Locks::mutator_lock_) {
        return tlsPtr_.monitor_enter_object;
}

其中tlsPtr_ 其实就是art虚拟机中对于线程ThreadLocal的代表,即代表着只属于线程的本地对象,会先尝试从这里拿,拿不到的话通过Thread类中的wait_mutex_对象去拿

Mutex* GetWaitMutex() const LOCK_RETURNED(wait_mutex_) {
        return wait_mutex_;
}

GetContendedMonitor 提供了一个方法查询当前线程想要的锁对象,这个锁对象以ObjPtrmirror::Object对象表示,其中mirror::Object类型是art中相对应于java层的Object类的代表,我们了解一下即可。看到这里我们可能还有一个疑问,这个Thread* thread的入参是什么呢?(其实是nativePeer,下文我们会了解)

我们有办法能够查询到线程当前请求的锁,那么这个锁被谁持有呢?只有解决这两个问题,我们才能进行死锁的判断对不对,我们继续往下

通过锁获取当前持有的线程

我们还记得上文中返回的锁对象是以ObjPtrmirror::Object表示的,当然,art中同样提供了方法,让我们通过这个锁对象去查询当前是哪个线程持有

uint32_t Monitor::GetLockOwnerThreadId(ObjPtr<mirror::Object> obj) {
    DCHECK(obj != nullptr);
    LockWord lock_word = obj->GetLockWord(true);
    switch (lock_word.GetState()) {
        case LockWord::kHashCode:
            // Fall-through.
        case LockWord::kUnlocked:
            return ThreadList::kInvalidThreadId;
        case LockWord::kThinLocked:
            return lock_word.ThinLockOwner();
        case LockWord::kFatLocked: {
            Monitor* mon = lock_word.FatLockMonitor();
            return mon->GetOwnerThreadId();
        }
        default: {
            LOG(FATAL) << "Unreachable";
            UNREACHABLE();
        }
    }
}

这里函数比较简单,如果当前调用正常,那么执行的就是LockWord::kFatLocked,返回的是native层的Thread的tid,最终是以uint32_t类型表示

注意这里GetLockOwnerThreadId中返回的Thread id千万不要跟Java层的Thread对象的tid混淆,这里的tid才是真正的线程id标识

线程启动

我们来看一下native层主线程的启动,它随着art虚拟机的启动随即启动,我们都知道java层的线程其实在没有跟操作系统的线程绑定的时候,它只能算是一块内存!只要经过与native线程绑定后,这时的Thread才能真正具备线程调度的能力,下面我们以主线程启动举例子:

thread.cc
void Thread::FinishStartup() {
Runtime* runtime = Runtime::Current();
CHECK(runtime->IsStarted());

// Finish attaching the main thread.
ScopedObjectAccess soa(Thread::Current());
// 这里是关键,为什么主线程称为“main线程”的原因
soa.Self()->CreatePeer("main", false, runtime->GetMainThreadGroup());
soa.Self()->AssertNoPendingException();

runtime->RunRootClinits(soa.Self());
soa.Self()->NotifyThreadGroup(soa, runtime->GetMainThreadGroup());
soa.Self()->AssertNoPendingException();
}

可以看到,为什么主线程被称为“主线程”,是因为在art虚拟机启动的时候,通过CreatePeer函数,创建的名称是“main”,CreatePeer是native线程中非常重要的存在,所有线程创建都经过它,这个函数有点长,笔者这里做了删减


void Thread::CreatePeer(const char* name, bool as_daemon, jobject thread_group) {
    Runtime* runtime = Runtime::Current();
    CHECK(runtime->IsStarted());
    JNIEnv* env = tlsPtr_.jni_env;

    if (thread_group == nullptr) {
        thread_group = runtime->GetMainThreadGroup();
    }
    // 设置了线程名字
    ScopedLocalRef<jobject> thread_name(env, env->NewStringUTF(name));
    // Add missing null check in case of OOM b/18297817
    if (name != nullptr && thread_name.get() == nullptr) {
        CHECK(IsExceptionPending());
        return;
    }
    // 设置Thread的各种属性
    jint thread_priority = GetNativePriority();
    jboolean thread_is_daemon = as_daemon;
    // 创建了一个java层的Thread对象,名字叫做peer
    ScopedLocalRef<jobject> peer(env, env->AllocObject(WellKnownClasses::java_lang_Thread));
    if (peer.get() == nullptr) {
        CHECK(IsExceptionPending());
        return;
    }
    {
        ScopedObjectAccess soa(this);
        tlsPtr_.opeer = soa.Decode<mirror::Object>(peer.get()).Ptr();
    }
    env->CallNonvirtualVoidMethod(peer.get(),
                                  WellKnownClasses::java_lang_Thread,
                                  WellKnownClasses::java_lang_Thread_init,
                                  thread_group, thread_name.get(), thread_priority, thread_is_daemon);
    if (IsExceptionPending()) {
        return;
    }
    // 看到这里,非常关键,self 指向了当前native Thread对象 self->Thread
    Thread* self = this;
    DCHECK_EQ(self, Thread::Current());
    env->SetLongField(peer.get(),
                      WellKnownClasses::java_lang_Thread_nativePeer,
                      reinterpret_cast64<jlong>(self));

    ScopedObjectAccess soa(self);
    StackHandleScope<1> hs(self);
   ....
}

这里其实就是一次jni调用,把java中的Thread 的nativePeer 进行了赋值,而赋值的内容,正是通过了这个调用SetLongField

env->SetLongField(peer.get(),
                      WellKnownClasses::java_lang_Thread_nativePeer,
                      reinterpret_cast64<jlong>(self));

这里我们简单了解一下SetLongField,如果进行过jni开发的同学应该能过明白,其实就是把peer.get()得到的对象(其实就是java层的Thread对象)的nativePeer属性,赋值为了self(native层的Thread对象的指针),并强转换为了jlong类型。我们接下来回到java层

Thread.java
private volatile long nativePeer;

说了一大堆,那么这个nativePeer究竟是个什么?通过上面的代码分析,我们能够明白了,Thread.java中的nativePeer就是一个指针,它所指向的内容正是native层中的Thread

nativePeer 与 native Thread tid 与java Thread tid

经过了上面一段落,我们了解了nativePeer,那么我们继续对比一下java层Thread tid 与native层Thread tid。我们通过在kotlin/java中,调用Thread对象的id属性,其实得到的是这个

private long tid;

它的生成方法如下

/* Set thread ID */
tid = nextThreadID();

private static synchronized long nextThreadID() {
    return ++threadSeqNumber;
}

可以看到,虽然它的确能代表一个java层中Thread的标识,但是生成其实可以看到,他也仅仅是一个普通的累积id生成,同时也并没有在native层中被当作唯一标识进行使用。

而native Thread 的 tid属性,才是真正的线程id

在art中,通过GetTid获取

pid_t GetTid() const {
    return tls32_.tid;
}

同时我们也可以注意到,tid 是保存在 tls32_结构体中,并且其位于Thread对象的开头,从内存分布上看,tid位于state_and_flags、suspend_count、think_lock_thread_id之后,还记得我们上面说过的nativePeer嘛?我们一直强调native是Thread的指针对象

因此我们可以通过指针的偏移,从而算出nativePeer到tid的换算公式,即nativePeer指针向下偏移三位就找到了tid(因为state_and_flags,state_and_flags,think_lock_thread_id都是int类型,那么对应的指针也就是int * )这里有点绕,因为涉及指针的内容

int *pInt = reinterpret_cast<int *>(native_peer);
//地址 +3,得到tid
pInt = pInt + 3;
return *pInt;

nativePeer对象因为就在java层,我们很容易通过反射就能拿到

val nativePeer = Thread::class.java.getDeclaredField("nativePeer")
nativePeer.isAccessible = true
val currentNativePeer = nativePeer.get(it)

这里我们通过nativePeer换算成tid可以写成一个jni方法

external fun nativePeer2Threadid(nativePeer:Long):Int

实现就是

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_signal_MainActivity_nativePeer2Threadid(JNIEnv *env, jobject thiz,
                                                         jlong native_peer) {
    if (native_peer != 0) {
            //long 强转 int
            int *pInt = reinterpret_cast<int *>(native_peer);
            //地址 +3,得到 native id
            pInt = pInt + 3;
            return *pInt;
        }
    }
}

dlsym与调用

我们上面终于把死锁能涉及到的点都讲完,比如如何获取线程所请求的锁,当前锁又被那个线程持有,如何通过nativePeer获取Thread id 做了分析,但是还有一个点我们还没能解决,就是如何调用这些函数。我们需要调用的是GetContendedMonitor,GetLockOwnerThreadId,这个时候dlsym系统调用就出来了,我们可以通过dlsym 进行调用我们想要调用的函数

void* dlsym(void* __handle, const char* __symbol);

这里的symbol是什么呢?其实我们所有的elf(so也是一种elf文件)的所有调用函数都会生成一个符号,代表着这个函数,它在elf的.text中。而我们android中,就会通过加载so的方式加载系统库,加载的系统库libart.so里面就包含着我们想要调用的函数GetContendedMonitor,GetLockOwnerThreadId的符号

我们可以通过objdump -t libart.so 查看符号

这里我们直接给出来各个符号,读者可以直接用objdump查看符号

GetContendedMonitor 对应的符号是

_ZN3art7Monitor19GetContendedMonitorEPNS_6ThreadE

GetLockOwnerThreadId 对应的符号

sdk <= 29
_ZN3art7Monitor20GetLockOwnerThreadIdEPNS_6mirror6ObjectE

>29是这个
_ZN3art7Monitor20GetLockOwnerThreadIdENS_6ObjPtrINS_6mirror6ObjectEEE

系统限制

然后到这里,我们还是没能完成调用,因为dlsym等dl系列的系统调用,因为从Android 7.0开始,Android系统开始阻止App中直接使用dlopen(), dlsym()等函数打开系统动态库,好家伙!谷歌大兄弟为了安全的考虑,做了很多限制。但是这个防君子不防程序员,业内依旧有很多绕过系统的限制的方法,我们看一下dlsym

__attribute__((__weak__))
void* dlsym(void* handle, const char* symbol) {
    const void* caller_addr = __builtin_return_address(0);
    return __loader_dlsym(handle, symbol, caller_addr);
}

__builtin_return_address是Linux一个内建函数(通常由编译器添加),__builtin_return_address(0)用于返回当前函数的返回地址。

在__loader_dlsym 会进行返回地址的校验,如果此时返回地址不是属于系统库的地址,那么调用就不成功,这也是art虚拟机保护手段,因此我们很容易就得出一个想法,我们是不是可以用系统的某个函数去调用dlsym,然后把结果给到我们自己的函数消费就可以了?是的,业内已经有很多这个方案了,比如ndk_dlopen

我们拿arm架构进行分析,arm架构中LR寄存器就是保存了当前函数的返回地址,那么我们是不是在调用dlsym时可以通过汇编代码直接修改LR寄存器的地址为某个系统库的函数地址就可以了?嗯!是的,但是我们还需要把原来的LR地址给保存起来,不然就没办法还原原来的调用了。

这里我们拿ndk_dlopen的实现举例子

if (SDK_INT <= 0) {
    char sdk[PROP_VALUE_MAX];
    __system_property_get("ro.build.version.sdk", sdk);
    SDK_INT = atoi(sdk);
    LOGI("SDK_INT = %d", SDK_INT);
    if (SDK_INT >= 24) {
        static __attribute__((__aligned__(PAGE_SIZE))) uint8_t __insns[PAGE_SIZE];
        STUBS.generic_stub = __insns;
        mprotect(__insns, sizeof(__insns), PROT_READ | PROT_WRITE | PROT_EXEC);

        // we are currently hijacking "FatalError" as a fake system-call trampoline
        uintptr_t pv = (uintptr_t)(*env)->FatalError;
        uintptr_t pu = (pv | (PAGE_SIZE - 1)) + 1u;
        uintptr_t pd = (pv & ~(PAGE_SIZE - 1));
        mprotect((void *)pd, pv + 8u >= pu ? PAGE_SIZE * 2u : PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC);
        quick_on_stack_back = (void *)pv;
  // arm架构汇编实现
#elif defined(__arm__)
            // r0~r3
            /*
             0x0000000000000000:     08 E0 2D E5     str lr, [sp, #-8]!
             0x0000000000000004:     02 E0 A0 E1     mov lr, r2
             0x0000000000000008:     13 FF 2F E1     bx r3
            */
            memcpy(__insns, "\x08\xE0\x2D\xE5\x02\xE0\xA0\xE1\x13\xFF\x2F\xE1", 12);
            if ((pv & 1u) != 0u) { // Thumb
                /*
                 0x0000000000000000:     0C BC   pop {r2, r3}
                 0x0000000000000002:     10 47   bx r2
                */
                memcpy((void *)(pv - 1), "\x0C\xBC\x10\x47", 4);
            } else {
                /*
                 0x0000000000000000:     0C 00 BD E8     pop {r2, r3}
                 0x0000000000000004:     12 FF 2F E1     bx r2
                */
                memcpy(quick_on_stack_back, "\x0C\x00\xBD\xE8\x12\xFF\x2F\xE1", 8);
            } //if

其中我们拿(*env)->FatalError作为了混淆系统调用的stub,我们参照着流程图去理解上述代码:

  • 02 E0 A0 E1 mov lr, r2 把r2寄存器的内容放到了lr寄存器,这个r2存的东西就是FatalError的地址
  • 0x0000000000000008: 13 FF 2F E1 bx r3 ,通过bx指令调转,就可以正常执行我们的dlsym了,r3就是我们自己的dlsym的地址
  • 0x0000000000000000: 0C 00 BD E8 pop {r2, r3} 调用完r3寄存器的方法把r2寄存器放到调用栈下,提供给后面的执行进行消费
  • 0x0000000000000004: 12 FF 2F E1 bx r2 ,最后就回到了我们的r2,完成了一次调用

总之,我们想要做到dl系列的调用,就是想尽方法去修改对应架构的函数返回地址的数值。

死锁检测所有代码


const char *get_lock_owner_symbol_name() {
    if (SDK_INT <= 29) {
        return "_ZN3art7Monitor20GetLockOwnerThreadIdEPNS_6mirror6ObjectE";
    } else {
        return "_ZN3art7Monitor20GetLockOwnerThreadIdENS_6ObjPtrINS_6mirror6ObjectEEE";
    }
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_signal_MyHandler_deadLockMonitor(JNIEnv *env, jobject thiz,
                                                  jlong native_thread) {
    //1、初始化
    ndk_init(env);

    //2、打开动态库libart.so
    void *so_addr = ndk_dlopen("libart.so", RTLD_NOLOAD);

    void * get_contended_monitor = ndk_dlsym(so_addr, "_ZN3art7Monitor19GetContendedMonitorEPNS_6ThreadE");
    void * get_lock_owner_thread = ndk_dlsym(so_addr, get_lock_owner_symbol_name());

    int monitor_thread_id = 0;
    if (get_contended_monitor != nullptr && get_lock_owner_thread != nullptr) {
        //1、调用一下获取monitor的函数,返回当前线程想要竞争的monitor
        int monitorObj = ((int (*)(long)) get_contended_monitor)(native_thread);
        if (monitorObj != 0) {
            // 2、获取这个monitor被哪个线程持有,返回该线程id
            monitor_thread_id = ((int (*)(int)) get_lock_owner_thread)(monitorObj);
        } else {
            monitor_thread_id = 0;
        }
    }
    return monitor_thread_id;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_signal_MainActivity_nativePeer2Threadid(JNIEnv *env, jobject thiz,
                                                         jlong native_peer) {
    if (native_peer != 0) {
        if (SDK_INT > 20) {
            //long 强转 int
            int *pInt = reinterpret_cast<int *>(native_peer);
            //地址 +3,得到 native id
            pInt = pInt + 3;
            return *pInt;
        }
    }
}

extern "C" jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    char sdk[PROP_VALUE_MAX];
    __system_property_get("ro.build.version.sdk", sdk);
    SDK_INT = atoi(sdk);
    return JNI_VERSION_1_4;
}

对应java层

external fun deadLockMonitor(nativeThread:Long):Int

private fun getAllThread():Array<Thread?>{
    val threadGroup = Thread.currentThread().threadGroup;
    val total = Thread.activeCount()
    val array = arrayOfNulls<Thread>(total)
    threadGroup?.enumerate(array)

    return array

}
external fun nativePeer2Threadid(nativePeer:Long):Int

总结

我们通过死锁这个例子,去了解了native层Thread的相关方法,同时也了解了如何使用dlsym打开函数符号并调用。本篇Android性能优化就到此结束,感谢观看!

作者:Pika
链接:https://juejin.cn/post/7159784805293359141

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末优快云官方认证微信卡片免费领取↓↓↓

复现CVE-2014-085(Apache ZooKeeper 未授权访问漏洞) 默认安装配置完的zookeeper允许未授权访问,管理员未配置访问控制列表(ACL)。导致攻击者可以在默认开放的2181端口下通过执行envi命令获得大量敏感信息(系统名称、java环境)导致任意用户可以在网络不受限的情况下进行未授权访问读取数据。 阅读详情

相关推荐

R语言实战:GEO单细胞数据下载与自动化整理全流程(附代码)

本文详细介绍了使用R语言从GEO数据库下载scRNA-seq数据并实现自动化整理的完整流程。通过GEOquery和Seurat等工具包,读者可以高效获取单细胞RNA测序数据,完成从原始数据下载到预处理的全过程,最终生成可直接用于分析的Seurat对象。文章包含完整代码示例,特别适合生物信息学研究者处理单细胞数据。

weixin_29061821的博客 425

ANR trace详解

文章目录1. Trace pattern2. 解释3. 关键词对照 1. Trace pattern 1."Signal Catcher" daemon prio=5 tid=3 Runnable 2. | group="system" sCount=0 dsCount=0 obj=0x32c050d0 self=0x7f97dd1400 3. | sysTid=9729 nice=0 cgrp=default sched=0/0 handle=0x7fa200e450 4. | state=R sc

wd229047557的博客 6499

Transformer在CV里太耗算力?手把手拆解Restormer的MDTA与GDFN模块

本文深度解析了CVPR2022最佳论文提名作品Restormer的创新模块MDTA与GDFN,有效解决了Transformer在计算机视觉领域的高计算复杂度问题。通过通道注意力降维和门控前馈网络设计,Restormer在图像恢复重建任务中显著降低计算量,同时保持高性能,为实时图像处理提供了可行方案。

weixin_30258901的博客 444

Android ANR Trace 详解

本文主要总结一下 Signal Catcher 线程在收到 SIGQUIT(3)后,Dump 信息的流程。 Android SourceCode: 6.0 Keyword:block signal,kRunnable,kSuspended,Checkpoint,traces.txt 1.ART中 block 信号 信号的block在虚拟机

大将军王虎剩的专栏 2万+

保姆级教程:在MounRiver Studio上为CH32V307配置FreeRTOS与LwIP网络栈

本文提供了一份详细的教程,指导如何在MounRiver Studio上为CH32V307配置FreeRTOS与LwIP网络栈。从开发环境搭建到网络功能验证,涵盖了FreeRTOS内核定制、LwIP协议栈配置、硬件抽象层实现等关键步骤,帮助开发者快速掌握RISC-V架构下的嵌入式网络开发。

weixin_29275257的博客 361

保姆级教程:用STM32F103C8T6和MAX6675搞定K型热电偶测温(附模拟/硬件SPI双版本代码)

本文提供STM32F103C8T6与MAX6675实现K型热电偶高精度测温的保姆级教程,涵盖硬件连接、SPI驱动实现及模拟SPI方案,并附双版本代码。详细解析温度数据读取、处理及常见问题排查,助力工业控制、实验室设备等场景的稳定测温系统搭建。

weixin_30765505的博客 587

android Mutator lock exclusive

Android Mutator Lock Exclusive 的深入探讨 Android 操作系统在多线程环境中引入了多种机制来保证数据的一致性和安全性。其中,Mutator Lock 是实现这一目的的重要组成部分。本文将介绍什么是 Mutator Lock Exclusive,以及它如何在 Android 中的重要性,最...

weixin_40894456的博客 383

实战案例七:Claude Code 构建完整的 Web 应用

前面的案例大多是单一功能的演示,本章将展示如何使用 Claude Code 从零开始构建一个完整的 Web 应用,涵盖需求分析、架构设计、编码实现、测试部署的全流程。

gaoxiang_120的专栏 580

Android ANR分析-笔记

ANR:Application Not Responding,即应用无响应。

且听真言 5433

基于 STM32 单片机的实验室多参数安全监测系统设计与实现

本系统以 STM32F103C8T6 单片机为核心,集成温湿度监测、烟雾检测、气体泄漏报警、人体移动监测等功能模块,通过 OLED 显示屏实时显示数据,并支持 Wi-Fi 远程传输。系统可对实验室异常环境参数(如高温、烟雾、燃气泄漏)及非法入侵实时报警,保障实验室安全。本文从硬件选型、电路设计到软件实现提供了完整的实验室安全监测系统方案,可根据实际需求调整传感器类型和通信方式,适用于科研、教学等场景的环境安全防护。:可增加继电器模块控制通风设备或切断电源,集成 ZigBee 组网实现多实验室联动监测。

weixin_42300449的博客 1284

黑猫带你学Nand第8篇:NandFlash的供电情况详解

是指在任何工作条件下,设备允许的最大电压、电流、功率以及温度等参数的界限值。

黑猫奥利奥的博客 1145

FastJNI导致的Android系统死机问题分析

1. 问题现象 问题发生的Android系统版本是7.0(Nougat); 滑动屏幕和按键都无响应,屏幕内容没有任何刷新; watchdog没有重启system_server; 问题现场可以连接adb; 2. 初步分析 对于死机问题,我们需要做一些分析前的准备工作: (1)拿到问题现场,及时充电以保证问题现场不被破坏; (2)如果没有现场可以忽略这一步,通过kill -3 后面...

宋金时的专栏 7891

099、多摄融合与变焦系统:广角到长焦的无缝切换算法

本文分享了多摄融合与变焦系统开发中的实战经验和关键挑战。作者通过一个旗舰机项目案例,揭示了广角到长焦切换时画面抖动问题的根源——融合算法误将视差当作运动补偿,并提出"视差冻结窗口"的解决方案。文章总结了三大多摄融合难点:物理视差处理、亮度差异补偿和切换时机把控,详细阐述了分层架构设计、融合权重计算等核心技术。同时强调了产线校准的重要性,并给出五项实用建议:允许边缘鬼影保留、参数场景化配置、加强产线检测、完善调试接口,以及合理运用AI技术。全文展现了多摄融合技术在时间、空间和感知维度上的复杂平衡艺术。

sundong_81的博客 45

ANR之traces日志线程参数解析大全

一秒内时钟中断的次数等于Hz。ANR故障是Android开发中的重点难点问题,而traces.txt日志的分析则是解决ANR问题的关键所在。执行 adb shell kill -3 pid或者当进程发生anr、native crash等故障的时候,系统会生成traces日志文件,默认输出到 /data/anr/ 目录下。Control groups,是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。其中以下片段为线程的各项状态参数,是本文将要解析的部分。

chuyouyinghe的专栏 1191

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

钉钉 ANR 治理最佳实践 | 定位 ANR 不再雾里看花

相信大家对 Android 的 ANR 问题并不陌生。钉钉作为一个用户数超 5 亿,服务着 2100 万家组织的产品,基本上其他 App 遇到的 ANR 问题,我们都会遇到。 和大家一样,我们最初在分析 ANR Trace 日志的时候,都会不禁怀疑上报的堆栈是否真的有问题,总有一种雾里看花的感觉。 本系列文章主要介绍钉钉在 ANR 治理过程中的思考方向,工具建设,典型问题等,希望能够通过本次分享,为有 ANR 治理诉求的团队提供一定的参考。

嘴巴吃糖了 511

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

性能优化-ANR总结

ANR总结

qq_37776700的博客 1327

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

【Android 逆向】Android 中常用的 so 动态库 ( libdvm.so | libart.so | libandroid_runtime.so | libandroidfw.so )

一、Android 虚拟机涉及的 libdvm.so 动态库 和 libart.so 动态库、 二、Android 运行时相关 libandroid_runtime.so 动态库、 三、Android Framework 层 Native 实现相关 libandroidfw.so 动态库

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 8181

Android ART虚拟机系列: 虚拟机CheckPoint机制分析

线程运行时执行Check Point闭包任务;挂起和恢复线程的执行;虚拟机为了实现这两个功能,在Java线程对应的Native类中添加了变量,通过设置或者来通知线程需要执行挂起任务还是闭包任务。具体执行任务的时机是在函数中,这函数(检查点)被插入到了解释执行和机器码执行的多个流程中,这样,Runnable状态的线程就能快速执行到检查点,完成线程挂起或者执行闭包任务。

Windy的专栏 1465

java mutator_深入理解java synchronized

// 附上CAS 源码分析import java.util.concurrent.atomic.AtomicInteger;public class Main {public static void main(String[] args) {AtomicInteger atomicInteger =new AtomicInteger(4);atomicInteger.addAndGet(6);}}...

weixin_31908827的博客 589

ANR 基本常识分析

当APP不响应、响应慢了、或者WatchDog的监视没有得到回应时,系统就会dump出一个`traces.txt`文件,存放在文件目录:`/data/anr/traces.txt`,通过traces文件,我们可以拿到线程名、堆栈信息、线程当前状态、binder call等信息。当你的项目越做越复杂,或者你的用户达到某个数量级的时候,你的代码不小心出现细小的问题,你会收到各种各样的bug,其中ANR的问题你一定不会陌生。`com.xxxx.moblie` 占用了11%的CPU,其实这并不算多。

kingmasterlife的博客 2125

Android libdvm.so 与 libart.so

Android libdvm.so 与 libart.so 系统升级到5.1之后,发现system/lib/下面没有libdvm.so了,只剩下了libart.so。对于libart模式,从4.4就在Developer optins里面就可以手动选择,到5.1算是转正了。 1,什么是libdvm,libart。 Android KK里引入了ART虚拟机作为Dalvik虚拟机的

可夫小子的频道 2万+

Android ANR日志分析指南

当你的项目越做越复杂,或者你的用户达到某个数量级的时候,你的代码不小心出现细小的问题,你会收到各种各样的bug,其中ANR的问题你一定不会陌生。本文将详细讲解ANR的类型、出现的原因、ANR案例详细分析、经典的案例。 定义 ANR(Application Not Responding) 应用程序无响应。如果你应用程序在UI线程被阻塞太长时间,就会出现ANR,通常出现ANR,系统会弹出一个提示提...

weixin_34274029的博客 2754

android死锁解决方案,【线程死锁】Android多线程死锁产生的原因以及如何避免

一、死锁定义1、生活中的列子两人吃饭,但只有一双筷子,2人轮流吃(同时拥有2只筷子才能吃),某个时候一人拿了左筷子,一人拿了右筷子,两人同时占用一个资源,等待另一个资源,这时候甲等乙吃完并释放他所占的筷子,而乙同样等待甲吃完并释放他所占有的筷子,这样就陷入了一个死循环,谁也无法继续吃饭。这就是死锁2、定义指多线程因竞争资源而造成的一种僵局(互相等待)若无外力作用这些进程都将无法向前推进 。二、死锁...

weixin_42594419的博客 2283

死锁的原因和解决方案

假如我们此时先看t1线程,在对object1对象加锁之后,然后输出了hello1,此时t2线程也开始执行了,再对object2加锁之后,输出了hello2,此时CPU切换到t1线程上,t1线程要对object2对象进行加速,但是object2是被t2线程已经加锁过,此时这个锁对象还没有被释放,于是t1线程阻塞等待,此时CPU切换到t2线程上,t2线程再对object1对象进行加锁,但是object1是被t1线程加锁中的,所以t2线程也阻塞等待。此时我们就很好的破解了循环等待,也就解决了死锁的问题。

qq_63525426的博客 1903

Andorid9.0和10.0下的的libart.so

有时候Android开发需要用nm出libart.so的symbol,10.0以下的话直接在/system/lib下直接拿就可以了;10.0以后,Android把libart.so放到了apex里面,需要用adb pull导出。这里为了方便直接贴出来,有需要就拿: Android9.0-libart.so Android10.0-libart.so 另外多说一句因为从7.0开始,Android对native api的函数库做了限制,以前常用的hook手段dlopen/dlsym受到了限制。目前替代方案

7470

Android 分析ANR和死锁

Android 分析ANR和死锁

好久不见 3万+
上一篇: Android Flutter进阶学习,助你成为全站工程师,迎接未来的技术浪潮
下一篇: 2022最新Android面试题(大厂Android中高级面试题持续更新~)
Agent学习路线
博客等级 码龄6年 1万+粉丝 1902原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值