C语言的内存泄露
1 内存泄露的常见原因
1)指针的重新赋值
char *a = (char *)malloc(10)
char *b = (char *)malloc(10)
a = b
### 指针a和b都分配了10个字节的内存,指针a被b重新赋值,
导致之前分配给a的内存变成了孤立内存,因没有指向该位置的引用,所以无法被释放。
2)错误的内存释放
char *a = (char *)malloc(10)
free(a) ##错误
##正确
free(a->b)
free(a)
假设 此时a的中间第二个字节位置又指向新的内存地址b。若直接释放a指针,会导致b内存地址变成孤立的内存。
3)返回值不正确的处理
char *f() {
return (char *) malloc(10)
}
void f1() {
f()
### 此时f1 调用了f,但是并没有使用该内存地址,导致该内存地址变成孤立内存,无法释放。
}
2 避免内存泄露的常见方法。
1 确保没有访问空指针
2 每一个malloc都有对应的free
3 每当向指针写入值时,都要确保对可用字节数和所写入的字节数进行交叉核对
4 在对指针赋值前,一定要确保没有内存位置会变为孤立的
5 每当释放结构化的元素(而该元素又包含指向动态分配的内存位置的指针)时,都应先遍历子内存位置并从那里开始释放,然后再遍历回父节点。
6 始终正确处理返回动态分配的内存引用的函数返回值
3 定位内存泄露的位置
当存在内存泄露时,需要定位内存泄露的位置。
工具: Valgrind是一个GPL的软件,用于Linux(For x86, amd64 and ppc32)程序的内存调试和代码剖析。你可以在它的环境中运行你的程序来监视内存的使用情况,比如C 语言中的malloc和free或者 C++中的new和 delete。使用Valgrind的工具包,你可以自动的检测许多内存管理和线程的bug,避免花费太多的时间在bug寻找上,使得你的程序更加稳固
使用具体参考:https://blog.csdn.net/qq_40989769/article/details/130785913
java语言中的内存泄露:
Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。
静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。
栈区 :当方法被执行时,方法体内的局部变量(其中包括基础数据类型、对象的引用)都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
堆区 : 又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存,也就是对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。
1 内存泄露的常见原因。
内存泄露的判断方法:1)某个对象时可达的,2)对象时无用的。 当满足这两个条件后,java中的GC机制时无法回收该对象的。
根本原因:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏。
1)静态集合类引起的内存泄露。
像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}
若要释放Vector,需要将vecotr赋值为null。
2)单例模式
单例模式和静态变量的生命周期类似,当单例模式引用外部对象后,这个对象将不会被gc逻辑回收。
3 监听器
在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。
2 Android中常见的内存泄露原因
1 集合类泄露
集合类如果仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。如果这个集合类是全局性的变量 (比如类中的静态属性,全局性的 map 等即有静态引用或 final 一直指向它),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减。如上面的vector
2 单例模式造成的内存泄露
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
### 这里的context 参数由外部传入的参数决定,如果是Activity的context,
##当Activity退出时,因为单例模式引用了该activity,所以我们无法activity。导致内存泄露。
this.context = context.getApplicationContext() ## 这里强制获取应用的ApplicationContext,从而保证周期的同步。
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}
}
3) 匿名内部类
匿名内部类是一种特殊的内部类,没有显示的类名,通常需要某个接口或者抽象类的实例。如监听类。
Button button = findViewById(R.id.button)
button.setOnclickListener(new View.OnclickListener() {
public void onClick(View v) {
//点击事件
}
当匿名内部类持有外部类时,当外部类被销毁,此时 匿名内部类依然持有外部类的引用,此时外部无法被回收,造成内存泄露。
解决方案: 使用弱引用,即使匿名内部类持有对弱引用的引用,也不会阻止外部类的垃圾回收。
- 强引用是默认类型的引用,在强引用存在时,垃圾回收器不会回收它指向的对象。
– 弱引用是一种比强引用更加灵活的引用类型,在垃圾回收时,如果只有弱引用指向对象,那么无论内存是否充足,对象都会被回收。
public class MyActivity extends Activity {
private WeakReference<MyActivity> mActivityRef;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mActivityRef = new WeakReference<>(this);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MyActivity activity = mActivityRef.get();
if (activity != null) {
// 处理点击事件
}
}
});
}
}
4)handler 造成的内存泄露
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for 10 minutes.
//此时当SampleActivity 被销毁后,此时由于Handler持有 外部类的引用,导致SampleActivity 无法被释放。
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ }
}, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}
使用弱引用
public class SampleActivity extends Activity {
/**
* Instances of static inner classes do not hold an implicit
* reference to their outer class.
*/
private static class MyHandler extends Handler {
private final WeakReference<SampleActivity> mActivity;
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
/**
* Instances of anonymous classes do not hold an implicit
* reference to their outer class when they are "static".
*/
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for 10 minutes.
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}
- 尽量避免使用 static 成员变量
如果成员变量被声明为 static,那我们都知道其生命周期将与整个app进程生命周期一样。
这会导致一系列问题,如果你的app进程设计上是长驻内存的,那即使app切到后台,这部分内存也不会被释放。按照现在手机app内存管理机制,占内存较大的后台进程将优先回收,yi’wei如果此app做过进程互保保活,那会造成app在后台频繁重启。当手机安装了你参与开发的app以后一夜时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默
6)资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
参考:https://blog.csdn.net/tianyaleixiaowu/article/details/75783477
go语言中的内存泄露
内存泄露的原因:
1)暂时性内存泄露。
获取长字符串中的一段导致长字符串未释放
获取长slice中的一段导致长slice未释放
在长slice新建slice导致泄漏
string相比于切片少了一个容量的cap字段,可以把string当成一个只读的切片类型。获取长string或切片中的一段内容,由于新生成的对象和老的string或切片共用一个内存空间,
会导致老的string和切片资源暂时得不到释放,造成短暂的内存泄露。
2) goroutine内存泄露
goroutine 阻塞会导致内存泄露。
// 没有消费者,发送端的的goroutine会一直被阻塞
func channelNoProducter() {
ch := make(chan int)
go func() {
ch <- 1
fmt.Println(111)
}()
}
// 没有上游的发送者,会被阻塞。
func channelNoProducer() {
ch := make(chan int, 1)
go func() {
<-ch
fmt.Println(111)
}()
}
// 解决方案,使用超时方案。
func TimeoutCancelContext() {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Millisecond*800))
go func() {
// 具体的业务逻辑
// 取消超时
defer cancel()
}()
select {
//如果任务没有完成(即cancel函数没有执行),此时会发送 ctx.Done() ,防止阻塞
case <-ctx.Done():
fmt.Println("time out!!!")
return
}
}
//使用channel
func TimeoutCancelChannel() {
done := make(chan struct{}, 1)
go func() {
// 执行业务逻辑
done <- struct{}{}
}()
select {
case <-done:
fmt.Println("call successfully!!!")
return
case <-time.After(time.Duration(800 * time.Millisecond)):
fmt.Println("timeout!!!")
// 使用独立的协程处理超时,需求添加return退出协程,否则会导致当前协程被通知channel阻塞,进而导致内存泄露
return
}
}
GO中已经封装好了,直接就能使用_ "net/http/pprof",可以记录程序的运行信息,可以是CPU使用情况、内存使用情况、goroutine运行情况等,当需要性能调优或者定位Bug时候,这些记录的信息是相当重要。
参考:https://blog.csdn.net/weixin_42117918/article/details/121461139
总结
不同语言回收内存的方式不一样,导致内存泄露的处理方式也不一样。
C语言: C 没GC回收机制,只有程序员手动进行回收,产生内存泄露主要是在编写程序是不规范导致。其优点是程序更加灵活,运行效率高,但同时也对程序员有更高的要求。
Java语言: Java有GC回收机制。 但其语言语法复杂,涉及静态,匿名,单例,监听器,handler等语法,其内存泄露的常见也是最复杂的,其衍生了弱,软,强引用。特别时在Android开发中,由于场景复杂多变,内存泄露极其常见。
Go 语言:go语言也有回收机制。其主要时goroutine的泄露。go语言的回收机制进一步可以参考:https://xie.infoq.cn/article/f56b419e9de2e8ca3d44ee0ce