理解 Java 中的内存泄漏,我们首先要清楚 Java 中的内存区域分配问题和内存回收的问题本文将分为三大部分介绍这些内容。
Java 中的内存区域主要分为线程共享的和线程私有的两大区域:
介绍了 Java 的内存分配问题,通过一段代码来进行一下总结
- public static void main(String[] args) {
- Animal animal = new Animal();
- }
- publci class Animal{
- public static String address = "the earth"
- public Animal(){
- }
- }
在 main 方法中,我们 new Animal(), 首先,检查内存中是否加载了 Animal.class,如果没有加载,则会先将 Animal.class 加载到方法区中,同时在方法区开辟一块内存区域,用于存储 static 的 "the earth"(这里更能清晰的理解 static 这个关键字,就是 static 修饰的变量是属于类的,而不是属于类的某一个对象的),随后在 Java 堆中开辟一块内存区域,用于存储 new Animal()这个对象,在虚拟机栈中的 animal 会指向这个对象的地址。
Java 内存回收主要是指的是 Java 堆上的已经分配给对象的内存回收,判断 Java 堆上的内存是否被回收,主要是通过可达性分析算法:从一系列可以作为 GC Roots 的对象开始向下搜索,搜索走过的路径称为引用链,当 GC 回收时,一个对象没有通过引用链与任何 GC Roots 对象连接,则这个对象就可以被回收了。可作为 GC Roots 对象的有以下几种:
需要注意的是 GC Roots 并不是一直可以作为 GC Roots 的,eg:
- public void testGc() {
- Person person = new Person();
- }
根据 GC Roots 的定义,new Person() 这个对象被 person 所引用,person 在虚拟机栈中,所以 new Person() 这个对象是作为了 GC Roots 的,但是当这个 testGc() 方法执行完成,person 释放内存,new Person() 这个对象就没有其他的引用,就不再是 GC Roots。
内存泄漏需要和内存溢出区分开来,内存泄漏是指:部分内存我们不再需要了,但是虚拟机不能回收,泄漏过多就会造成内存溢出。就是部分对象我们已经用不上了,但是这些对象和 GC Roots 存在一定程度上的引用关系,导致不能被垃圾回收机制回收。
- public class InnerStactivity extends AppCompatActivity {
- private static Object ininerClass;@Override protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_inner_stactivity);
- }
- class InnerClass {
- }
- public void startInnerClass() {
- ininerClass = new InnerClass();
- }
- /**
- * 创建了InnerClass的静态实例引用
- * @param view
- */
- public void createInner(View view) {
- startInnerClass();
- }
- }
当 static Object ininerClass 指向了 newInnerClass() 这个对象时,这个对象就可以作为了 GC Roots, 同时非静态的内部类会持有外部类的引用,InnerStactivity 就会在 GC Roots 的引用链上,当我们需要离开这个 InnerStactivity,并且不再需要这个 InnerStactivity 时,这个 InnerStactivity 并不能被回收。
- public class HandlerActivity extends AppCompatActivity {
- Handler mHandler;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_handler);
- mHandler= new Handler(){
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- show();
- }
- };
- }
- public void sendMessage(View view) {
- mHandler.sendEmptyMessageDelayed(1,1000*30);
- finish();
- }
- public void show(){
- }
- }
可以看到我们这么写 Android studio 已经提示了警告,提示我们应该用 static 修饰 handler 对象,否则会造成内存的泄漏, 这是不容易犯的错误。
Handler 这么写之所以会出现内存泄漏是因为:Message 会持有一个对 Handler 的引用,当这个 Handler 是非静态内部类的时候,又会持有一个对外部类的引用(比如 Activity)。如果发送一条延时的 Message,由于这个 Message 会长期存在于队列中,就会导致 Handler 长期持有对 Activity 的引用,从而引起视图和资源泄漏。当你发送一条延时的 Mesaage,并且把这个 Message 保存在某些地方(例如静态结构中)备用,内存泄漏会变得更加严重。
我们现在都通过 static 修饰 Handler 类,并通过弱引用来和当前界面的 Activity 交互,并在 onDestory()中去除 Handler 的所有的消息来规避可能出现的内存泄漏。
- public class HandlerImproveActivity extends AppCompatActivity {
- Handler mHandler;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_handler);
- mHandler= new ImproveHandler(HandlerImproveActivity.this);
- }
- public void sendMessage(View view) {
- mHandler.sendEmptyMessageDelayed(1,1000*30);
- finish();
- }
- private static class ImproveHandler extends Handler{
- private WeakReference<HandlerImproveActivity> mActivity;
- public ImproveHandler(HandlerImproveActivity improveActivity) {
- this.mActivity = new WeakReference<HandlerImproveActivity>(improveActivity);
- }
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- if (mActivity != null && mActivity.get() != null) {
- mActivity.get().show();
- }
- }
- }
- public void show(){
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if(mHandler != null){
- mHandler.removeCallbacksAndMessages(null);
- }
- }
- }
- AlertDialog alertDialog = new AlertDialog.Builder(this).create();
- Toast.makeText(getApplicationContext(), "", Toast.LENGTH_SHORT).show();
Android Developer 中关于如何管理内存的链接 developer.android.com/topic/perfo…
来源: https://juejin.im/post/5a30d76651882503dc53b2c8