-
内存泄漏的解决方案
2020-07-07 18:24:24估计每个C/C++程序员都遭受过内存泄漏的困扰。本文提供一种通过wrap malloc查找memory leak的思路,使得你翻车的时候能够自救,而不至于车毁人亡。= 什么是内存泄漏? 内存泄漏就是动态申请的内存丢失引用,造成...第一部分:内存泄漏的说明和解决方案的介绍 (转载)
第二部分:实现模拟检查内存泄漏的样例代码 (原创)
内存泄漏的说明和解决方案的介绍
以下文章转载以:https://www.bcdaren.com/557359488538251265/blog_content.html
众所周知,C/C++执行效率高,但难以驾驭,开车一时爽,但稍不留神容易翻车。估计每个C/C++程序员都遭受过内存泄漏的困扰。本文提供一种通过wrap malloc查找memory leak的思路,使得你翻车的时候能够自救,而不至于车毁人亡。=
什么是内存泄漏?
内存泄漏就是动态申请的内存丢失引用,造成没有办法回收它(我知道杠jing要说进程退出前系统会统一回收),相当于在人身上轧个口子,伤口一直流血不止,关键这口子还不知道轧哪儿。
内存泄漏对于客户端应用可能不是什么大事,而对于长久运行的服务器程序则可能是致命的。
隐式释放
Java等傻瓜式编程语言会自动管理内存回收,你只管用,系统会通过引用计数技术跟踪动态分配的每块内存,在合适的时机自动释放掉,这种方式叫隐式释放,相当于车的自动挡。
显式释放
而C/C++需要显式的释放,也就是开发者需要确保malloc/free配对,确保每块申请的内存都恰当的释放掉,相当于车的手动挡。有很多手段可以避免内存泄漏,比如RAII、比如智能指针(大多基于引用计数)、比如内存池。C/C++程序员也是蛮拼的,一直在跟内存泄漏做殊死搏斗。
理论上,只要我们足够小心,在每次申请的时候,都牢记释放,那么这个世界就清净了。但现实往往没有这般美好,比如抛异常了,释放内存的语句执行不到,比如模块之间的指针传递,又或者某菜鸟程序员不小心埋了颗雷,所以,我们必须直面真实的世界,那就是我们会遭遇内存泄漏。
怎么查内存泄漏?
我们可以review代码,double check,结对编程,但从海量代码里找到隐藏的问题,这如同大海捞针,谈何容易啊?兄弟。
所以,我们需要借助工具,比如valgrind,但这些找内存泄漏的工具,往往对你使用动态内存的方式有某种期待,或者说约束,比如常驻内存的对象会被误报出来,然后真正有用的信息会被掩盖在误报的汪洋大海里,所以,很多时候,valgrind根本解决不了日常项目中的问题,并没什么卵用。
很多著名的开源项目,为了能用valgrind跑,都大张旗鼓的修改源代码,从而使得项目符合valgrind的要求,用vargrind跑过没有任何报警叫valgrind干净,这倒也不失为一个一劳永逸的办法。
既然这些个玩意儿都中看不中用,所以,求人不如求己,还得自力更生,多大点事儿。
operator new/delete重载和hook malloc/free
可以通过operator new/delete,operator new[]/delete[]重载,但这里有很细致的功夫,你需要全面了解,而不是贸然行动,建议看看Effective C++,对operator new系列操作符重载有专门的阐述。
你也可以hook malloc、free等c编程接口。
你还可以开启ptmalloc的调试功能,它有时候也能管点用。
什么是动态内存分配器?
动态内存分配器是介于kernel跟应用程序之间的一个函数库,linux glibc提供的动态内存分配器叫ptmalloc,因为抱了linux的大腿,故而是应用最广泛的动态内存分配器。
从kernel角度看,动态内存分配器属于应用程序层;而从应用程序的角度看,动态内存分配器属于系统层。到底属于哪一层,这取决于你的身份和角度。
应用程序可以通过mmap系统调用直接向系统申请动态内存,也可以通过动态内存分配器的malloc接口分配内存,而动态内存分配器会通过sbrk、mmap向系统分配内存,所以应用程序通过free释放的内存,并不一定会真正返还给系统,它也有可能被动态内存分配器缓存起来。
所以当你malloc/free配对得很好,但通过top命令去看进程的内存占用,还是很高,你不必感到惊讶。
google有自己的动态内存分配器tcmalloc,另外jemalloc也是著名的动态内存分配器,他们有不同的性能表现,也有不同的缓存和分配策略。你可以用它们替换linux系统glibc自带的ptmalloc。
new/delete跟malloc/free的关系
new是c++的用法,比如Foo *f = new Foo,其实它分为3步。
- 通过operator new()分配sizeof(Foo)的内存,最终通过malloc分配。
- 在新分配的内存上构建Foo对象。
- 返回新构建的对象地址。
new=分配内存+构造+返回,而delete则是等于析构+free。
所以搞定malloc、free就是从根本上搞定动态内存分配。
chunk
每次通过malloc返回的一块内存叫一个chunk,动态内存分配器是这样定义的,后面我们都这样称呼。
wrap malloc
gcc支持wrap,即通过传递-Wl,--wrap,malloc的方式,可以改变调用malloc的行为,把对malloc的调用链接到自定义的__wrap_malloc(size_t)函数,而我们可以在__wrap_malloc(size_t)函数的实现中通过__real_malloc(size_t)真正分配内存,而后我们可以做搞点小动作。
同样,我们可以wrap free。
malloc跟free是配对的,当然也有其他相关API,比如calloc、realloc、valloc,这些都是细节,根本上还是malloc和free,比如realloc就是malloc + free的组合。
怎么去定位内存泄漏呢?
我们会malloc各种不同size的chunk,也就是每种不同size的chunk会有不同数量,如果我们能够跟踪每种size的chunk数量,那就可以知道哪种size的chunk在泄漏。很简单,如果该size的chunk数量一直在增长,那它很可能泄漏。
光知道某种size的chunk泄漏了还不够,我们得知道是哪个调用路径上导致该size的chunk被分配,从而去检查是不是正确释放了。
怎么跟踪到每种size的chunk数量?
我们可以维护一个全局 unsigned int malloc_map[1024 * 1024]数组,该数组的下标就是chunk的size,malloc_map[size]的值就对应到该size的chunk分配量。
这等于维护了一个chunk size到chunk count的映射表,它足够快,而且可以覆盖到0 ~ 1M大小的chunk的范围,它已经足够大了,试想一次分配一兆的块已经很恐怖了,可以覆盖到大部分场景。
那大于1M的块怎么办呢?我们可以通过log的方式记录下来。
在__wrap_malloc里,++malloc_map[size]
在__wrap_free里,--malloc_map[size]
如此一来,我们便通过malloc_map记录了各size的chunk的分配量。
如何知道释放的chunk的size?
不对,free(void *p)只有一个参数,我如何知道释放的chunk的size呢?怎么办?
我们通过在__wrap_malloc(size_t)的时候,分配8+size的chunk,也就是额外分配8字节,用起始的8字节存储该chunk的size,然后返回的是(char*)chunk + 8,也就是偏移8个字节地址,返回给调用malloc的应用程序。
这样在free的时候,传入参数void* p,我们把p往前移动8个字节,解引用就能得到该chunk的大小,而该大小值就是之前在__wrap_malloc的时候设置的size。
好了,我们真正做到记录各size的chunk数量了,它就存在于malloc_map[1M]的数组中,假设64个字节的chunk一直在被分配而没有被正确回收,最终会表现在malloc_map[size]数值一直在增长,我们觉得该size的chunk很有可能泄漏,那怎么定位到是哪里调用过来的呢?
如何记录调用链?
我们可以维护一个toplist数组,该数组假设有10个元素,它保存的是chunk数最大的10种size,这个很容易做到,通过对malloc_map取top 10就行。
然后我们在__wrap_malloc(size_t)里,测试该size是不是toplist之一,如果是的话,那我们通过glibc的backtrace把调用堆栈dump到log文件里去。
注意:这里不能再分配内存,所以你只能使用backtrace,而不能使用backtrace_symbols,这样你只能得到调用堆栈的符号地址,而不是符号名。
如何把符号地址转换成符号名,也就是对应到代码行呢?答案是addr2line。
addr2line
addr2line工具可以做到,你可以追查到调用链,进而定位到内存泄漏的问题。
至此,恭喜你,你已经get到了整个核心思想。
当然,实际项目中,我们做的更多,我们不仅仅记录了toplist size,还记录了各size chunk的增量toplist,会记录大块的malloc/free,会wrap更多的API。
总结
通过wrap malloc/free + backtrace + addr2line,你就可以定位到内存泄漏了。
美好的时间过得太快,又是时候说byebye!
实现模拟检查内存泄漏的样例代码:
#ifndef __MALLOC_MAP_H__ #define __MALLOC_MAP_H__ #ifndef NULL #define NULL (void *)0 #endif #define TOPLIST_SIZE (10) struct __malloc_map { size_t dump_boundary; /**< 出现dump时的动态内存块数量边界 */ struct __malloc_node *ptr_list; /**< 维护动态分配的内存链表 */ struct __malloc_node *toplist[TOPLIST_SIZE]; /**< 维护最大内存块数量top的前10个 */ }; extern void *__wrap_malloc(size_t size); extern void *__wrap_calloc(size_t size); extern void *__wrap_realloc(void *mem_address, size_t newsize); extern void __wrap_free(void *ptr); extern void malloc_map_dump(void); extern void malloc_map_show_toplist(void); extern int malloc_map_set_dump_boundary(size_t dump_boundary); #define malloc(size) __wrap_malloc(size) #define calloc(size) __wrap_calloc(size) #define realloc(mem_address, newsize) __wrap_realloc(mem_address, newsize) #define free(size) __wrap_free(size) #endif /* __MALLOC_MAP_H__ */
#include <execinfo.h> #include <signal.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> struct __malloc_node { struct __malloc_node *ptr_next; size_t chunk_count; size_t chunk_size; }; #include "mallocmap.h" #define BT_BUF_SIZE (100) #define MALLOC_MAP_INIT \ { .ptr_list = NULL, .dump_boundary = 0, .toplist = {NULL} } static int sg_interrupted = 0; static struct __malloc_map sg_malloc_map = MALLOC_MAP_INIT; void *__real_malloc(size_t size); void __real_free(void *ptr); char *get_name_by_pid(pid_t pid, char *task_name) { FILE* fp = NULL; char proc_pid_path[1024] = {0}; char buf[1024] = {0}; sprintf(proc_pid_path, "/proc/%d/status", pid); fp = fopen(proc_pid_path, "r"); if (NULL == fp) { return ""; } if (NULL != fgets(buf, 1024 - 1, fp)) { sscanf(buf, "%*s %s", task_name); } fclose(fp); return task_name; } /* 若找到,则存放到ptr_cur指针中,返回ptr_cur的之前指针 */ static struct __malloc_node *malloc_node_find_chunk_size(struct __malloc_node *ptr_list, size_t chunk_size, struct __malloc_node **ptr_cur) { struct __malloc_node *ptr_pre = ptr_list; struct __malloc_node *ptr_tmp = ptr_list; while (NULL != ptr_tmp) { if (ptr_tmp->chunk_size == chunk_size) { (*ptr_cur) = ptr_tmp; break; } ptr_pre = ptr_tmp; ptr_tmp = ptr_tmp->ptr_next; } return ptr_pre; } static int malloc_node_insert(struct __malloc_node **pptr_list, size_t chunk_count, size_t chunk_size) { struct __malloc_node *ptr_node = NULL; ptr_node = (struct __malloc_node *)__real_malloc(sizeof(struct __malloc_node)); if (NULL == ptr_node) { return -1; } ptr_node->chunk_count = chunk_count; ptr_node->chunk_size = chunk_size; ptr_node->ptr_next = NULL; (*pptr_list) = ptr_node; return 0; } static void malloc_node_bubble_sort(struct __malloc_node *ptr_list) { struct __malloc_node *p = NULL; struct __malloc_node *q = NULL; struct __malloc_node tmp; for (p = ptr_list; p->ptr_next != NULL; p = p->ptr_next){ for (q = p; q->ptr_next != NULL; q = q->ptr_next){ if (q->chunk_count < q->ptr_next->chunk_count){ tmp.chunk_count = q->chunk_count; tmp.chunk_size = q->chunk_size; q->chunk_count = q->ptr_next->chunk_count; q->chunk_size = q->ptr_next->chunk_size; q->ptr_next->chunk_count = tmp.chunk_count; q->ptr_next->chunk_size = tmp.chunk_size; } } } } void malloc_map_dump(void) { int idx = 0; int nptrs = 0; char task_name[1024] = {0}; char execmd[BT_BUF_SIZE] = {0}; void *buffer[BT_BUF_SIZE]; /* buffer里面存放的是每层调用函数的返回地址 */ nptrs = backtrace(buffer, BT_BUF_SIZE); printf("******** malloc_map_dump (PID:%u) *********\n", (unsigned int)getpid()); for (idx = 0; idx < nptrs; idx++) { printf("backtrace(%d) addresses: %p\n", idx, buffer[idx]); snprintf(execmd, sizeof(execmd), "./addr2line -e %s %p -f", \ get_name_by_pid(getpid(), task_name), buffer[idx]); system(execmd); } printf("******** malloc_map_dump (end) *********\n"); exit(-1); return; } void malloc_map_dump_fd(int fd) { int nptrs = 0; void *buffer[BT_BUF_SIZE]; /* buffer里面存放的是每层调用函数的返回地址 */ nptrs = backtrace(buffer, BT_BUF_SIZE); if (nptrs > 0) { backtrace_symbols_fd(buffer, BT_BUF_SIZE, fd); } return; } static int malloc_map_del_list(struct __malloc_map *ptr_map, size_t chunk_size) { struct __malloc_node *ptr_cur = NULL; struct __malloc_node *ptr_pre = NULL; ptr_pre = malloc_node_find_chunk_size(ptr_map->ptr_list, chunk_size, &ptr_cur); if (NULL == ptr_cur || NULL == ptr_pre) { fprintf(stderr, "ptr_cur or ptr_pre is null, code=%d (%s)", errno, strerror(errno)); return -1; } if (ptr_cur->chunk_count > 0) { ptr_cur->chunk_count--; } if (0 == ptr_cur->chunk_count && NULL != ptr_pre) { if (ptr_pre == ptr_cur) { ptr_map->ptr_list = NULL; } else { ptr_pre->ptr_next = ptr_cur->ptr_next; } __real_free(ptr_cur); } return 0; } static int malloc_map_add_list(struct __malloc_map *ptr_map, size_t chunk_size) { struct __malloc_node *ptr_cur = NULL; struct __malloc_node *ptr_pre = NULL; if (NULL == ptr_map->ptr_list) { malloc_node_insert(&(ptr_map->ptr_list), 1, chunk_size); } else { ptr_pre = malloc_node_find_chunk_size(ptr_map->ptr_list, chunk_size, &ptr_cur); if (NULL != ptr_cur) { // 找到与chunk_size相同的指针 ptr_cur->chunk_count++; // 分配的chunk数量大于dump_boundary则dump出栈调用流程 if (ptr_cur->chunk_count > ptr_map->dump_boundary) { malloc_map_dump(); } } else if (NULL != ptr_pre) { // 没找到插入到链表尾部 malloc_node_insert(&(ptr_pre->ptr_next), 1, chunk_size); } } return 0; } static void malloc_map_update_toplist(struct __malloc_map *ptr_map) { int idx = 0; struct __malloc_node *p = NULL; malloc_node_bubble_sort(ptr_map->ptr_list); for (idx = 0, p = ptr_map->ptr_list; idx < TOPLIST_SIZE && NULL != p; idx++, p = p->ptr_next) { ptr_map->toplist[idx] = p; //printf("__wrap_flush_toplist idx=%u, chunk_size=%u\n", idx, p->chunk_size); } } void malloc_map_show_toplist(void) { int idx = 0; char task_name[1024] = {0}; struct __malloc_map *ptr_map = NULL; ptr_map = &sg_malloc_map; if (ptr_map->ptr_list) { system("clear"); printf("toplist(boundary:%d):\n", ptr_map->dump_boundary); printf("PID\tADDRESS\tSIZE\tCOUNT\tPNAME\n"); for (idx = 0; idx < TOPLIST_SIZE; idx++) { if (NULL != ptr_map->toplist[idx]) { printf("%u\t%p\t%u\t%u\t%s\n", (int)getpid(), \ ptr_map->toplist[idx] + sizeof(size_t), \ ptr_map->toplist[idx]->chunk_size - sizeof(size_t), \ ptr_map->toplist[idx]->chunk_count, get_name_by_pid(getpid(), task_name)); } } printf("\n"); } } int malloc_map_set_dump_boundary(size_t dump_boundary) { sg_malloc_map.dump_boundary = dump_boundary; return dump_boundary; } void *__wrap_malloc(size_t size) { size_t chunk_size = 0; void *ptr_chunk = NULL; chunk_size = size + sizeof(size_t); if (NULL == (ptr_chunk = __real_malloc(chunk_size))) { fprintf(stderr, "ptr_chunk is null, code=%d (%s)", errno, strerror(errno)); return NULL; } memcpy(ptr_chunk, &size, sizeof(size_t)); //printf("__wrap_malloc size=%d, (size_t *)ptr_chunk=%d\n", size, *(size_t *)ptr_chunk); malloc_map_add_list(&sg_malloc_map, chunk_size); malloc_map_update_toplist(&sg_malloc_map); return ptr_chunk + sizeof(size_t); } void *__wrap_calloc(size_t size) { void *ptr_data = NULL; if (NULL == (ptr_data = __wrap_malloc(size))) { return NULL; } memset(ptr_data, 0x00, size); return ptr_data; } void *__wrap_realloc(void *mem_address, size_t newsize) { size_t real_size = 0; void *ptr_data = NULL; real_size = *(size_t *)(mem_address - sizeof(size_t)); if (NULL == (ptr_data = __wrap_malloc(newsize))) { return NULL; } memcpy(ptr_data, mem_address, real_size); return ptr_data; } void __wrap_free(void *ptr) { size_t chunk_size = 0; void *ptr_chunk = NULL; if (NULL != ptr) { ptr_chunk = ptr - sizeof(size_t); chunk_size = (*(size_t *)(ptr_chunk)) + sizeof(size_t); __real_free(ptr_chunk); malloc_map_del_list(&sg_malloc_map, chunk_size); } } static void abnormal_andler(int signo) { printf("abnormal_andler signo:%d\n", signo); sg_interrupted = 1; malloc_map_dump(); exit(0); } // void test_func_xxx(void) { int idx = 0; char *ptr = NULL; for (idx = 0; idx < 20; idx++) { ptr = __wrap_malloc(1024 + idx); if (3 == idx) { __wrap_free(ptr); } if (1 == idx) { __wrap_free(ptr); } } } void test_func_yyy(void) { int idx = 0; char *ptr = NULL; for (idx = 0; idx < 20; idx++) { ptr = __wrap_malloc(1024 + idx); if (5 == idx) { __wrap_free(ptr); } if (1 == idx) { __wrap_free(ptr); } } } int testmain(void) { printf("wrap_malloc main\n"); malloc_map_set_dump_boundary(8); test_func_xxx(); test_func_yyy(); signal(SIGSEGV, abnormal_andler); signal(SIGABRT, abnormal_andler); while (!sg_interrupted) { malloc_map_show_toplist(); test_func_xxx(); __wrap_malloc(1024); usleep(1000*3000); } return 0; } //
CFLAGS := -g -Wall -D__GNU__ $(MYCFLAGS) -D_GNU_SOURCE -D__USE_XOPE -mfloat-abi=hard WRAPFUNC := -rdynamic -funwind-tables -ffunction-sections -Wl,--wrap=malloc -Wl,--wrap=free -Wl,--wrap=calloc -Wl,--wrap=realloc CFLAG_TARGET := $(CFLAGS) CFLAG_TARGET += -Wl,-rpath-link $(TARGET_LIB_DIR) CFLAGS += -I./include/ SOURCES := $(wildcard *.c) $(wildcard $(DIR_EXBOARD)/*.c $(DIR_DB)/*.c \ $(DIR_RS485)/*.c $(DIR_XYBASE)/*.c $(DIR_NET)/*.c $(DIR_READER)/*.c ) OBJS := $(patsubst %.c,%.o,$(SOURCES)) DEPS := $(patsubst %.o,%.d,$(OBJS)) MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS)) MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS)) \ $(patsubst %.d,%.cc,$(MISSING_DEPS))) CPPFLAGS += -MD TARGET := mallocmap all: $(TARGET) $(TARGET): $(OBJS) $(CC) $(CFLAG_TARGET) -o $(TARGET_SERVICE_DIR)/$(TARGET) $(OBJS) $(WRAPFUNC) $(LIB) cp $(TARGET_SERVICE_DIR)/$(TARGET) $(DEST_PATH)/$(TARGET)_$(LOGNAME) -rf $(OBJS): %.o: %.c $(DEP) $(CC) $(CFLAGS) $(WRAPFUNC) -c $< -o $(subst ../,,$@) clean: rm -rf $(subst ../,,$(OBJS)) rm -rf *.gcno cleanall: rm -rf $(subst ../,,$(OBJS)) rm -rf $(TARGET_SERVICE_DIR)/$(TARGET) rm -rf *.h~ rm -rf *.c~ rm -rf *.d
-
android内存泄漏的解决方案
2016-12-17 10:04:02您上次回复的这个问题 Queue 用单例来封装是对的,但是内存泄漏明显不是因为是不是单例原因造成的,是因为楼主写的 new Response.Listener() 这句使用了内部匿名类,因为他初始化的时候是在HeapTestActivity 里面... -
Android 常见内存泄漏的解决方案
2016-04-01 14:36:41在Android程序开发中,当一个对象已经不需要再使用了,本该被回收时,而另外一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致本该被回收的对象不能被回收而停留在堆内存中,内存泄漏就产生了。内存...在Android程序开发中,当一个对象已经不需要再使用了,本该被回收时,而另外一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致本该被回收的对象不能被回收而停留在堆内存中,内存泄漏就产生了。
内存泄漏有什么影响呢?它是造成应用程序OOM的主要原因之一。由于Android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时,就难免会导致应用所需要的内存超过这个系统分配的内存限额,这就造成了内存溢出而导致应用Crash。
一、单例造成的内存泄漏
Android的单例模式非常受开发者的喜爱,不过使用的不恰当的话也会造成内存泄漏。因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
如下这个典例:
public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context; } public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; } }
这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
1、传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样长 ;
2、传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。所以正确的单例应该修改为下面这种方式:
public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context.getApplicationContext(); } public static AppManager getInstance(Context context) { if (instance != null) { instance = new AppManager(context); } return instance; } }
这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏。
二、非静态内部类创建静态实例造成的内存泄漏
有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,会出现这种写法:
public class MainActivity extends AppCompatActivity { private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mManager == null){ mManager = new TestResource(); } //... } class TestResource { //... } }
这样就在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏,因为非静态内部类默认会持有外部类的引用,而又使用了该非静态内部类创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为:
将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext 。三、Handler造成的内存泄漏
Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏,如下示例:
public class MainActivity extends AppCompatActivity { private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { //... } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); loadData(); } private void loadData(){ //...request Message message = Message.obtain(); mHandler.sendMessage(message); } }
这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:
public class MainActivity extends AppCompatActivity { private MyHandler mHandler = new MyHandler(this); private TextView mTextView ; private static class MyHandler extends Handler { private WeakReference<Context> reference; public MyHandler(Context context) { reference = new WeakReference<>(context); } @Override public void handleMessage(Message msg) { MainActivity activity = (MainActivity) reference.get(); if(activity != null){ activity.mTextView.setText(""); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView)findViewById(R.id.textview); loadData(); } private void loadData() { //...request Message message = Message.obtain(); mHandler.sendMessage(message); } }
创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,更准确的做法如下:
public class MainActivity extends AppCompatActivity { private MyHandler mHandler = new MyHandler(this); private TextView mTextView ; private static class MyHandler extends Handler { private WeakReference<Context> reference; public MyHandler(Context context) { reference = new WeakReference<>(context); } @Override public void handleMessage(Message msg) { MainActivity activity = (MainActivity) reference.get(); if(activity != null){ activity.mTextView.setText(""); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView)findViewById(R.id.textview); loadData(); } private void loadData() { //...request Message message = Message.obtain(); mHandler.sendMessage(message); } @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); } }
使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。
四、线程造成的内存泄漏
对于线程造成的内存泄漏,也是平时比较常见的,如下这两个示例可能每个人都这样写过//——————test1 new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { SystemClock.sleep(10000); return null; } }.execute(); //——————test2 new Thread(new Runnable() { @Override public void run() { SystemClock.sleep(10000); } }).start();
上面的异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。正确的做法还是使用静态内部类的方式,如下:
static class MyAsyncTask extends AsyncTask<Void, Void, Void> { private WeakReference<Context> weakReference; public MyAsyncTask(Context context) { weakReference = new WeakReference<>(context); } @Override protected Void doInBackground(Void... params) { SystemClock.sleep(10000); return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); MainActivity activity = (MainActivity) weakReference.get(); if (activity != null) { //... } } } static class MyRunnable implements Runnable{ @Override public void run() { SystemClock.sleep(10000); } } //—————— new Thread(new MyRunnable()).start(); new MyAsyncTask(this).execute();
这样就避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源。
五、资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。 -
内存泄漏的解决方案(转载)
2018-06-22 11:50:11内存泄漏的产生原因:JVM内存过小;程序不严谨,产生了过多的垃圾;程序的体现:内存中加载的数据量过于庞大,如一次从数据库中取出过多的数据。集合类中有对对象的引用,使用完后未清空,使得JVM不能回收。代码中...内存泄漏的产生原因:
- JVM内存过小;
- 程序不严谨,产生了过多的垃圾;
程序的体现:
- 内存中加载的数据量过于庞大,如一次从数据库中取出过多的数据。
- 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收。
- 代码中存在死循环或循环产生过多重复的对象实体。
- 使用第三方软件中的BUG。
- 启动参数内存值设定的过小。
错误的提示:
解决方法:
1)增加JVM的内存大小
对于Tomcat容器,找到Tomcat在电脑中的安装目录,进入这个目录,然后进入bin目录中,在windows环境下找到bin目录中的catalina.bat,在linux环境下找到catalina.sh。
编辑catalina.bat文件,找到JAVA_OPTS(具体来说是set“JAVA_OPTS=%JAVA_OPTS% %LOGGING_MANAGER%”)这个选项的位置,这个参数是Java启动的时候,需要的启动参数。
也可以在操作系统的环境变量中对JAVA_OPTS进行设置,因为Tomcat在启动的时候,也会读取操作系统中环境变量的值,进行加载。
如果是修改了操作系统的环境变量,需要重启机器,再重启Tomcat,如果修改的是Tomcat的配置文件,需要将配置文件保存,然后重启Tomcat,设置就能生效了。
2)优化程序,释放垃圾
主要思路就是避免程序体现上出现的情况。避免死循环,防止一次载入太多的数据,提高程序健壮性及时释放。因此,从根本上解决Java内存溢出的唯一方法就是修改程序,及时地释放没用的对象,释放内存空间。
内存泄漏
指程序在申请内存之后,无法释放已申请的内存空间,一次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光。
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点。
1)首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;
2)其次,这些对象是无用的,即程序以后不会再使用这些对象。
如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC回收,然而它却占用内存。
关于内存泄漏的处理页,就是提高程序的健壮性,因为内存泄漏是纯代码层面的问题。
内存溢出和内存泄漏的联系
内存泄漏最终会导致内存溢出。
相同点:都会导致应用程序运行出现问题,性能下降或者挂起。
不同点:
1)内存泄漏时导致内存溢出的原因之一,内存泄漏积累起来将导致内存溢出。
2)内存泄漏可以通过完善代码来避免,内存溢出可以通过调整配置来减少发生频率,但无法彻底避免。
一个Java内存泄漏的排查案例
某个业务系统在一段时间突然变慢,怀疑是因为出现内存泄漏问题导致的,于是踏上了排查之路。
确定频繁Full GC现象
首先通过“虚拟机经常状况工具:jps”找出正在运行的虚拟机进程,最主要是找出这个进程在本地虚拟机的唯一ID(LVMID,Local Virtual Machine Identifier),因为在后面的排查过程中都是需要这个LVMID来确定要监控的是哪一个虚拟机进程。
同时,对于本地虚拟机进程来说,LVMID与操作系统的进程ID(PID,Processor Identifier)是一致的,使用windows的任务管理器或Unix的ps命令也可以查询到虚拟机进程的LVMID。
jps命令格式为:
jps [ options ] [ hostid ]
使用命令如下:使用jps:
jps -l
使用ps:
ps aux | grep tomat
找到你需要监控的ID(假设为20954),再利用“虚拟机统计信息监视工具:jstat”监视虚拟机各种运行状态信息。jstat命令格式为:
jstat [ option vmid [interval[s|ms] [count]] ]
使用命令如下:jstat -gcutil 20954 1000
意思是每1000毫秒查询一次,一直查。gcutil的意思是已使用空间站总空间的百分比。
jstat执行结果
查询结果表明:这台服务器的新生代Eden区(E,表示Eden)使用了28.30%(最后)的空间,两个Survivor区(S0、S1,表示Survivor0、Survivor1)分别是0和8.93%,老年代(O,表示Old)使用了87.33%。程序运行以来共发生Minor GC(YGC,表示Young GC)101次,总耗时1.961秒,发生Full GC(FGC,表示Full GC)7次,Full GC总耗时3.022秒,总的耗时(GCT,表示GC Time)为4.983秒。
找出导致频繁Full GC的原因
分析方法通常有两种:
1)把堆dump下来再用MAT等工具进行分析,但dump堆要花较长的时间,并且文件巨大,再从服务器上拖回本地导入工具,这个过程有些折腾,不到万不得已最好别这么干。
2)更轻量级的在线分析,使用“Java内存影像工具:jmap”生成堆转储快照(一般称为headdump或dump文件)。
jmap命令格式:jmap [ option ] vmid
使用命令如下:jmap -histo:live 20954
存活对象
按照一位IT友的说法,数据不正常,十有八九就是泄露的。在我这个图上对象还是挺正常的。
我在网上找了一位博友的不正常数据,如下:
可以看出HashTable中的元素有5000多万,占用内存大约1.5G的样子。这肯定不正常。
定位到代码
定位带代码,有很多种方法,比如前面提到的通过MAT查看Histogram即可找出是哪块代码。——我以前是使用这个方法。也可以使用BTrace。
-
Android 内存泄漏的解决方案
2017-08-24 13:45:16请见谅 我直接贴的链接 http://blog.csdn.net/zhuanglonghai/article/details/38233069 写的不错之前写在开发系统应用的时候发现很多的内存泄漏。下边是我碰到的加网上看到总结的一些内存溢出问题。
1. handler 非静态匿名内部类
2. 非正当使用static
3. 集合中的对象善于清理
4. 单例模式
5. WebView
6. 一些资源类handler
源码中提到
/*
* Set this flag to true to detect anonymous, local or member classes
* that extend this Handler class and that are not static. These kind
* of classes can potentially create leaks.
*/
意思是说 如果在使用Handler 不是static 修饰的话可能会引起内存泄漏
因为在使用handler 的时候 其本身是一个内部类,在java 中非静态的内部类 会持有外部类的对象,静态的内部类不会引用外部类的对象
如果外部类是Activity,则会引起Activity泄露 。当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。解决方案
static class MyHandler extends Handler{ WeakReference<MainActivity> weakReference ; public MyHandler(MainActivity mainActivity) { weakReference = new WeakReference<>(mainActivity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); MainActivity mainActivity = weakReference.get(); mainActivity.toDo(); // 此方法为 你的Acitivity 中的方法 } } MyHandler myHandler = new MyHandler(this);
当然还需要做一下处理
当Activity finish后 handler对象还是在Message中排队。 在Activity onStop或者onDestroy的时候,取消掉该Handler对象的Message和Runnable。
在Activity remove 相关Runnable 或者相关message 即可内部类的处理也是一样的(一般不含handler remove 操作)
非正当使用static
当我们的成员变量是static的时候,那么它的生命周期将和整个app的生命周期一致。这必然会导致一系列问题,如果你的app进程设计上是长驻内存的,那即使app切到后台,这部分内存也不会被释放。按照现在手机app内存管理机制,占内存较大的后台进程将优先回收,因为如果此app做过进程互保保活,那会造成app在后台频繁重启。当手机安装了你参与开发的app以后一夜时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默。这里修复的方法是:
不要在类初始时初始化静态成员。可以考虑lazy初始化(延迟加载)。架构设计上要思考是否真的有必要这样做,尽量避免。如果架构需要这么设计,那么此对象的生命周期你有责任管理起来
集合类的处理
我们通常会把一些对象的引用加入到集合容器(比如ArrayList)中,当我们不再需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。所以在退出程序之前,将集合里面的东西clear,然后置为null,再退出程序.这个问题曾出现在一个锁屏的问题(属于系统进程,然后不停往里塞 用完没有及时会后。这个不合理)
解决方案 用完的对象注意回收, 不用集合了remove 所有对象 并将集合置null
单例模式
如果单利模式的对象引用了Activity 恭喜你 准备被你老大批吧, 因为单例是一个静态对象,当你调用的时候进行了初始化它的销毁时间是App 销毁时间, 也就是说它一旦初始化 进程活着它就活着。然后它引用了一个Activity Activity 调用了onDestroy 方法销毁自身的时候内存被单例引用 虽然调用onDestroy ()(这里应该说是执行, 因为Activity 不会主动调用它,finish()方法会最终调它)但是堆中内存并没有被回收。导致内存泄漏,所以这个单例模式要特别小心
解决方案 不要将activity对象关联单例, 引申不要把非static 对象的与static 关联
WebView
Activity 和 WebView 生命周期不一致,当WebView 中加载没有完全(线程没有执行完全) Acitvity 销毁,恭喜又发生内存泄露了,webview 此时还活着 webview 依赖Acitvity 生存,可以说关联着Acitvity , 造成Acitvity 内存不能被回收。
解决方案?给WebView 开独立进程。用完杀当前进程即可。
资源类的回收
bitmap io 等 该主动回收的主动回收 该close 的close -
关于每次关闭tomcat时都报内存泄漏的解决方案
2020-07-28 09:46:43每次关闭tomcat时都报内存泄漏...这个是我在网上找的解决方案 https://blog.csdn.net/qq_40860565/article/details/105547194 但这个只是解决一部分问题,但c3p0数据源的内存泄漏为解决。 http://www.voidcn.com/articl -
Android Handler导致内存泄漏的解决方案
2016-12-30 11:39:56最近一段时间,一直在优化项目,遇到了Handler导致的内存泄漏的问题,在解决之余,也发表一下自己的感想,希望对别人有点帮助。在优化项目的时候,我发现很多人都喜欢用匿名内部类的方式创建一个Handler(当年我也... -
RAC潜在的内存泄漏的解决方案
2017-12-13 12:51:581.RACObserve 2.RACSubject 对subject进行了map操作 ...###热信号和冷信号相关的文章 http://tech.meituan.com/talk-about-reactivecocoas-cold-signal-and-hot-s... -
以线上实例来看 内存泄漏的解决方案
2020-04-17 09:56:33项目上线了一个接口,灰度一台机器运行一断时间后开始报OOM异常,当天就是上线的deadline,刺激~ -
关于yii2框架中内存泄漏的解决方案
2017-09-07 20:53:44结果每次都会报出内存溢出的错误,类似于这样: 第 475 次,消耗内存 312.68MB第 476 次,消耗内存 313.22MB第 477 次,消耗内存 313.79MB。还是报错。内存溢出。Allowed memory size of 402653184 bytes exhausted... -
ajax内存泄漏的解决方案以及长连接和短连接的详解
2016-08-04 14:50:31后来功能没有问题,性能测试时发现IE开久了页面经常卡死,内存只增不减。 每次jquery的Ajax请求都会创建一个xmlHttprequest对象,理论上讲,长连接的请 求是一个无限递归,请求数量是非常大的,但是由于每 -
使用IFRAME引入页面导致IE内存泄漏的解决方案
2011-09-15 23:06:231、资源回收 function gbg(){ if(contentIframe){ contentIframe.document.write(""); contentIframe.document.clear(); //document.body.re -
php内存泄漏的解决方案(在读取大数量时遇到的问题 sphinx查询)
2013-03-29 16:41:39前言:持续我一贯的标题党作风,说说例子解决方案,没有深入探讨。 情景:线上图片服务压缩的图片品质(100),缩略图品质(100)占用了很多空间,导致后来又55个文件了(占用空间160G)才发现这个问题。现在... -
java和Android常见的内存泄漏与解决方案
2019-11-18 14:29:01文章目录概念内存泄漏(Memory Leak)内存溢出(Out Of Memory—OOM)Java虚拟机的GC(垃圾回收)策略可达性分析算法引用记数算法内存泄漏的原因内存泄漏的解决方案Java中的内存泄漏集合类引起的内存泄漏static... -
IOS 常见内存泄漏以及解决方案
2020-08-30 15:56:52主要介绍了IOS 常见内存泄漏以及解决方案的相关资料,需要的朋友可以参考下 -
JS常见内存泄漏及解决方案解析
2020-10-15 06:35:20主要介绍了JS常见内存泄漏及解决方案解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
Python内存泄漏和内存溢出的解决方案
2020-09-27 10:27:18主要介绍了Python内存泄漏和内存溢出的解决方案,帮助大家维护后台进程,感兴趣的朋友可以了解下
-
华南理工大学《电机学》期末考试试卷(含答案).pdf
-
【Python-随到随学】 FLask第一周
-
湖南大学《电子技术》作业及其解答.pdf
-
Oreo易支付开源版.zip
-
Cypress web 自动化的Cookie操作
-
江西财经大学《概率论与数理统计》期中试卷(含答案).pdf
-
朱老师鸿蒙系列课程第1期-2鸿蒙系统Harmonyos源码架构分析
-
vue文档介绍
-
华为1+X——网络系统建设与运维(中级)
-
LeetCode 304. 二维区域和检索 - 矩阵不可变
-
湖南大学大一《普通化学》试题及答案.pdf
-
暨南大学《护理综合》2010--2015历届考研习题.pdf
-
Vue中v-for遍历数组和对象
-
Amoeba 实现 MySQL 高可用、负载均衡和读写分离
-
MySQL 性能优化(思路拓展及实操)
-
Java 容器知识及面试题?了解下……
-
linux基础入门和项目实战部署系列课程
-
MySQL 高可用工具 heartbeat 实战部署详解
-
access应用的3个开发实例
-
3-4 变量