ToB企服应用市场:ToB评测及商务社交产业平台
标题:
JVM口试篇(下)
[打印本页]
作者:
缠丝猫
时间:
2024-6-5 16:42
标题:
JVM口试篇(下)
垃圾网络器
简述 Java 垃圾回收机制
在 java 中,步伐员是不需要表现的去释放一个对象的内存的,而是由虚拟机自行实行。在 JVM
中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会实行的,只有在虚拟机空闲或
者当前堆内存不敷时,才会触发实行,扫面那些没有被任何引用的对象,并将它们添加到要回收
的集合中,进行回收。
GC 是什么?为什么要 GC
GC
是垃圾网络的意思(Gabage Collection),内存处理是编程职员容易出现问题的地方,
忘记大概错误的内存
回收会导致步伐或系统的不稳定以致崩溃 Java 提供的 GC 功能可以自动监测对象是否超过
作用域从而达到自动
回收内存的目的, Java语言没有提供释放已分配内存的表现操纵方法。
垃圾回收的优点和原理。2 种回收机制
Java 语言最显着的特点就是引入了垃圾回收机制,它使 Java 步伐员在编写步伐时不再考虑内存
管理的问题。
由于有这个垃圾回收机制, Java 中的对象不再有“作用域”的概念,只有引用的对象才有“作
用域”。
垃圾回收机制有效的防止了内存泄露,可以有效的利用可利用的内存。
垃圾回收器通常作为一个单独的低级别的线程运行,在不可预知的情况下对内存堆中已经死
亡的或很长时间没有用过的对象进行扫除和回收。
步伐员不能实时的对某个对象或所有对象调用垃圾回收器进行垃圾回收。垃圾回收有
分代复制
垃圾回收
、
标记垃圾回收、增量垃圾回收
。
垃圾回收器的基本原理是什么?
对于
GC
来说,当步伐员创建对象时,GC 就开始监控这个对象的地址、大小以及利用情况。
主动通知虚拟机进行垃圾回收的办法?
通常,
GC
采用
有向图的方式记录
和
管理堆
(heap)中的
所有对象
。通过这种方式确定哪些对象
是"可达的",哪些对象是"不可达的"。当 GC 确定一些对象为" 不可达"时,GC 就有责任回收这
些内存间。
垃圾回收器可以立刻回收内存吗?
可以。步伐员可以
手动实行 System.gc()
,通知 GC 运行,但是 Java 语言规范并
不保证
GC 一
定会实行。
我们能保证 GC 实行吗?
不能,虽然你可以调用 System.gc() 大概 Runtime.gc(),但是没有办法保证GC 的实行。
Java 中引用类型有哪些?
强引用:发生 gc 的时间不会被回收。
软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
弱引用:有用但不是必须的对象,在下一次 GC 时会被回收。
虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用PhantomReference 实现虚
引用,虚引用的用途是在 gc 时返回一个通知。
强引用、软引用、弱引用、虚引用的区别?
思路: 先说一下四种引用的定义,可以结合代码讲一下,也可以扩展谈到ThreadLocalMap
里弱引用用处。
强引用
我们平时 new 了一个对象就是强引用,比方 Object obj = new Object();纵然在内存不敷
的情况下,JVM 宁愿抛出 OutOfMemory 错误也不会回收这种对象
软引用
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不
足了,就会回收这些对象的内存。
SoftReference<String> softRef=new SoftReference<String>(str); // 软引用
复制代码
用处:软引用在实际中有重要的应用,比方欣赏器的后退按钮。按后退时,这个后退时
表现的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现计谋了。
如果一个网页在欣赏结束时就进行内容的回收,则按后退查看前面欣赏过的页面
时,需要重新构建
如果将欣赏过的网页存储到内存中会造成内存的大量浪费,以致会造成内存溢出.
如下代码
Browser prev = new Browser(); // 获取页面进行浏览
SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用
if(sr.get()!=null){
rev = (Browser) sr.get(); // 还没有被回收器回收,直接获取
}else{
prev = new Browser(); // 由于内存吃紧,所以对软引用的对象回收了
sr = new SoftReference(prev); // 重新构建
}
复制代码
弱引用
具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过
程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
String str=new String("abc");
WeakReference<String> abcWeakRef = newWeakReference<String>(str);
str=null;
等价于
str = null;
System.gc();
复制代码
虚引用
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时间都可能被垃圾回收
器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。
怎么判断对象是否可以被回收?
垃圾网络器在做垃圾回收的时间,首先需要判断的就是哪些内存是需要被回收的,哪些对象是
「存活」的,是不可以被回收的;哪些对象已经「死掉」了,需要被回收。
一样平常有两种方法来判断:
引用计数器法
:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计
数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
可达性分析算法
:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象
到 GC Roots 没有任何引用链相连时,则证实此对象是可以被回收的。
在 Java 中,对象什么时间可以被垃圾回收
当对象对当前利用这个对象的应用步伐变得不可触及的时间,这个对象就可以被回收了。
垃圾回收不会发生在永久代,如果永久代满了大概是超过了临界值,会触发完全垃圾回收(Full
GC)。如果你过细查看垃圾网络器的输出信息,就会发现永久代也是被回收的。这就是为什么
正确的永久代大小对制止 Full GC 是非常重要的原因.
JVM 运行时堆内存如何分代?
Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、 From Survivor 区和 To Survivor 区)和
老年代。
参考图 1:
参考图 2:
从图中可以看出: 堆大小 = 新生代 + 老年代。此中,堆的大小可以通过参数 –Xms、-Xmx 来
指定。
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –
XX:NewRatio 来指定 ),
即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小
。
此中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个Survivor 区域分别被命
名为 from 和 to,以示区分.
默认的,
Eden: from : to = 8 :1 : 1 ( 可以通过参数–XX:SurvivorRatio 来设定 ),即: Eden
= 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小
JVM 每次只会利用 Eden 和此中的一块 Survivor 区域来为对象服务,以是无论什么时间,总是
有一块 Survivor 区域是空闲着的。
因此,新生代实际可用的内存空间为 9/10 ( 即 90% )的新生代空间。
新生代
是用来存放新生的对象。一样平常占据堆的 1/3 空间。由于频繁创建对象,以是新生代会频繁触发
MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、 ServivorTo 三个区。
Eden 区
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当 Eden 区内
存不够的时间就会触发 MinorGC,对新生代区进行一次垃圾回收。
Servivor from 区
上一次 GC 的幸存者,作为这一次 GC 的被扫描者。
Servivor to 区
保存了一次 MinorGC 过程中的幸存者。
MinorGC 的过程(复制->清空->交换)
MinorGC 采用复制算法。
eden、 servicorFrom 复制到 ServicorTo,年龄+1首先,把 Eden 和 ServivorFrom 区域
中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准,则赋值
到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不够位置了就放到老年区);
清空 eden、 servicorFrom然后,清空 Eden 和 ServicorFrom 中的对象;
ServicorTo 和 ServicorFrom 交换最后, ServicorTo 和 ServicorFrom 交换,原
ServicorTo 成为下一次 GC 时的 ServicorFrom 区。
老年代
主要存放应用步伐中生命周期长的内存对象。
老年代的对象比力稳定,以是 MajorGC (常常称之为 FULL GC)不会频繁实行。在进行 FULL
GC 前一样平常都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才
触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进
行垃圾回收腾出空间。
FULL GC 采用标记扫除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记
的对象。ajorGC 的耗时比力长,由于要扫描再回收。FULLGC 会产生内存碎片,为了淘汰内存
消耗,我们一样平常需要进行合并大概标记出来方便下次直接分配。当老年代也满了装不下的时间,
就会抛出 OOM(Outof Memory)异常.
永久代
指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时间被放
入永久区域, 它和和存放实例的区域不同,GC 不会在主步伐运行期对永久区域进行清理。以是这
也导致了永久代的区域会随着加载的Class 的增多而胀满,最终抛出 OOM 异常。
JVM 内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为 Eden和 Survivor。
思路
: 先讲一下 JAVA 堆,新生代的划分,再谈谈它们之间的转化,相互之间一些参数的配置
(如: –XX:NewRatio,–XX:SurvivorRatio 等),再表明为什么要这样划分,最好加一点自己
的理解。
答:
这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
共享内存区划分
- 共享内存区 = 持久带 + 堆
持久带 = 方法区 + 其他
Java 堆 = 老年代 + 新生代
新生代 = Eden + S0 + S1
复制代码
一些参数的配置
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ,可以通过参数
–XX:NewRatio 配置。
默认的,Eden : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定)
Survivor 区中的对象被复制次数为 15(对应虚拟机参数-XX:+MaxTenuringThreshold)
复制代码
为什么要分为 Eden 和 Survivor?为什么要设置两个 Survivor 区?
1、如果没有 Survivor,Eden 区每进行一次 Minor GC,存活的对象就会被送到老年代。
老年代很快被填满,触发 Major GC.老年代的内存空间远大于新生代,进行一次 Full GC
消耗的时间比 Minor GC 长得多,所以需要分为 Eden和 Survivor。
2、Survivor 的存在意义,就是减少被送到老年代的对象,进而减少 Full GC 的
发生,Survivor 的预筛选保证,只有经历 16 次 Minor GC 还能在新生代中存
活的对象,才会被送到老年代。
3、设置两个 Survivor 区最大的好处就是解决了碎片化,刚刚新建的对象在
Eden 中,经历一次 Minor GC,Eden 中的存活对象就会被移动到第一块
survivor space S0,Eden 被清空;等 Eden 区再满了,就再触发一次 Minor
GC,Eden 和 S0 中的存活对象又会被复制送入第二块 survivor space S1(这
个过程非常重要,因为这种复制算法保证了 S1 中来自 S0 和 Eden 两部分的
存活对象占用连续的内存空间,避免了碎片化的发生)
复制代码
JVM 中一次完整的 GC 流程是怎样的,对象如何提升到老年代
思路
:先描述一下 Java 堆内存划分,再表明 Minor GC,Major GC,full GC,描述它们之间转
化流程。
答:
Java 堆 = 老年代 + 新生代
新生代 = Eden + S0 + S1
当 Eden 区的空间满了, Java 虚拟机会触发一次 Minor GC,以网络新生代的垃圾,存活
下来的对象,则会转移到 Survivor 区。
大对象
(需要大量连续内存空间的 Java 对象,如那种很长的字符串)
直接进入老年态
;
如果对象在 Eden 出生,并经过第一次 Minor GC 后仍然存活,并且被Survivor 容纳的
话,年龄设为 1,每熬过一次 Minor GC,年龄+1,
若年龄超过肯定限制(15),则被晋
升到老年态。即长期存活的对象进入老年态
。
老年代满了
而无法容纳更多的对象
,Minor GC 之后通常就会进行 Full GC,Full GC 清理
整个内存堆 –
包括年轻代和年老代。
Major GC
发生在老年代的 GC
,
清理老年区
,常常会伴随至少一次 MinorGC,
比 Minor
GC 慢 10 倍以上
。
JVM 中的永久代中会发生垃圾回收吗
垃圾回收不会发生在永久代,如果永久代满了大概是超过了临界值,会触发完全垃圾回收(Full
GC)。如果你过细查看垃圾网络器的输出信息,就会发现永久代也是被回收的。这就是为什么正
确的永久代大小对制止 Full GC 是非常重要的原因。请参考下 Java8:从永久代到元数据区。
(译者注:Java8 中已经移除了永久代,新加了一个叫做元数据区的 native 内存区)
JAVA8 与元数据
在 Java8 中, 永久代已经被移除,被一个称为“元数据区”(元空间)的区域所代替。元空间
的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是利用
本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。 类的元数据放入 native
memory, 字符串池和类的静态变量放入 java 堆中, 这样可以加载多少类的元数据就不再由
MaxPermSize 控制, 而由系统的实际可用空间来控制。
如何判断对象可以被回收?
判断对象是否存活一样平常有两种方式:
引用计数:
每个对象有一个引用计数属性,新增一个引用时计数加 1,引用释放时计数减1,计数为 0
时可以回收。此方法简单,无法解决对象相互循环引用的问题。
可达性分析(Reachability Analysis):
从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots 没有
任何引用链相连时,则证实此对象是不可用的,不可达对象。
引用计数法
在 Java 中,引用和对象是有关联的。如果要操纵对象则必须用引用进行。因此,很显然一个简
单的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之
关联的引用, 即他们的引用计数都不为0, 则阐明对象不太可能再被用到,那么这个对象就是可
回收对象。
可达性分析
为了解决引用计数法的循环引用问题, Java 利用了可达性分析的方法。通过一系列的“GC
roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是
不可达的。要注意的是,不可达对象不等价于可回收对象, 不可达对象变为可回收对象至少要经
过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
Minor GC 与 Full GC 分别在什么时间发生?
新生代内存不够用时间发生 MGC 也叫 YGC,JVM 内存不够的时间发生 FGC
垃圾网络算法有哪些类型?
GC 最基础的算法有三类: 标记 -扫除算法、复制算法、标记-压缩算法,我们常用的垃圾回
收器一样平常都采用分代网络算法。
标记 -扫除算法,“标记-扫除”(Mark-Sweep)算法,如它的名字一样,算法分为“标
记”和“扫除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有
被标记的对象。
复制算法,“复制”(Copying)的网络算法,它将可用内存按容量划分为大小相称的两
块,每次只利用此中的一块。当这一块的内存用完了,就将还存活着的对象复制到别的一块
上面,然后再把已利用过的内存空间一次清理掉。
标记-压缩算法,标记过程仍然与“标记-扫除”算法一样,但后续步骤不是直接对可回收对
象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
分代网络算法,“分代网络”(Generational Collection)算法,把 Java 堆分为新生代和
老年代,这样就可以根据各个年代的特点采用最得当的网络算法。
说一下 JVM 有哪些垃圾回收算法?
标记-扫除算法:标记无用对象,然后进行扫除回收。缺点:服从不高,无法扫除垃圾碎
片。
复制算法:按照容量划分二个大小相称的内存区域,当一块用完的时间将活着的对象复制到
另一块上,然后再把已利用的内存空间一次清理掉。缺点:内存利用率不高,只有原来的一
半。
标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接扫撤消端边界
以外的内存。
分代算法:根据对象存活周期的不同将内存划分为几块,一样平常是新生代和老年代,新生代基
本采用复制算法,老年代采用标记整理算法。
标记-扫除算法
标记无用对象,然后进行扫除回收。
标记-扫除算法(Mark-Sweep)是一种常见的基础垃圾网络算法,它将垃圾网络分为两个阶
段:
标记阶段
:标记出可以回收的对象。
扫除阶段
:回收被标记的对象所占用的空间。
标记-扫除算法之以是是基础的,是由于后面讲到的垃圾网络算法都是在此算法的基础上进
行改进的。
优点
:实现简单,不需要对象进行移动。
缺点
:标记、扫除过程服从低,产生大量不连续的内存碎片,提高了垃圾回收的频率。
标记-扫除算法的实行的过程如下图所示:
复制算法
为了解决标记-扫除算法的服从不高的问题,产生了复制算法。它把内存空间划为两个相称的区
域,每次只利用此中一个区域。垃圾网络时,遍历当前利用的区域,把存活对象复制到别的一个
区域中,最后将当前利用的区域的可回收的对象进行回收。
优点
:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。
缺点
:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。
复制算法的实行过程如下图所示:
标记-整理算法
在新生代中可以利用复制算法,但是在老年代就不能选择复制算法了,由于老年代的对象存活率
会较高,这样会有较多的复制操纵,导致服从变低。标记- 扫除算法可以应用在老年代中,但是
它服从不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记-整理算法(Mark-
Compact)算法,与标记-整理算法不同的是,在标记可回收的对象后将所有存活的对象压缩到
内存的一端,使他们紧凑的分列在一起,然后对端边界以外的内存进行回收。回收后,已用和未
用的内存都各自一边。
优点
:解决了标记-清理算法存在的内存碎片问题。
缺点
:仍需要进行局部对象移动,肯定程度上降低了服从。
标记-整理算法的实行过程如下图所示:
分代网络算法
分代网络法是目前大部门 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内
存划分为不同的域,一样平常情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代
(YoungGeneration)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点
是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
当前商业虚拟机都采用
分代网络
的垃圾网络算法。分代网络算法,顾名思义是根据对象的
存活周
期
将内存划分为几块。一样平常包括
年轻代、老年代 和 永久代
,
如图所示:
当前主流 VM 垃圾网络都采用”分代网络” (Generational Collection)算法, 这种算法会根据对
象存活周期的不同将内存划分为几块, 如 JVM 中的 新生代、老年代、永久代, 这样就可以根据
各年代特点分别采用最得当的 GC 算法。
新生代与复制算法
每次垃圾网络都能发现大批对象已死, 只有少量存活. 因此选用复制算法, 只需要付出少量存活对
象的复制本钱就可以完成网络。
目前大部门 JVM 的 GC 对于新生代都采取 Copying 算法,由于新生代中每次垃圾回收都要回收
大部门对象,即要复制的操纵比力少,但通常并不是按照 1: 1 来划分新生代。一样平常将新生代划
分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次利用
Eden 空间和此中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另
一块 Survivor 空间中。
老年代与标记复制算法
由于老年代对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标
记—整理” 算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存。因而采用
Mark-Compact 算法。
JAVA 虚拟机提到过的处于方法区的永生代(Permanet Generation), 它用来存储 class
类,常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类
对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的From Space(Survivor
目前存放对象的那一块),少数情况会直接分配到老生代。
当新生代的 Eden Space 和 From Space 空间不敷时就会发生一次 GC,进行 GC 后,
EdenSpace 和 From Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和
FromSpace 进行清理。
如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代。
在进行 GC 后,利用的便是 Eden Space 和 To Space 了,云云反复循环。
当对象在 Survivor 区躲过一次 GC 后,其年龄就会+1。 默认情况下年龄到达 15 的对象会
被移到老生代中。
GC 垃圾网络器
Java 堆内存被划分为新生代和年老代两部门,新生代主要利用复制和标记-扫除垃圾回收算法;
年老代主要利用标记-整理垃圾回收算法,因此 java 虚拟中针对新生代和年老代分别提供了多种
不同的垃圾网络器, JDK1.6 中 SunHotSpot 虚拟机的垃圾网络器如下:
说一下 JVM 有哪些垃圾回收器?
如果说垃圾网络算法是内存回收的方法论,那么垃圾网络器就是内存回收的具体实现。下图展示
了 7 种作用于不同分代的网络器,此中用于回收新生代的网络器包括 Serial、PraNew、Parallel
Scavenge,回收老年代的网络器包括Serial Old、Parallel Old、CMS,另有用于回收整个 Java
堆的 G1 网络器。不同网络器之间的连线表示它们可以搭配利用。
Serial 网络器(复制算法): 新生代单线程网络器,标记和清理都是单线程,优点是简单高效;
ParNew 网络器 (复制算法): 新生代收并行集器,实际上是 Serial 网络器的多线程版本,在
多核 CPU 环境下有着比 Serial 更好的表现;
Parallel Scavenge 网络器 (复制算法): 新生代并行网络器,追求高吞吐量,高效利用
CPU。吞吐量 = 用户线程时间/(用户线程时间+GC 线程时间),高吞吐量可以高服从的利用
CPU 时间,尽快完成步伐的运算任务,得当后台应用等对交互相应要求不高的场景;
Serial Old 网络器 (标记-整理算法): 老年代单线程网络器,Serial 网络器的老年代版本;
Parallel Old 网络器 (标记-整理算法): 老年代并行网络器,吞吐量优先,
ParallelScavenge 网络器的老年代版本;
CMS(Concurrent Mark Sweep)网络器(标记-扫除算法): 老年代并行网络器,以获取最
短回收停顿时间为目标的网络器,具有高并发、低停顿的特点,追求最短GC 回收停顿时间。
G1(Garbage First)网络器 (标记-整理算法): Java 堆并行网络器,G1 网络器是JDK1.7 提
供的一个新网络器,G1 网络器基于“标记-整理”算法实现,也就是说不会产生内存碎片。
此外,G1 网络器不同于之前的网络器的一个重要特点是:G1 回收的范围是整个 Java 堆(包
括新生代,老年代),而前六种网络器回收的范围仅限于新生代或老年代。
Serial 与 Parallel GC 之间的不同之处?
Serial 与 Parallel 在 GC 实行的时间都会引起 stop-the-world。它们之间主要不同 serial 网络
器是默认的复制网络器,实行 GC 的时间只有一个线程,而 parallel 网络器利用多个 GC 线程来
实行。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4