Java内存回收方式

姓名:刘亚宁 学号:17101223434

转载自:https://juejin.im/entry/5a31f567f265da4325295002?from=singlemessage,有删减。

【嵌牛导读】:本文主要介绍Java内存回收方式以及搞懂Java内存泄漏。

【嵌牛鼻子】:leakcanary检测、内存泄露的几种方式、内部类、Handler引起内存泄漏

【嵌牛提问】:Java内存泄漏的情况有哪些?内部类的情况下如何判断内存泄漏?如何处理内存泄露的方式呢?

【嵌牛正文】:

Java判断对象是否可以回收使用的而是可达性分析算法。

在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。

在Java语言里,可作为GC Roots对象的包括如下几种:

a.虚拟机栈(栈桢中的本地变量表)中的引用的对象
b.方法区中的类静态属性引用的对象
c.方法区中的常量引用的对象
d.本地方法栈中JNI的引用的对象

摘自《深入理解Java虚拟机》

使用leakcanary检测泄漏

关于LeakCanary使用参考以下文章:

  1. LeakCanary: 让内存泄露无所遁形
  2. LeakCanary 中文使用说明

LeakCanary的内存泄露提示一般会包含三个部分:
第一部分(LeakSingle类的sInstance变量)
引用第二部分(LeakSingle类的mContext变量),
导致第三部分(MainActivity类的实例instance)泄露.

leakcanary使用注意

即使是空的Activity,如果检测泄露时候遇到了如下这样的泄露,注意,把refWatcher.watct()放在onDestroy里面即可解决,或者忽略这样的提示。
由于文章已写很多,下面的就不再修改,忽略这种错误即可。

  • com.less.demo.TestActivity has leaked:

  • GC ROOT static android.app.ActivityThread.sCurrentActivityThread

  • references android.app.ActivityThread.mActivities

  • references android.util.ArrayMap.mArray

  • references array java.lang.Object[].[1]

  • references android.app.ActivityThread$ActivityClientRecord.activity

  • leaks com.less.demo.TestActivity instance

protected void onDestroy() {

super.onDestroy();

RefWatcher refWatcher = App.getRefWatcher(this);

refWatcher.watch(this);

}

leakcanary和代码示例说明内存泄露

案例一(静态成员引起的内存泄露)

public class App extends Application {

private RefWatcher refWatcher;

@Override

public void onCreate() {

    super.onCreate();

    refWatcher = LeakCanary.install(this);

}

public static RefWatcher getRefWatcher(Context context) {

    App application = (App) context.getApplicationContext();

    return application.refWatcher;

}

}

测试内部类持有外部类引用,内部类是静态的(GC-ROOT,将一直连着这个外部类实例),静态的会和Application一个生命周期,这会导致一直持有外部类引用(内部类隐含了一个成员变量$0), 即使外部类制空= null,也无法释放。

OutterClass

public class OutterClass {

private String name;

class Inner{

    public void list(){

        System.out.println("outter name is " + name);

    }

}

}

TestActivity

public class TestActivity extends Activity {

// 静态的内部类实例

private static OutterClass.Inner innerClass;

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_test);

    OutterClass outterClass = new OutterClass();

    innerClass = outterClass.new Inner();

    RefWatcher refWatcher = App.getRefWatcher(this);

    refWatcher.watch(outterClass);// 监控的对象

    outterClass = null;

}

案例二(单例模式引起的内存泄露)

DownloadManager

public class DownloadManager {

private static DownloadManager instance;

private Task task ;

public static DownloadManager getInstance(){

    if (instance == null) {

        instance = new DownloadManager();

    }

    return instance;

}

public Task newTask(){

    this.task = new Task();

    return task;

}

}

Task

public class Task {

private Call call;

public Call newCall(){

    this.call = new Call();

    return call;

}

}

Call

public class Call {

public void execute(){

    System.out.println("=========> execute call");

}

}

TestActivity

public class TestActivity extends Activity {

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_test);

    RefWatcher refWatcher = App.getRefWatcher(this);

    Task task = DownloadManager.getInstance().newTask();

    Call call = task.newCall();

    call.execute();

    refWatcher.watch(call);// 监控的对象

    call = null; // 无法回收,DownloadManager是静态单例,引用task,task引用了call,即使call置为空,也无法回收,切断GC_ROOT 联系即可避免内存泄露,即置task为空。

}

}

eo.png

部分日志打印如下:当前的GC_ROOT是DownloadManager的instance实例。

In com.leakcanary.demo:1.0:1.

  • com.less.demo.Call has leaked:

  • GC ROOT static com.less.demo.DownloadManager.instance

  • references com.less.demo.DownloadManager.task

  • references com.less.demo.Task.call

  • leaks com.less.demo.Call instance


关于上面两种方式导致的内存泄露问题,这里再举两个案例说明以加强理解。

案例三(静态变量导致的内存泄露)

public class TestActivity extends Activity {

private static Context sContext;

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_test);

    sContext = this;

    RefWatcher refWatcher = App.getRefWatcher(this);

    refWatcher.watch(this);// 监控的对象

}
1281543-b60d0c5a7529d8c1.png

打印日志如下:

In com.leakcanary.demo:1.0:1.

com.less.demo.TestActivity has leaked:

GC ROOT static com.less.demo.TestActivity.sContext

leaks com.less.demo.TestActivity instance

从这段日志可以分析出:声明static后,sContext的生命周期将和Application一样长,Activity即使退出到桌面,Application依然存在->sContext依然存在,GC此时想回收Activity却发现Activity仍然被sContext(GC-ROOT连接着),导致死活回收不了,内存泄露。

上面的代码改造一下,如下。

public class TestActivity extends Activity {

private static View sView;

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_test);

    sView = new View(this);

    RefWatcher refWatcher = App.getRefWatcher(this);

    refWatcher.watch(this);

}

}

dd.png

日志如下

In com.leakcanary.demo:1.0:1.

com.less.demo.TestActivity has leaked:

GC ROOT static com.less.demo.TestActivity.sView

references android.view.View.mContext

leaks com.less.demo.TestActivity instance

案例四(单例模式导致的内存泄露)
DownloadManager

public class DownloadManager {

private static DownloadManager instance;

private List<DownloadListener> mListeners = new ArrayList<>();

public interface DownloadListener {

    void done();

}

public static DownloadManager getInstance(){

    if (instance == null) {

        instance = new DownloadManager();

    }

    return instance;

}

public void register(DownloadListener downloadListener){

    if (!mListeners.contains(downloadListener)) {

        mListeners.add(downloadListener);

    }

}

public void unregister(DownloadListener downloadListener){

    if (mListeners.contains(downloadListener)) {

        mListeners.remove(downloadListener);

    }

}

}

TestActivity

public class TestActivity extends Activity implements DownloadManager.DownloadListener{

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_test);

    DownloadManager.getInstance().register(this);

    RefWatcher refWatcher = App.getRefWatcher(this);

    refWatcher.watch(this);

}

@Override

protected void onDestroy() {

    super.onDestroy();

    // 忘记 unregister

    // DownloadManager.getInstance().unregister(this);

}

@Override

public void done() {

    System.out.println("done!");

}

}

In com.leakcanary.demo:1.0:1.

  • com.less.demo.TestActivity has leaked:

  • GC ROOT static com.less.demo.DownloadManager.instance

  • references com.less.demo.DownloadManager.mListeners

  • references java.util.ArrayList.array

  • references array java.lang.Object[].[0]

  • leaks com.less.demo.TestActivity instance

错误写法一定导致内存泄露吗?

答案是:否定的。
如下案例,有限时间内是可以挽救内存泄露发生的,所以控制好生命周期,其他情况:如单例对象(静态变量)的生命周期比其持有的sContext
的生命周期更长时 ->内存泄露,更短时->躲过内存泄露。

public class TestActivity extends Activity {

private static Context sContext;

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_test);

    sContext = this;

    new Handler().postDelayed(new Runnable() {

        @Override

        public void run() {

            sContext = null; 

        }

    },1000);// 分别测试1s和12s,前者不会内存泄露,后者一定泄露。所以如果赶在GC之前切断GC_ROOT是可以避免内存泄露的。

}

@Override

protected void onDestroy() {

    super.onDestroy();

    RefWatcher refWatcher = App.getRefWatcher(this);

    refWatcher.watch(this);

}

}

Handler 引起的内存泄露详细分析

handler导致的内存泄露可能我们大多数都犯过.
注意以下代码中的注释,非静态内部类虽然持有外部类引用,但是持有并不代表一定泄露,而是看gc时谁的命长。经过测试

情况(1)始终没有内存泄露。

为什么会这样, 很早阅读Handler源码时候记得这几个货都是互相引用来引用去的,Message有个target字段, message.target = handler;
handler.post(message);又把这个message推入了MessageQueue中,而MessageQueue是在一个Looper线程中不断轮询处理消息,而有时候message还是个老不死,能够重复利用。如果当Activity退出时候,还有消息未处理或正在处理,由于message引用handler,handler又引用Activity,此时将引发内存泄露。

public class TestActivity extends Activity {

private Handler handler = new Handler() {

    @Override

    public void handleMessage(Message msg) {

        super.handleMessage(msg);

        System.out.println("===== handle message ====");

    }

};

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_test);

    // (1) 不会导致内存泄露

    handler.sendEmptyMessageDelayed(0x123,0);

    // (2) 会导致内存泄露,非静态内部类(包括匿名内部类,比如这个 Handler 匿名内部类)会引用外部类对象 this(比如 Activity)

    // 当它使用了 postDelayed 的时候,如果 Activity 已经 finish 了,而这个 handler 仍然引用着这个 Activity 就会致使内存泄漏

    // 因为这个 handler 会在一段时间内继续被 main Looper 持有,导致引用仍然存在.

    handler.sendEmptyMessageDelayed(0x123, 12000);

}

@Override

protected void onDestroy() {

    super.onDestroy();

    RefWatcher refWatcher = App.getRefWatcher(this);

    refWatcher.watch(this);

}

}

com.less.demo.TestActivity has leaked:

  • GC ROOT android.view.inputmethod.InputMethodManager$ControlledInputConnectionWrapper.mH

  • references com.android.internal.view.IInputConnectionWrapper$MyHandler.mQueue

  • references android.os.MessageQueue.mMessages

  • references android.os.Message.target , matching exclusion field android.os.Message#target

  • references com.less.demo.TestActivity$1.this$0 (anonymous subclass of android.os.Handler)

  • leaks com.less.demo.TestActivity instance

知道了原理后,即使写出易于内存泄露的代码也能够避免内存泄露啦。
上述代码只需在onDestroy()函数中调用mHandler.removeCallbacksAndMessages(null);
在Activity退出的时候的移除消息队列中所有消息和所有的Runnable。

内部类引起的内存泄露

内部类种类大致如下:

  1. 非静态内部类(成员内部类)
  2. 静态内部类(嵌套内部类)
  3. 局部内部类(定义在方法内或者作用域内的类,好似局部变量,所以不能有访问控制符和static等修饰)
  4. 匿名内部类(没有名字,仅使用一次new个对象即扔掉类的定义)

为什么非静态内部类持有外部类引用,静态内部类不持有外部引用。

这个问题非常简单,就像 static的方法只能调用static的东西,非static可以调用非static和static的一样。static–> 针对class, 非static->针对 对象,我是这么简单理解的。看图:

ds.png

匿名内部类
将局部内部类的使用再深入一步,假如只创建某个局部内部类的一个对象,就不必命名了。

匿名内部类的类型可以是如下几种方式。

  1. 接口匿名内部类
  2. 抽象类匿名内部类
  3. 类匿名内部类

匿名内部类总结:

  1. 其实主要就是类定义一次就失效了,主要使用的是这个类(不知道名字)的实例。根据内部类的特性,能够实现回调和闭包。
  2. JavaScript和Python的回调传递的是fuction,Java传递的是object。
    Java中常常用到回调,而回调的本质就是传递一个对象,JavaScript或其他语言则是传递一个函数(如Python,或者C,使用函数指针的方式),由于传递一个对象可以携带其他的一些信息,所以Java认为传递一个对象比传递一个函数要灵活的多(当然java也可以用Method反射对象传递函数)。参考《Java核心技术》

非静态内部类导致内存泄露(前提dog的命长)
下面的案例将导致内存泄露
其中private static Dog dog ;

导致Dog的命比TestActivity长,这就糟糕了,但是注意,如果改为private Dog dog ;

即使Dog是非静态内部类,也不会导致内存泄露,要死也是Dog先死,毕竟Dog是TestActivity的家庭成员,开挂也得看主人。

public class TestActivity extends Activity {

private static Dog dog ;

class Dog {

    public void say(){

        System.out.println("I am lovely dog!");

    }

}

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_test);

    dog = new Dog();

}

@Override

protected void onDestroy() {

    super.onDestroy();

    RefWatcher refWatcher = App.getRefWatcher(this);

    refWatcher.watch(this);

}

}

d.png

In com.leakcanary.demo:1.0:1.

  • com.less.demo.TestActivity has leaked:

  • GC ROOT static com.less.demo.TestActivity.dog

  • references com.less.demo.TestActivity$Dog.this$0

  • leaks com.less.demo.TestActivity instance

哪些内部类或者回调函数是否持有外部类对象?
一个反射案例说明一切

/**

  • 作者: limitless

  • 描述: 一个案例测试所有类型内部类对外部类对象的持有情况

  • 网站: https://github.com/wangli0

*/

public class Main {

/* 持有外部类引用 */

private IAppListener mAppListener = new IAppListener() {

    private String name;

    @Override

    public void done() {

        System.out.println("匿名内部类对象作为成员变量");

    }

};

/* 未持有 */

private static IAppListener sAppListener = new IAppListener() {

    private String name;

    @Override

    public void done() {

        System.out.println("匿名内部类对象作为static成员变量");

    }

};

public static void main(String args[]) {

    Main main = new Main();

    main.test1();

    main.test2();

    main.test3();// test3 《=》test4

    main.test4();

    main.test5();

    main.test6();

}

class Dog {

    private String name;

}

/* 持有外部类引用 */

public void test1(){

    Dog dog = new Dog();

    getAllFieldName(dog.getClass());

}

static class Cat {

    private String name;

}

/* 未持有 */

private void test2() {

    Cat cat = new Cat();

    getAllFieldName(cat.getClass());

}

/* 持有外部类引用 */

private void test3() {

    class Monkey{

        String name;

    }

    Monkey monkey = new Monkey();

    getAllFieldName(monkey.getClass());

}

/* 持有外部类引用 */

private void test4() {

    // 常用作事件回调的地方(有可能引起内存泄露)

    IAppListener iAppListener = new IAppListener() {

        private String name;

        @Override

        public void done() {

            System.out.println("匿名内部类");

        }

    };

    getAllFieldName(iAppListener.getClass());

}

/* 持有外部类引用 */

private void test5() {

    getAllFieldName(mAppListener.getClass());

}

/* 未持有 */

private void test6() {

    getAllFieldName(sAppListener.getClass());

}

private void getAllFieldName(Class<?> clazz) {

    System.out.println("className: ======> " + clazz.getSimpleName());

    Field[] fields = clazz.getDeclaredFields();

    for (Field field : fields) {

        System.out.println(field.getName());

    }

}

}

上述结果足够说明,即使是方法内的回调对象也是持有外部类引用的,那么虽然作用域是局部的,也有存在内存泄露的可能。我多次强调

持有某对象

不代表一定泄露,看的是谁命长。回调在Android开发过程中几乎处处可见,如果持有就会内存泄露的话,那几乎就没法玩了。
一般情况下,我们常常设置某个方法内的xx.execute(new Listener(){xx});是不会导致内存泄露的,这个方法执行完,局部作用域就失效了。但是如果在execute(listener);过程中,某个单例模式的对象 突然引用了这个listener对象,那么就会导致内存泄露。

下面用实例证明我的想法
TestActivity

public class TestActivity extends Activity {

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_test);

    Task task = new Task();

    task.execute(new ICallback() {

        @Override

        public void done() {

            System.out.println("下载完成!");

        }

    });

}

@Override

protected void onDestroy() {

    super.onDestroy();

    RefWatcher refWatcher = App.getRefWatcher(this);

    refWatcher.watch(this);

}

}

Task

public class Task {

public void execute(ICallback iCallback) {

    DownloadManager.getInstance().execute(iCallback);

}

}

DownloadManager

public class DownloadManager {

public static DownloadManager instance;

private ICallback mICallback;

public static DownloadManager getInstance(){

    if (instance == null) {

        instance = new DownloadManager();

    }

    return instance;

}

public void execute(ICallback iCallback) {

    this.mICallback = iCallback;// 反例,千万不要这么做,将导致内存泄露,如果注释掉这行,内存泄露不会发生

    iCallback.done();

}
gg.png

这足以证明我的想法是正确的,Callback的不巧当使用同样会导致内存泄露

的发送。

总结

  1. 如果某些单例需要使用到Context对象,推荐使用Application的context,不要使用Activity的context,否则容易导致内存泄露。单例对象的生命周期和Application一致,这样Application和单例对象就一起销毁。

  2. 优先使用静态内部类而不是非静态的,因为非静态内部类持有外部类引用可能导致垃圾回收失败。如果你的静态内部类需要宿主Activity的引用来执行某些东西,你要将这个引用封装在一个WeakReference中,避免意外导致Activity泄露,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收

    只被弱引用关联

    的对象,只被

    说明这个对象本身已经没有用处了。

    public class TestActivity extends Activity {

    private MyHandler myHandler = new MyHandler(this);
    
    @Override
    
    protected void onCreate(Bundle savedInstanceState) {
    
        super.onCreate(savedInstanceState);
    
        setContentView(R.layout.activity_test);
    
    }
    
    static class MyHandler extends Handler {
    
        private WeakReference<Activity> mWeakReference;
    
        public MyHandler(Activity activity){
    
            mWeakReference = new WeakReference<Activity>(activity);
    
        }
    
        @Override
    
        public void handleMessage(Message msg) {
    
            super.handleMessage(msg);
    
            Toast.makeText(mWeakReference.get(), "xxxx", Toast.LENGTH_LONG).show();
    
            Log.d("xx", mWeakReference.get().getPackageName());
    
        }
    
    }
    
    @Override
    
    protected void onDestroy() {
    
        super.onDestroy();
    
        RefWatcher refWatcher = App.getRefWatcher(this);
    
        refWatcher.watch(this);
    
    }
    

    }

#内存泄露

#内部类

#性能优化

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,646评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,979评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,391评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,356评论 0 215
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,740评论 3 293
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,836评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,022评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,764评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,487评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,728评论 2 252
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,200评论 1 263
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,548评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,217评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,134评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,921评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,919评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,766评论 2 274

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,585评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,296评论 18 399
  • 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏大家都不陌生了,简单粗俗的讲,...
    宇宙只有巴掌大阅读 2,333评论 0 12
  • Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题。内存泄漏...
    _痞子阅读 1,593评论 0 8
  • 和世界交手许多年,你是否光彩依旧?兴致勃然?
    唐建飞V阅读 135评论 0 0