java弱引用什么时候回收

java中虚引用是怎么回事?

很早Java API就添加了弱引用(WeakReference)和软引用(SoftReference),引用类在垃圾回收工作的过程中有重要作用。我们都知道垃圾回收器会回收符合回收条件的对象的内存,但并不是所有的程序员都知道回收条件取决于指向该对象的引用类型。

这正是Java中弱引用和软引用的主要区别。如果一个对象只有弱引用指向它,垃圾回收器会立即回收该对象,这是一种急切回收方式。相对的,如果有软引用指向这些对象,则只有在JVM需要内存时才回收这些对象。弱引用和软引用的特殊行为使得它们在某些情况下非常有用。例如:软引用可以很好的用来实现缓存,当JVM需要内存时,垃圾回收器就会回收这些只有被软引用指向的对象。而弱引用非常适合存储元数据,例如:存储ClassLoader引用。

如果有类被加载,那么也没有指向ClassLoader的引用。一旦上一次的强引用被去除,只有弱引用的ClassLoader就会被回收。

Java中弱引用VS软引用

Java中有如下四种类型的引用:

强引用(Strong Reference)

弱引用(WeakReference)

软引用(SoftReference)

虚引用(PhantomReference)

强引用是我们在编程过程中使用的最简单的引用,如代码String s=”abc”中变量s就是字符串对象”abc”的一个强引用。任何被强引用指向的对象都不能被垃圾回收器回收,这些对象都是在程序中需要的。弱引用使用java.lang.ref.WeakReference class 类来表示,你可以使用如下代码创建弱引用:

代码如下:

Counter counter = new Counter(); // strong reference – line 1

WeakReferenceCounter weakCounter = new WeakReferenceCounter(counter);

//weak reference

counter = null; // now Counter object is eligible for garbage collection

现在只要你给强引用对象counter赋空值null,该对象就可以被垃圾回收器回收。因为该对象此时不再含有其他强引用,即使指向该对象的弱引用weakCounter也无法阻止垃圾回收器对该对象的回收。相反的,如果该对象含有软引用,Counter对象不会立即被回收,除非JVM需要内存。Java中的软引用使用java.lang.ref.SoftReference类来表示,你可以使用如下代码创建软引用:

代码如下:

Counter prime = new Counter(); // prime holds a strong reference _ line 2

SoftReference soft = new SoftReference(prime) ; //soft reference variable has

SoftReference to Counter Object created at line 2

prime = null; // now Counter object is eligible for garbage collection but only be

collected when JVM absolutely needs memory

强引用置空之后,代码的第二行为对象Counter创建了一个软引用,该引用同样不能阻止垃圾回收器回收对象,但是可以延迟回收,与弱引用中急切回收对象不同。鉴于软引用和弱引用的这一区别,软引用更适用于缓存机制,而弱引用更适用于存贮元数据。另一个使用弱引用的例子是WeakHashMap,它是除HashMap和TreeMap之外,Map接口的另一种实现。

WeakHashMap有一个特点:map中的键值(keys)都被封装成弱引用,也就是说一旦强引用被删除,WeakHashMap内部的弱引用就无法阻止该对象被垃圾回收器回收。

虚引用是java.lang.ref package包中第三种可用的引用,使java.lang.ref.PhantomReference类来表示。拥有虚引用的对象可以在任何时候被垃圾回收器回收。和弱引用和软引用相似,你可以通过如下代码创建虚引用:

代码如下:

DigitalCounter digit = new DigitalCounter(); // digit reference variable has strong

reference _ line 3

PhantomReference phantom = new PhantomReference(digit); // phantom reference to object created at line 3

digit = null;

一旦移除强引用,第三行的DigitalCounter对象可以在任何时候被垃圾回收器回收。因为只有一个虚引用指向该对象,而虚引用无法阻止垃圾回收器回收对象。

除了了解弱引用、软引用、虚引用和WeakHashMap,还需要了解ReferenceQueue。在创建任何弱引用、软引用和虚引用的过程中你可以通过如下代码提供引用队列ReferenceQueue:

代码如下:

ReferenceQueue refQueue = new ReferenceQueue(); //reference will be stored in this queue for cleanup

DigitalCounter digit = new DigitalCounter();

PhantomReferenceDigitalCounter phantom = new

PhantomReferenceDigitalCounter(digit, refQueue);

引用实例被添加在引用队列中,你可以再任何时候通过查询引用队列回收对象。一个对象的生命周期可以通过下图进行描述:

在新窗口打开图片

这就是Java中弱引用和软引用的区别。我们还学到了一些基本的引用类:弱引用、软引用、虚引用以及WeakHashMap和WeakHashMap。总之,合理的使用引用可以帮助垃圾回收器更好的管理Java内存。

java弱引用什么时候回收

在java中,对象什么时候可以被垃圾回收

1. 引用计数器算法

解释

系统给每个对象添加一个引用计数器,每当有一个地方引用这个对象的时候,计数器就加1,当引用失效的时候,计数器就减1,在任何一个时刻计数器为0的对象就是不可能被使用的对象,因为没有任何地方持有这个引用,这时这个对象就被视为内存垃圾,等待被虚拟机回收

优点

客观的说,引用计数器算法,他的实现很简单,判定的效率很高,在大部分情况下这都是相当不错的算法

其实,很多案例中都使用了这种算法,比如 IOS 的Object-C , 微软的COM技术(用于给window开发驱动,.net里面的技术几乎都是建立在COM上的),Python语言等.

缺陷

无法解决循环引用的问题.

这就好像是悬崖边的人采集草药的人, 想要活下去就必须要有一根绳子绑在悬崖上. 如果有两个人, 甲的手拉着悬崖, 乙的手拉着甲, 那么这两个人都能活, 但是, 如果甲的手拉着乙, 乙的手也拉着甲, 虽然这两个人都认为自己被别人拉着, 但是一样会掉下悬崖.

比如说 A对象的一个属性引用B,B对象的一个属性同时引用A A.b = B() B.a = A(); 这个A,B对象的计数器都是1,可是,如果没有其他任何地方引用A,B对象的时候,A,B对象其实在系统中是无法发挥任何作用的,既然无法发挥作用,那就应该被视作内存垃圾予以清理掉,可是因为此时A,B的计数器的值都是1,虚拟机就无法回收A,B对象,这样就会造成内存浪费,这在计算机系统中是不可容忍的.

解决办法

在语言层面处理, 例如Object-C 就使用强弱引用类型来解决问题.强引用计数器加1 ,弱引用不增加

Java中也有强弱引用

2. 可达性分析算法

解释

这种算法通过一系列成为 “GC Roots ” 的对象作为起始点,从这些节点开始向下搜索所有走过的路径成为引用链(Reference Chain) , 当一个对象GC Roots没有任何引用链相连(用图论的话来说就是从GC Roots到这个对象不可达),则证明此对象是不可用的

优点

这个算法可以轻松的解决循环引用的问题

大部分的主流java虚拟机使用的都是这种算法

3. Java语言中的GC Roots

在虚拟机栈(其实是栈帧中的本地变量表)中引用的对象

在方法区中的类静态属性引用对象

在方法区中的常量引用的对象

在本地方法栈中JNI(即一般说的Native方法)的引用对象

【JVM】对象分配与回收–垃圾回收机制

对象回收需要确认三件事,那些需要回收(对象存活判定,二次标记),何时回收(GC触发条件)以及如何回收(垃圾回收算法,垃圾回收器)

1)引用计数法

2)可达性分析:GCRoots作为起始点,沿着引用链搜索。

GCRoots可以为:虚拟机栈中引用的对象;本地方法栈中native引用的对象;方法区中类静态属性引用的对象;方法区中常量引用的对象;

1)强引用:即常见的那种引用,一个引用指向堆内存中对象。

2)软引用:还有用但非必须的,当空间不足了(即将OOM),才会对它进行回收。

3)弱引用:也是非必须的,不管空间剩余多少,只要有垃圾回收就进行回收。

4)虚引用:一个对象是否有虚引用,不会对它的生存事件有任何影响,也无法通过虚引用获得一个实例。唯一目的能在对象被回收时收到一个系统通知。

1)当对象被判定为不可达后,会进行一次标记,并筛选出覆盖了finalize方法且还没被执行过的对象进入下一步,那些没有覆盖的,或覆盖但已执行过的(finalize只能执行一次)将会被回收。

2)将筛选出的对象加入一个队列,并有一个优先级很低的Finalier线程去执行队列中对象的finalize方法,若finalize方法中该对象重新获得了引用,则复活,否则在第二次标记时他将被回收(第二次标记就是第一步中的标记,循环这两步)。

1)标记清除:将不可达的对象进行标记,GC时回收、—-效率低、空间碎片

2)复制:将内存区域(堆)分为两块,每次只使用其中的一块。将可达对象进行标记,复制到另一边,然后将刚才那一边全部清空。

这中算法适用于存活对象很少的情况,经常被用于新生代的垃圾回收。

3)标记整理:将不可达对象标记后,清除对象后将存活对象向一端移动,减少空间碎片的产生。

该算法被广泛使用于老年代中。

4)分代收集:由于不同的对象有自己不同的特点。将区域划分为新生代与老年代。

新生代:对象大都朝生夕死,存活时间短。每次GC只有少量对象存活。一般用 复制算法 。(复制算法一般需要空间分配担保空间)

老年代:对象存活时间较长。也没有额外空间对他进行分配担保,更适合标记清除 、标记整理算法。

要注意垃圾回收器的设计目标是,有的是为了减少STW的时间,有的是为了吞吐量。这也应该作为一个选择回收器的标准。

1)Serial / Serial Old

Serial是一个单线程的垃圾回收器,进行垃圾回收时,必然要STW。Serial在新生代采用复制算法,其对应的老年代回收器在老年代采用标记整理法。

2)ParNew 

ParNew实质上一个多线程的Serial,他能够实现多个线程进行垃圾回收,但仍是需要用户线程STW之后才能并发执行垃圾回收。一般开启CPU核数个线程,用-XX:ParallelGCThreads设置。也可以用-XX:SurvivorRatio设置年轻代Eden与Survivor的比例,-XX:HandlerPromotionFailure设置空间分配担保是否开启。

是一种年轻代的垃圾回收器,采用复制算法。

3)Parallel Scavenge收集器 /Parallel Old

这是一款新生代收集器,也是采用复制算法。特点是关注点不在于停顿时间,而在于吞吐量。可通过时设置-XX:UseAdapatorSizePolicy为true将该收集器设为一个自适应调节策略,会自动根据吞吐量与停顿时间等因素进行自适应调节。

相关参数:设置吞吐量大小-XX: GCTimeRatio,-XX:MaxGCPauseMillis:设置最大垃圾收集停顿时间,-XX:UseAdaptativeSizePolicy自适应的设置新生代中区域的比例

Parallel Old是它的老年代的回收器,采用多线程和标记整理算法实现,可以选择Parallel Scavenge与Parallel Old配合使用组合为一个注重吞吐量的垃圾回收机制。

4)CMS (Concurrent Mark Sweep)老年代回收器!

由名字就可以看出来,是一款基于并发的标记清除算法的收集器,CMS收集器是针对于老年带的收集器,以最短停顿时间为目标。

STEP    分为四个步骤,其中耗时最长的并发标记与并发清除是与用户线程并发完成的,而其它两个步骤会有短暂的停顿,以此种方式尽量减少停顿时间

a.初始标记:就是标记一些GCRoots的直接引用,速度非常快,这一过程会有短暂的SWT

b.并发标记:与用户线程并发的进行GCRoots tracing,沿引用链去标记不可达的对象。

c.重新标记:由于第二步是与用户线程一起发生的,有可能在回收过程中会有新的垃圾产生,这一步就是对于这些浮动垃圾进行标记(短暂停顿)

d.并发清除:与用户线程并发进行垃圾回收,采用标记清除算法。

优缺点

并发收集、低停顿

a.对CPU资源敏感,降低吞吐量(吞吐量指用户线程占用时间/总运行时间,这种算法占用了用户现成的一部分CPU时间)(并发涉及的程序都会争抢CPU资源)

b.标记清除算法导致的空间碎片:可能会造成大量空间浪费,若大对象(直接进入老年代)存储时,则会由于连续空间不足引发FullGC。可通过设置-XX:UseCMSCompactAtFullCollection开关,设置是否在FullGC前执行碎片整理合并。

c.浮动垃圾:在并发阶段,用户也可能会随时产生垃圾。在这里需要考虑另一个问题,由于是并发的,所以我们需要考虑用户线程需要占用一部分存储空间,就不可能等到老年代快满了再回收垃圾,需预留给用户线程一些空间。可以通过设置-XX:CMSInitialingOccupationFraction来设置触发FullGC时内存空间使用比例,若这个参数过低,则会增加发生GC的次数;若过高,留给用户空间的内存不够,又会引发Concurrent Mode Failure,而启用Serial Old收集器作为备份方案。

5)G1(Garbage first)

特点

a.将新生代与年老代同一划分为多个Region区域。

b.标记整理算法。

c.与用户线程并发执行。

d.可预测停顿时间。因为可以有计划的避免在整个java堆中进行全局的回收,将堆划分为多个Region,并为每个计算Region里垃圾堆积的价值大小(回收空间大小以及时间),根据价值维护一个Region的优先列表,每次都选取列表中第一个进行回收,即回收价值最大的那个Region。

需要考虑的问题是:化整为零后,多个region之间的一些引用如何确定呢(新生代老年代也有该问题),虚拟机采用Remember Set来避免全表扫描。每个region维护一个Remember Set,在对引用类型进行写操作时,会发生写中断,此时检查该引用的对象是否在别的Region中,若是,则将该信息写入他自己所属Region的RememberSet中。以便在对某一个Region进行可达性分析是不需要全表扫描。

STEP

a.初始标记:仅标记GCRoot是直接引用

b.并发标记:并发的标记所有不在引用链上的引用

c.最终标记:在并发标记阶段新产生的对象会写入Remember set log中,在该阶段将Remember Set log中的数据更新进Remember set中,进行标记。

d.筛选回收:!!!不是并发的,对Region的价值进行排序,并根据用户希望的GC停顿时间制定回收计划,由于只回收一个Region,速度也很快,就没有采取并发。

从前面已经知道了,我们根绝对象生存时间的长短不一这一特征,将堆划分为年轻代和年老代。这里进一步了解年轻代内部划分,以及分配对象时区域之间是如何协助的,从而进一步能够知道GC触发的时间。

1)年轻代的划分与基本工作机制

年轻代一般采用复制算法,将年轻代划分为Eden区与FromSurvivor、ToSurvivor。分配对象时,首先分配到Eden与FromSurvivor区中,然后将可达的对象复制到ToSurvivor区中,注意此时将这些对象的年龄+1,并清除Eden+FromS中的无用对象。然后再将FromS变为ToS,ToS变为FromS,(也省去了将To中的对象复制给From中这一步骤)以便下一次的对象回收。

2)对象分配的机制

a. 一般对象首先分配给Eden区,若空间不足则会触发MinorGC。

b. 大对象会直接分配到年老代,若空间不足触发FullGC。

c. 对象在几个情况下将从年轻代进入年老代:

        c1:对象年龄到了(默认为15,在复制过程中年龄增加,因为可达而熬过了一次GC嘛)可通过-XX:MaxTenuringThreshole设置进入年老代的年龄。

        c2:动态对象年龄判断:当年轻代中同一年龄的对象所占存储空间之和大于 Survivor 的一半时(占满了toSurvivor?),将大于等于该年龄的对象都放入年老代

        c3:空间分配担保:年轻代采用复制算法,就需要有空间在survivor区装不下存活对象的时候帮忙存储多出来的对象(一般发生于一次MinorGC之前,存活对象过多导致survivor放不下了),年老代即为空间分配担保的空间。发生空间分配担保时,会进入老年代。

3)空间分配担保机制

年老代进行空间分配担保是有风险的,若担保过来的对象超过了剩余空间,那么年老代会发生FullGC。

所以在发生MinorGC之前,虚拟机会判断新生代所有对象总空间(!!一次保障性的对比,若成立则其子集肯定成立)是否小于年老代最大连续空间大小,若小于,则担保成功,认为这次MinorGC是安全的,执行GC。若大于,则要判断年老代是否开启HandlePromotionFailure(是否允许空间分配担保),若没开启,则直接执行FullGC。若开启了,则判断以往平均担保大小是否小于最大连续空间大小,若小于,则尝试MinorGC(有风险,失败了再FullGC,多一次MinorGC),若大于,则FullGC(包括MinorGC)。

4)总结一下GC触发条件与设置 

MinGC:当Eden区不够时,可设置-XX:SurvivorRatio设置年轻代中Eden的比例,-XX:NewRatio设置堆中年轻代比例,-Xms  -Xmx设置堆最小最大值。

FullGC:a.System.gc()

                b.永久代,类、静态变量,常量太多,不够放  -XX:MaxPermSize

                c.老年代:c1大对象直接进入时不够 c2空间分配担保,MinorGC前,不愿意担保直接FullGC,愿意担保且空间小于之前担保平均值FullGC,若大于但担保失败了(这次太多了)先MinorGC(发现不行)再FullGC。

                d,CMS回收器:只针对老年代收集,浮动垃圾过多,当超过设置的比例大小时(因为用户线程也有空间),会FullGC。设置老年代空间使用比例

-XX:CMSInitialingOccupationFraction,到达这么多时FullGC

目前了解的调优方式:

1)频繁GC时,要使用JDK一些工具排查问题具体位置。

jstat:虚拟机各方面的运行数据  jmap:生成内存转储快照  一般通过这两个就能定位堆中的问题,判断是空间设置不合理还是程序问题。

跟着GC出发点走,调整各个区域大小-Xms -Xmx -XNewRatio -XX:SurvivorRatio  -XX:permSize,以及年老代相关的 maxTenuringTreshold,handlePromotionFailure(一般设为true,以减少FullGC,给MinorGC机会嘛)

2)性能问题,要根据需求选择合适的垃圾回收器,以停顿时间为目标还是以吞吐量为目标

Java中的强引用,软引用,弱引用,虚引用有什么用

强引用:

只要引用存在,垃圾回收器永远不会回收

Object obj = new Object();

//可直接通过obj取得对应的对象 如obj.equels(new Object());

而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。

软引用:

非必须引用,内存溢出之前进行回收,可以通过以下代码实现

Object obj = new Object();

SoftReferenceObject sf = new SoftReferenceObject(obj);

obj = null;

sf.get();//有时候会返回null

这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null;

软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。

弱引用:

第二次垃圾回收时回收,可以通过如下代码实现

Object obj = new Object();

WeakReferenceObject wf = new WeakReferenceObject(obj);

obj = null;

wf.get();//有时候会返回null

wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾

弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。

弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。

虚引用:

垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现

Object obj = new Object();

PhantomReferenceObject pf = new PhantomReferenceObject(obj);

obj=null;

pf.get();//永远返回null

pf.isEnQueued();//返回是否从内存中已经删除

虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。

虚引用主要用于检测对象是否已经从内存中删除。

本文来自投稿,不代表【】观点,发布者:【

本文地址: ,如若转载,请注明出处!

举报投诉邮箱:253000106@qq.com

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2024年3月26日 06:24:27
下一篇 2024年3月26日 06:35:47

相关推荐

  • 深入java虚拟机pdf,深入java虚拟机 中村成洋 pdf

    在linux环境下,java怎么实现从word格式转换为pdf格式 //设置当前使用的打印机,我的Adobe Distiller打印机名字为 Adobe PDF wordCom.setProperty( ActivePrinter , new Variant( Adobe PDF ));//设置printout的参数,将word文档打印为postscript…

    2024年5月23日
    4600
  • java截取指定长度字符串,java截取指定字符串之后的

    java中如何截取字符串中的指定一部分 第一个参数是开始截取的字符位置。(从0开始)第二个参数是结束字符的位置+1。(从0开始)indexof函数的作用是查找该字符串中的某个字的位置,并且返回。 int end);截取s中从begin开始至end结束时的字符串,并将其赋值给s;split讲解:java.lang.string.split split 方法 将…

    2024年5月23日
    4400
  • java绑定一个端口,java使用端口

    java如何多个service共用一个端口 你如果有多个项目的话,你可以把多个项目放到一个tomcat里面,这样端口相同使用项目名称来进行区分项目。你如果非要使用同一个,你也可以配置不同的域名导向不同的项目。就是访问的域名不同转接到的项目不同。 如果需要同时启动多个程序,要么修改tomcat的配置文件中的监听端口。要么修改jar包程序的监听端口。不能在一台服…

    2024年5月23日
    3600
  • java多线程并发编程基础,Java多线程并发执行返回

    电脑培训分享Java并发编程:核心理论 电脑培训发现本系列会从线程间协调的方式(wait、notify、notifyAll)、Synchronized及Volatile的本质入手,详细解释JDK为我们提供的每种并发工具和底层实现机制。 人们开始意识到了继承的众多缺点,开始努力用聚合代替继承。软件工程解决扩展性的重要原则就是抽象描述,直接使用的工具就是接口。接…

    2024年5月23日
    4700
  • 自学java找工作,自学java找工作需要包装简历吗

    自学java学多久可以找到工作 1、自学Java至少需要一年以上的时间才能达到找工作的水平。报班培训四到六个月的时间就可以找到一份不错的工作。 2、自学Java至少需要一年以上的时间才能达到找工作的水平。 3、如果要想找到一份Java相关的工作,需要至少学习5-6个月时间才能就业。Java开发需要掌握一些基础的编程语言知识,比如掌握面向对象的编程思想、基本的…

    2024年5月23日
    4400
  • java左移右移,java 左移

    java位移问题 1、思路:直接用Integer类的bit运算操作。 2、移位操作:左移:向左移位,符号后面的数字是移了多少位,移的位用0补齐,例如2进制数01111111左移一位后变为11111110,移位是字节操作。 3、Java 位运算 Java 位运算[转]一,Java 位运算表示方法: 在Java语言中,二进制数使用补码表示,最高位为符号位,正数的…

    2024年5月23日
    4300
  • java技术规范,java规范性要求

    现在主流的JAVA技术是什么? java最流行开发技术程序员必看 1 、Git Git一直是世界上最受欢迎的Java工具之一,也是Java开发人员最杰出的工具之一。Git是一个开源工具,是-种出色的分布式版本控制解决方案。 (1).Java基础语法、数组、类与对象、继承与多态、异常、范型、集合、流与文件、反射、枚举、自动装箱和注解。(2).Java面向对象编…

    2024年5月23日
    4100
  • javasocket编程,Java socket编程中,禁用nagle算法的参数

    Java进行并发多连接socket编程 1、Java可利用ServerSocket类对外部客户端提供多个socket接口。基本的做法是先创建一个ServerSocket实例,并绑定一个指定的端口,然后在这个实例上调用accept()方法等待客户端的连接请求。 2、Socket socket=server.accept(0;Thread handleThrea…

    2024年5月23日
    4700
  • java死亡,java死代码是什么意思

    我的世界传送回死亡点指令是什么? 1、下面就让我们一起来了解一下吧:我的世界回到死的地方的指令是输入/back,就可以回到死亡地点了,当然也可以看信标,因为死亡后会有一道光集中在死亡点,只要循着光就可以找到目的地了。 2、在服务器中的指令 首先打开指令台,在指令行输入“/back”就可以回到自己的死亡地点了。在单人游戏中的指令 在单人游戏中,您无法直接返回到…

    2024年5月23日
    4900
  • myeclipse能部署java工程么,myeclipse支持jdk18

    myeclipse如何建java文件 1、点击【File】—【New】–【Class】在如下界面,输入Class的名字,如Test,点击【Finish】。Test.java文件创建成功。 2、点击【File】—【New】–【Class】 在如下界面,输入Class的名字,如Test,点击【Finish】。 Te…

    2024年5月23日
    4100

发表回复

登录后才能评论



关注微信