├── KKThreadMonitor ├── CallStack │ ├── KKCallStack.h │ ├── KKCallStack.m │ ├── KKCallStackSymbol.c │ └── KKCallStackSymbol.h ├── KKThreadMonitor.h └── KKThreadMonitor.m └── README.md /KKThreadMonitor/CallStack/KKCallStack.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKCallStack.h 3 | // KKMagicHook 4 | // 5 | // Created by 吴凯凯 on 2020/4/10. 6 | // Copyright © 2020 吴凯凯. All rights reserved. 7 | // 8 | 9 | #import 10 | #include 11 | 12 | 13 | typedef NS_ENUM(NSUInteger, KKCallStackType) { 14 | KKCallStackTypeAll, //全部线程 15 | KKCallStackTypeMain, //主线程 16 | KKCallStackTypeCurrent //当前线程 17 | }; 18 | 19 | NS_ASSUME_NONNULL_BEGIN 20 | 21 | @interface KKCallStack : NSObject 22 | 23 | + (NSString *)callStackWithType:(KKCallStackType)type; 24 | //+ (NSString *)callStackWithThread_t:(thread_t)thread; 25 | 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /KKThreadMonitor/CallStack/KKCallStack.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKCallStack.m 3 | // KKMagicHook 4 | // 5 | // Created by 吴凯凯 on 2020/4/10. 6 | // Copyright © 2020 吴凯凯. All rights reserved. 7 | // 8 | 9 | #import "KKCallStack.h" 10 | #include "KKCallStackSymbol.h" 11 | 12 | #if defined(__arm64__) 13 | //#define DETAG_INSTRUCTION_ADDRESS(A) ((A) & ~(3UL)) 14 | #define KK_THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT 15 | #define KK_THREAD_STATE ARM_THREAD_STATE64 16 | #define KK_FRAME_POINTER __fp 17 | #define KK_LINKER_POINTER __lr 18 | #define KK_INSTRUCTION_ADDRESS __pc 19 | 20 | #elif defined(__arm__) 21 | //#define DETAG_INSTRUCTION_ADDRESS(A) ((A) & ~(1UL)) 22 | #define KK_THREAD_STATE_COUNT ARM_THREAD_STATE_COUNT 23 | #define KK_THREAD_STATE ARM_THREAD_STATE 24 | #define KK_FRAME_POINTER __r[7] 25 | #define KK_LINKER_POINTER __lr 26 | #define KK_INSTRUCTION_ADDRESS __pc 27 | 28 | #elif defined(__x86_64__) 29 | //#define DETAG_INSTRUCTION_ADDRESS(A) (A) 30 | #define KK_THREAD_STATE_COUNT x86_THREAD_STATE64_COUNT 31 | #define KK_THREAD_STATE x86_THREAD_STATE64 32 | #define KK_FRAME_POINTER __rbp 33 | #define KK_LINKER_POINTER __rlr 34 | #define KK_INSTRUCTION_ADDRESS __rip 35 | 36 | #elif defined(__i386__) 37 | //#define DETAG_INSTRUCTION_ADDRESS(A) (A) 38 | #define KK_THREAD_STATE_COUNT x86_THREAD_STATE32_COUNT 39 | #define KK_THREAD_STATE x86_THREAD_STATE32 40 | #define KK_FRAME_POINTER __ebp 41 | #define KK_LINKER_POINTER __elr 42 | #define KK_INSTRUCTION_ADDRESS __eip 43 | 44 | #endif 45 | 46 | typedef struct { 47 | const uintptr_t *fp; //stp fp, lr, ... 48 | const uintptr_t lr; 49 | } StackFrameFP_LR; 50 | 51 | static thread_t _main_thread; 52 | static int StackMaxDepth = 32; 53 | 54 | @implementation KKCallStack 55 | 56 | + (void)load 57 | { 58 | _main_thread = mach_thread_self(); 59 | } 60 | 61 | + (NSString *)callStackWithType:(KKCallStackType)type 62 | { 63 | NSString *result; 64 | if (type == KKCallStackTypeAll) { 65 | thread_act_array_t threads; 66 | mach_msg_type_number_t thread_count = 0; 67 | if (KERN_SUCCESS != task_threads(mach_task_self(), &threads, &thread_count)) { 68 | return @"fail get all threads!"; 69 | } 70 | NSMutableString *resultStr = [NSMutableString string]; 71 | [resultStr appendFormat:@"当前线程数量:%d\n", thread_count]; 72 | for (int i = 0; i < thread_count; i++) { 73 | [resultStr appendFormat:@"%@", [self callStackWithThread_t:threads[i]]]; 74 | } 75 | result = resultStr.copy; 76 | } 77 | else if (type == KKCallStackTypeMain) 78 | { 79 | if ([NSThread isMainThread]) { 80 | result = [self callStackSymbolsWithCurrentThread]; 81 | } 82 | else 83 | { 84 | result = [self callStackWithThread_t:_main_thread]; 85 | } 86 | } 87 | else 88 | { 89 | result = [self callStackSymbolsWithCurrentThread]; 90 | } 91 | 92 | printf("\n⚠️⚠️⚠️⚠️⚠️⚠️👇堆栈👇⚠️⚠️⚠️⚠️⚠️⚠️\n"); 93 | NSLog(@"\n%@", result); 94 | printf("⚠️⚠️⚠️⚠️⚠️⚠️👆堆栈👆⚠️⚠️⚠️⚠️⚠️⚠️\n\n"); 95 | return result; 96 | } 97 | 98 | + (NSString *)callStackSymbolsWithCurrentThread 99 | { 100 | 101 | NSArray *arr = [NSThread callStackSymbols]; 102 | NSMutableString *strM = [NSMutableString stringWithFormat:@"\ncallStack of thread: %@\n", [NSThread isMainThread]?@"main thread":[NSThread currentThread].name]; 103 | for (NSString *symbol in arr) { 104 | [strM appendFormat:@"%@\n", symbol]; 105 | } 106 | return strM.copy; 107 | } 108 | 109 | + (NSString *)callStackWithThread_t:(thread_t)thread 110 | { 111 | _STRUCT_MCONTEXT machineContext; 112 | if (!getMachineContext(thread, &machineContext)) { 113 | return [NSString stringWithFormat:@"fail get thread(%u) state", thread]; 114 | } 115 | 116 | uintptr_t pc = machineContext.__ss.KK_INSTRUCTION_ADDRESS; 117 | uintptr_t fp = machineContext.__ss.KK_FRAME_POINTER; 118 | uintptr_t lr = machineContext.__ss.KK_LINKER_POINTER; 119 | 120 | uintptr_t pcArr[StackMaxDepth]; 121 | int i = 0; 122 | pcArr[i++] = pc; 123 | StackFrameFP_LR frame = {(void *)fp, lr}; 124 | vm_size_t len = sizeof(frame); 125 | while (frame.fp && i < StackMaxDepth) { 126 | pcArr[i++] = frame.lr; 127 | bool flag = readFPMemory(frame.fp, &frame, len); 128 | if (!flag || frame.fp==0 || frame.lr==0) { 129 | break; 130 | } 131 | } 132 | return generateSymbol(pcArr, i, thread); 133 | } 134 | 135 | NSString *generateSymbol(uintptr_t *pcArr, int arrLen, thread_t thread) 136 | { 137 | CallStackInfo *csInfo = (CallStackInfo *)malloc(sizeof(CallStackInfo)); 138 | if (csInfo == NULL) { 139 | return @"malloc fail"; 140 | } 141 | csInfo->length = 0; 142 | csInfo->allocLength = arrLen; 143 | csInfo->stacks = (FuncInfo *)malloc(sizeof(FuncInfo) * csInfo->allocLength); 144 | if (csInfo->stacks == NULL) { 145 | return @"malloc fail"; 146 | } 147 | callStackOfSymbol(pcArr, arrLen, csInfo); 148 | NSMutableString *strM = [NSMutableString stringWithFormat:@"\ncallStack of thread: %u\n", thread]; 149 | for (int j = 0; j < csInfo->length; j++) { 150 | [strM appendFormat:@"%@", formatFuncInfo(csInfo->stacks[j])]; 151 | } 152 | // NSLog(@"\n%@", strM); 153 | freeMemory(csInfo); 154 | return strM.copy; 155 | } 156 | 157 | NSString *formatFuncInfo(FuncInfo info) 158 | { 159 | if (info.symbol == NULL) { 160 | return @""; 161 | } 162 | char *lastPath = strrchr(info.machOName, '/'); 163 | NSString *fname = @""; 164 | if (lastPath == NULL) { 165 | fname = [NSString stringWithFormat:@"%-30s", info.machOName]; 166 | } 167 | else 168 | { 169 | fname = [NSString stringWithFormat:@"%-30s", lastPath+1]; 170 | } 171 | return [NSString stringWithFormat:@"%@ 0x%08" PRIxPTR " %s + %llu\n", fname, (uintptr_t)info.addr, info.symbol, info.offset]; 172 | } 173 | 174 | void freeMemory(CallStackInfo *csInfo) 175 | { 176 | if (csInfo->stacks) { 177 | free(csInfo->stacks); 178 | } 179 | if (csInfo) { 180 | free(csInfo); 181 | } 182 | } 183 | 184 | bool getMachineContext(thread_t thread, _STRUCT_MCONTEXT64 *machineContext) 185 | { 186 | mach_msg_type_number_t state_count = KK_THREAD_STATE_COUNT; 187 | return KERN_SUCCESS == thread_get_state(thread, KK_THREAD_STATE, (thread_state_t 188 | )&machineContext->__ss, &state_count); 189 | } 190 | 191 | // 读取fp开始,len(16)字节长度的内存。因为stp fp, lr... , fp占8字节,然后紧接着上面8字节是lr 192 | bool readFPMemory(const void *fp, const void *dst, const vm_size_t len) 193 | { 194 | vm_size_t bytesCopied = 0; 195 | kern_return_t kr = vm_read_overwrite(mach_task_self(), (vm_address_t)fp, len, (vm_address_t)dst, &bytesCopied); 196 | return KERN_SUCCESS == kr; 197 | } 198 | 199 | @end 200 | -------------------------------------------------------------------------------- /KKThreadMonitor/CallStack/KKCallStackSymbol.c: -------------------------------------------------------------------------------- 1 | // 2 | // KKCallStackSymbol.c 3 | // KKMagicHook 4 | // 5 | // Created by 吴凯凯 on 2020/4/9. 6 | // Copyright © 2020 吴凯凯. All rights reserved. 7 | // 8 | 9 | #include "KKCallStackSymbol.h" 10 | 11 | #import 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | typedef struct { 18 | const struct mach_header *header; 19 | const char *name; 20 | uintptr_t slide; 21 | } KKMachHeader; 22 | 23 | typedef struct { 24 | KKMachHeader *array; 25 | uint32_t allocLength; 26 | } KKMachHeaderArr; 27 | 28 | static KKMachHeaderArr *machHeaderArr = NULL; 29 | 30 | void getMachHeader() 31 | { 32 | machHeaderArr = (KKMachHeaderArr *)malloc(sizeof(KKMachHeaderArr)); 33 | machHeaderArr->allocLength = _dyld_image_count(); 34 | machHeaderArr->array = (KKMachHeader *)malloc(sizeof(KKMachHeader) * machHeaderArr->allocLength); 35 | for (uint32_t i = 0; i < machHeaderArr->allocLength; i++) { 36 | KKMachHeader *machHeader = &machHeaderArr->array[i]; 37 | machHeader->header = _dyld_get_image_header(i); 38 | machHeader->name = _dyld_get_image_name(i); 39 | machHeader->slide = _dyld_get_image_vmaddr_slide(i); 40 | } 41 | } 42 | 43 | bool pcIsInMach(uintptr_t slidePC, const struct mach_header *header) 44 | { 45 | uintptr_t cur = (uintptr_t)(((struct mach_header_64*)header) + 1); 46 | for (uint32_t i = 0; i < header->ncmds; i++) { 47 | struct load_command *command = (struct load_command *)cur; 48 | if (command->cmd == LC_SEGMENT_64) { 49 | struct segment_command_64 *segmentCommand = (struct segment_command_64 *)command; 50 | uintptr_t start = segmentCommand->vmaddr; 51 | uintptr_t end = segmentCommand->vmaddr + segmentCommand->vmsize; 52 | if (slidePC >= start && slidePC <= end) { 53 | return true; 54 | } 55 | } 56 | cur = cur + command->cmdsize; 57 | } 58 | return false; 59 | } 60 | 61 | KKMachHeader *getPCInMach(uintptr_t pc) 62 | { 63 | if (!machHeaderArr) { 64 | getMachHeader(); 65 | } 66 | for (uint32_t i = 0; i < machHeaderArr->allocLength; i++) { 67 | KKMachHeader *machHeader = &machHeaderArr->array[i]; 68 | if (pcIsInMach(pc-machHeader->slide, machHeader->header)) { 69 | return machHeader; 70 | } 71 | } 72 | return NULL; 73 | } 74 | 75 | 76 | 77 | void findPCSymbolInMach(uintptr_t pc, KKMachHeader *machHeader, CallStackInfo *csInfo) 78 | { 79 | if (!machHeader) { 80 | return; 81 | } 82 | 83 | struct segment_command_64 *seg_linkedit = NULL; 84 | struct symtab_command *sym_command = NULL; 85 | const struct mach_header *header = machHeader->header; 86 | uintptr_t cur = (uintptr_t)(((struct mach_header_64*)header) + 1); 87 | for (uint32_t i = 0; i < header->ncmds; i++) { 88 | struct load_command *command = (struct load_command *)cur; 89 | if (command->cmd == LC_SEGMENT_64) { 90 | struct segment_command_64 *segmentCommand = (struct segment_command_64 *)command; 91 | if (strcmp(segmentCommand->segname, SEG_LINKEDIT)==0) { 92 | seg_linkedit = segmentCommand; 93 | } 94 | } 95 | else if (command->cmd == LC_SYMTAB) 96 | { 97 | sym_command = (struct symtab_command*)command; 98 | } 99 | cur = cur + command->cmdsize; 100 | } 101 | if (!seg_linkedit || !sym_command) { 102 | return; 103 | } 104 | 105 | uintptr_t linkedit_base = (uintptr_t)machHeader->slide + seg_linkedit->vmaddr - seg_linkedit->fileoff; 106 | struct nlist_64 *symtab = (struct nlist_64 *)(linkedit_base + sym_command->symoff); 107 | const uintptr_t strtab = linkedit_base + sym_command->stroff; 108 | 109 | uintptr_t slidePC = pc - machHeader->slide; 110 | uint64_t offset = UINT64_MAX; 111 | int best = -1; 112 | for (uint32_t i = 0; i < sym_command->nsyms; i++) { 113 | uint64_t distance = slidePC - symtab[i].n_value; 114 | if (slidePC >= symtab[i].n_value && distance <= offset) { 115 | offset = distance; 116 | best = i; 117 | } 118 | } 119 | 120 | if (best >= 0) { 121 | FuncInfo *funcInfo = &csInfo->stacks[csInfo->length++]; 122 | funcInfo->machOName = machHeader->name; 123 | funcInfo->addr = symtab[best].n_value; 124 | funcInfo->offset = offset; 125 | funcInfo->symbol = (char *)(strtab + symtab[best].n_un.n_strx); 126 | if (*funcInfo->symbol == '_') 127 | { 128 | funcInfo->symbol++; 129 | } 130 | if (funcInfo->machOName == NULL) { 131 | funcInfo->machOName = ""; 132 | } 133 | } 134 | } 135 | 136 | void callStackOfSymbol(uintptr_t *pcArr, int arrLen, CallStackInfo *csInfo) 137 | { 138 | for (int i = 0; i < arrLen; i++) { 139 | KKMachHeader *machHeader = getPCInMach(pcArr[i]); 140 | if (machHeader) { 141 | findPCSymbolInMach(pcArr[i], machHeader, csInfo); 142 | } 143 | } 144 | } 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /KKThreadMonitor/CallStack/KKCallStackSymbol.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKCallStackSymbol.h 3 | // KKMagicHook 4 | // 5 | // Created by 吴凯凯 on 2020/4/9. 6 | // Copyright © 2020 吴凯凯. All rights reserved. 7 | // 8 | 9 | #ifndef KKCallStackSymbol_h 10 | #define KKCallStackSymbol_h 11 | 12 | #include 13 | #include 14 | 15 | typedef struct { 16 | uint64_t addr; 17 | uint64_t offset; 18 | const char *symbol; 19 | const char *machOName; 20 | } FuncInfo; 21 | 22 | typedef struct { 23 | FuncInfo *stacks; 24 | int allocLength; 25 | int length; 26 | }CallStackInfo; 27 | 28 | void callStackOfSymbol(uintptr_t *pcArr, int arrLen, CallStackInfo *csInfo); 29 | 30 | #endif /* KKCallStackSymbol_h */ 31 | -------------------------------------------------------------------------------- /KKThreadMonitor/KKThreadMonitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // KKThreadMonitor.h 3 | // KKMagicHook 4 | // 5 | // Created by 吴凯凯 on 2020/4/11. 6 | // Copyright © 2020 吴凯凯. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface KKThreadMonitor : NSObject 14 | 15 | + (void)startMonitor; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /KKThreadMonitor/KKThreadMonitor.m: -------------------------------------------------------------------------------- 1 | // 2 | // KKThreadMonitor.m 3 | // KKMagicHook 4 | // 5 | // Created by 吴凯凯 on 2020/4/11. 6 | // Copyright © 2020 吴凯凯. All rights reserved. 7 | // 8 | 9 | #import "KKThreadMonitor.h" 10 | #import "KKCallStack.h" 11 | #include 12 | 13 | #ifndef kk_dispatch_main_async_safe 14 | #define kk_dispatch_main_async_safe(block)\ 15 | if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\ 16 | block();\ 17 | } else {\ 18 | dispatch_async(dispatch_get_main_queue(), block);\ 19 | } 20 | #endif 21 | 22 | static pthread_introspection_hook_t old_pthread_introspection_hook_t = NULL; 23 | static int threadCount = 0; 24 | #define KK_THRESHOLD 40 25 | static const int threadIncreaseThreshold = 10; 26 | 27 | //线程数量超过40,就会弹窗警告,并且控制台打印所有线程的堆栈;之后阈值每增加5条(45、50、55...)同样警告+打印堆栈;如果线程数量再次少于40条,阈值恢复到40 28 | static int maxThreadCountThreshold = KK_THRESHOLD; 29 | static dispatch_semaphore_t global_semaphore; 30 | static int threadCountIncrease = 0; 31 | static bool isMonitor = false; 32 | 33 | @implementation KKThreadMonitor 34 | 35 | + (void)startMonitor 36 | { 37 | global_semaphore = dispatch_semaphore_create(1); 38 | dispatch_semaphore_wait(global_semaphore, DISPATCH_TIME_FOREVER); 39 | mach_msg_type_number_t count; 40 | thread_act_array_t threads; 41 | task_threads(mach_task_self(), &threads, &count); 42 | threadCount = count; //加解锁之间,保证线程的数量不变 43 | old_pthread_introspection_hook_t = pthread_introspection_hook_install(kk_pthread_introspection_hook_t); 44 | dispatch_semaphore_signal(global_semaphore); 45 | 46 | isMonitor = true; 47 | kk_dispatch_main_async_safe(^{ 48 | [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(clearThreadCountIncrease) userInfo:nil repeats:YES]; 49 | }); 50 | } 51 | + (void)clearThreadCountIncrease 52 | { 53 | threadCountIncrease = 0; 54 | } 55 | 56 | void kk_pthread_introspection_hook_t(unsigned int event, 57 | pthread_t thread, void *addr, size_t size) 58 | { 59 | if (old_pthread_introspection_hook_t) { 60 | old_pthread_introspection_hook_t(event, thread, addr, size); 61 | } 62 | if (event == PTHREAD_INTROSPECTION_THREAD_CREATE) { 63 | threadCount = threadCount + 1; 64 | if (isMonitor && (threadCount > maxThreadCountThreshold)) { 65 | maxThreadCountThreshold += 5; 66 | kk_Alert_Log_CallStack(false, 0); 67 | } 68 | threadCountIncrease = threadCountIncrease + 1; 69 | if (isMonitor && (threadCountIncrease > threadIncreaseThreshold)) { 70 | kk_Alert_Log_CallStack(true, threadCountIncrease); 71 | } 72 | } 73 | else if (event == PTHREAD_INTROSPECTION_THREAD_DESTROY){ 74 | threadCount = threadCount - 1; 75 | if (threadCount < KK_THRESHOLD) { 76 | maxThreadCountThreshold = KK_THRESHOLD; 77 | } 78 | if (threadCountIncrease > 0) { 79 | threadCountIncrease = threadCountIncrease - 1; 80 | } 81 | } 82 | } 83 | 84 | void kk_Alert_Log_CallStack(bool isIncreaseLog, int num) 85 | { 86 | dispatch_semaphore_wait(global_semaphore, DISPATCH_TIME_FOREVER); 87 | if (isIncreaseLog) { 88 | printf("\n🔥💥💥💥💥💥一秒钟开启 %d 条线程!💥💥💥💥💥🔥\n", num); 89 | } 90 | [KKCallStack callStackWithType:KKCallStackTypeAll]; 91 | dispatch_semaphore_signal(global_semaphore); 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KKThreadMonitor 2 | 轻量级线程监控工具,当线程数量过多或线程爆炸时候,就打印所有线程堆栈。 3 | 4 | # 效果 5 | 当线程爆炸或线程数量过多时候,控制台打印所有的线程堆栈~ 6 | ``` 7 | 🔥💥💥💥💥💥一秒钟开启 28 条线程!💥💥💥💥💥🔥 8 | 9 | 👇👇👇👇👇👇👇堆栈👇👇👇👇👇👇👇 10 | 2020-04-12 12:36:29.270755+0800 BaiduIphoneVideo[55732:6928996] 11 | 当前线程数量:43 12 | 13 | callStack of thread: 9219 14 | libsystem_kernel.dylib 0x18022cc38 semaphore_wait_trap + 8 15 | libdispatch.dylib 0x00003928 _dispatch_sema4_wait + 28 16 | BaiduIphoneVideo 0x101285be0 -[TDXXXX脱敏🤣处理(去掉真实类名)XXXXager sendRequestWithBody:withURL:flag:] + 440 17 | BaiduIphoneVideo 0x10128535c __29-[TDAXXXX脱敏🤣处理(去掉真实类名)XXXXnager sendMessage]_block_invoke + 804 18 | libdispatch.dylib 0x00001dfc _dispatch_call_block_and_release + 32 19 | ... //省略中间线程堆栈 20 | callStack of thread: 8707 21 | libsystem_kernel.dylib 0x18022cc38 semaphore_wait_trap + 8 22 | libdispatch.dylib 0x00003928 _dispatch_sema4_wait + 28 23 | BaiduIphoneVideo 0x1026d2cd4 +[BaiduMobStatDeviceInfo testDeviceId] + 56 24 | BaiduIphoneVideo 0x1026ca8a4 -[BaiduMobStatLogManager checkHeaderBeforeSendLog:] + 3384 25 | BaiduIphoneVideo 0x1026cefdc -[BaiduMobStatLogManager _syncSendAllLog] + 536 26 | BaiduIphoneVideo 0x1026c6398 __33-[BaiduMobStatController sendLog]_block_invoke + 56 27 | libdispatch.dylib 0x00001dfc _dispatch_call_block_and_release + 32 28 | ... //省略中间线程堆栈 29 | callStack of thread: 80387 30 | libsystem_kernel.dylib 0x18024ea60 __open_nocancel + 8 31 | libsystem_kernel.dylib 0x1802356e0 open$NOCANCEL + 20 32 | BaiduIphoneVideo 0x101dcd488 -[XXXX脱敏🤣处理(去掉真实类名)XXXX recursiveCalculateAtPath:isAbnormal:isOutdated:needCheckIgnorePath:] + 1360 33 | BaiduIphoneVideo 0x101dcd488 -[XXXX脱敏🤣处理(去掉真实类名)XXXX recursiveCalculateAtPath:isAbnormal:isOutdated:needCheckIgnorePath:] + 1360 34 | BaiduIphoneVideo 0x101dcd488 -[XXXX脱敏🤣处理(去掉真实类名)XXXX recursiveCalculateAtPath:isAbnormal:isOutdated:needCheckIgnorePath:] + 1360 35 | BaiduIphoneVideo 0x101dccd90 -[XXXX脱敏🤣处理(去掉真实类名)XXXX initWithOutdatedDays:abnormalFolderSize:abnormalFolderFileNumber:ignoreRelativePathes:checkSparseFile:sparseFileLeastDifferPercentage:sparseFileLeastDifferSize:visitors:] + 576 36 | BaiduIphoneVideo 0x101dcd274 +[XXXX脱敏🤣处理(去掉真实类名)XXXX folderSizeAtPath:] + 72 37 | BaiduIphoneVideo 0x101de3900 __56-[HMDToBCrashTracker(Environment) environmentInfoUpdate]_block_invoke_2 + 88 38 | libdispatch.dylib 0x00001dfc _dispatch_call_block_and_release + 32 39 | 40 | 👆👆👆👆👆👆👆堆栈👆👆👆👆👆👆👆 41 | ``` 42 | 43 | # 用法 44 | ``` 45 | //在main函数里,或者任何你想开始监控的地方调用startMonitor,就可以了 46 | //一般在main,或者application:didFinishLaunchingWithOptions:函数里。 47 | [KKThreadMonitor startMonitor]; 48 | 49 | //在KKThreadMonitor.m文件里,可根据需求修改这两个值 50 | #define KK_THRESHOLD 40 //表示线程数量超过40,就打印所有线程堆栈(根据自己项目来定!!!) 51 | static const int threadIncreaseThreshold = 10; //表示一秒钟新增加的线程数量(新建的线程数量 - 销毁的线程数量)超过10,就打印所有的线程堆栈 52 | ``` 53 | 54 | # 说明 55 | 打印线程堆栈,我目前是用自己另外一个库[KKCallStack](https://github.com/maniackk/KKCallStack)。 56 | 57 | # 原理 58 | [博客](https://juejin.im/post/5e92a113e51d4547134bdadb) 59 | --------------------------------------------------------------------------------