自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(54)
  • 收藏
  • 关注

原创 JVM调优之OOM案例汇总分析

9.6. OOM案例汇总OOM产生的原因多种多样,例如,有些程序未必产生OOM,但是会不断FGC,或是CPU飙高但内存回收特别少。出现OOM的场景进行记录,如下所示:【案例1】硬件升级系统反而卡顿的问题,如调优实战小节中的调优实战1。【案例2】线程池不当运用产生OOM问题,如调优实战小节中的调优实战2。【案例3】jira问题,详细信息见附件。该问题导致的现象是系统出现FGC很频繁,实际系统不断重启。解决方案是更换垃圾回收器G1,换用更大的内存。【案例4】tomcat中的http问题,详细信息见附件

2020-12-16 23:10:44 869

原创 GC调优的监测工具

9.5. 监测工具9.5.1. jconsole远程连接jconsole在使用之前,需要在程序启动时加入参数:java -Djava.rmi.server.hostname=192.168.17.11 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=11111 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremot

2020-12-16 23:09:11 631 3

原创 GC调优的场景、目标、步骤

9.4. GC调优9.4.1. 调优的目标**1)吞吐量:**吞吐量=用户代码执行时间/(用户代码执行时间+垃圾回收时间)**2)响应时间:**垃圾回收算法中的STW时间越短,响应时间越好在进行调优的时候,首先要确定调优的首要目标是什么?响应时间优先,还是吞吐量优先,还是在一定响应时间范围内吞吐量最大等等。如果是吞吐量优先,可以考虑Parrallel Scavenge和Parrallel Old垃圾回收器组合;这种一般出现在科学计算、数据挖掘、thrput等场景。如果是响应时间优先,那么要考虑JD

2020-12-14 23:24:57 418

原创 垃圾回收器可调参数汇总

9.3. 垃圾回收器可调参数汇总9.3.1. 通用可调参数 -Xmn -Xms -Xmx -Xss 年轻代 最小堆 最大堆 栈空间 -XX:+UseTLAB 使用TLAB,默认打开 -XX:+PrintTLAB 打印TLAB的使用情况 -XX:TLABSize 设置TLAB大小 -XX:+DisableExplictGC System.gc()不管用 ,FGC -XX:+PrintGC 打印GC的信

2020-12-13 23:23:37 182

原创 GC日志

9.2. GC日志1)特别说明每种垃圾回收器的日志格式是不同的2)实际生产中日志参数设置在实际生产中要保存的日志格式如下所示,至少需要包含以下内容:-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log 设置日志的保存位置-XX:+UseGCLogFileRotation 设置日志循环保存-XX:NumberOfGCLogFiles=5 设置使用5个日志文件-XX:GCLogFileSize=20M 设置每个日志文件的大小为20M

2020-12-12 23:26:07 223

原创 JVM常用命令行参数

9.1. JVM常用命令行参数1)HotSpot参数分类标准 - 开头 所有的HotSpot都支持非标准 -X 开头 特定版本HotSpot支持特定命令不稳定 -XX 开头 下个版本可能取消2)查询参数的方法采用命令:-XX: PrintFlagsFinal -version | grep 待查询的东西例如寻找所有与CMS垃圾回收器相关的参数,使用指令-XX:PrintFlagsFinal -version | grep CMS3)关于GC的常用命令行参数a) -XX:+PrintComm

2020-12-11 23:30:12 278

原创 ZGC垃圾回收器

8.5.5. ZGC垃圾回收器1)ZGC的介绍ZGC垃圾回收器全称为Z Garbage Collector,是一个可伸缩的、低延迟、并发的垃圾收集器,主要为了满足如下目标进行设计:1.停顿时间不会超过10ms2.停顿时间不会随着堆的增大而增大(不管多大的堆都能保持在10ms以下)3.可支持几百M,甚至几T的堆大小(最大支持4T)2)ZGC的停顿时间停顿时间在10ms以下,10ms其实是一个很保守的数据,在SPECjbb 2015基准测试,128G的大堆下最大停顿时间才1.68ms,远低于10m

2020-12-10 23:31:33 693

原创 G1垃圾回收器

8.5.4. G1垃圾回收器8.5.4.1. 基本概念1)Card Table设计由于做YGC时,需要扫描整个Old区,效率非常低,所以JVM设计了CardTable, 如果一个Old区CardTable中有对象指向Young区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card即可。在结构上,Card Table用BitMap来实现。2)CsetCset的全称是Collection Set,这是一组可被回收分区的集合。在Cset中存活的数据会在GC过程中被移动到另一个可用分区,Cs

2020-12-09 11:25:19 304

原创 ParNew和CMS垃圾回收器组合

8.5.3. ParNew和CMS垃圾回收器组合8.5.3.1. ParNew和CMS垃圾回收器组合1)工作地点ParNew垃圾回收器工作在年轻代,而CMS工作在老年代。2)工作原理当使用ParNew垃圾回收器时:首先在用户线程处于安全的时候执行STW(stop-the-world),这个时刻称为安全点,执行STW时用户线程会暂停执行,同时会有多个线程来并行清理垃圾,垃圾清理的算法是Copying拷贝算法。ParNew与Parallel Scavenge十分相似,ParNew是Parallel S

2020-12-04 11:13:11 1711 1

原创 Parallel Scavenge和Parallel Old垃圾回收器组合

8.5.2. Parallel Scavenge和Parallel Old垃圾回收器组合1)工作地点Parallel Scavenge垃圾回收器工作在年轻代,而Parallel Old工作在老年代。2)工作原理当使用Parallel Scavenge垃圾回收器时:首先在用户线程处于安全的时候执行STW(stop-the-world),这个时刻称为安全点,执行STW时用户线程会暂停执行,同时会有多个线程来并行清理垃圾,垃圾清理的算法是Copying拷贝算法。当使用Parallel Old垃圾回收器时

2020-12-01 23:23:42 879

原创 Serial和Serial Old垃圾回收器组合

8.5.1. Serial和Serial Old垃圾回收器组合1)工作地点Serial垃圾回收器工作在年轻代,而Serial Old工作在老年代。2)工作原理当使用Serial垃圾回收器时:首先在用户线程处于安全的时候执行STW(stop-the-world),这个时刻称为安全点,执行STW时用户线程会暂停执行,同时会有一个单独的线程来清理垃圾,垃圾清理的算法是Copying拷贝算法。当使用Serial Old垃圾回收器时:首先在用户线程处于安全的时候执行STW(stop-the-world),这

2020-12-01 23:22:32 318

原创 垃圾回收器及其组合

8.5. 垃圾回收器及其组合1)GC大致的分类在年轻代空间耗尽时进行回收的机制,叫做MinorGC/YGC,频率比较高,因为大部分对象的存活寿命较短,在新生代里被回收。性能耗费较小。在老年代耗尽时进行回收或是在新生代和老年代同时进行回收的机制,叫做MajorGC/FullGC。默认堆空间使用到达80%(可调整)的时候会触发fgc。以我们生产环境为例,一般比较少会触发fgc,有时10天或一周左右会有一次。2)GC调优中的栈上分配和线程本地分配在GC调优中,栈上分配和线程本地分配无需调整。3)常用

2020-12-01 23:20:11 660

原创 堆内存逻辑分代结构

7.4. 堆内存逻辑分代结构1)部分垃圾回收器使用的模型除Epsilon、ZGC、Shenandoah之外的GC均采用逻辑分代模型,包含Serial、Serial Old、Parallel Scavenge、Parallel Old、ParNew 、CMS、G1等垃圾回收器,其中,G1只是逻辑分代但是物理不分代,其他的垃圾回收器既是逻辑分代也是物理分代。2)逻辑分代模型介绍逻辑分代模型如下图所示,其中可分为两代-young新生代和old老年代,除了G1和Shenandoah等垃圾回收期外的其他垃圾回

2020-11-25 14:55:23 599

原创 GC-垃圾回收算法

7.3. 垃圾回收算法7.3.1. Mark Sweep标记清除1)Mark Sweep算法的原理Mark Sweep算法称为标记清除算法,原理上:将对象划分为三个类别-存活对象、未使用对象、可回收对象,标记清除算法就是将可回收对象清理掉。2)实现过程第一步,对所有区域进行第一遍扫描,利用root Searching垃圾定位算法找到所有不可回收的区域和可回收的区域。第二步,对所有区域进行第二遍扫描,将所有可回收的部分进行清理,完成清理工作。此外,所有空闲的区域,使用空闲列表进行管理。3)优势

2020-11-21 22:50:54 118

原创 GC-垃圾定位算法

7.2. 垃圾定位算法7.2.1. RC 引用计数1)RC的定义RC全称为reference count,引用计数。原理上就是对象中有个参数(引用计数)记录有多少个引用使用了它,每有一个引用放弃使用该对象,那么该参数(引用计数)-1,当为0的时候该对象就变成了垃圾。该过程如下所示。2)存在的问题RC算法无法解释环形垃圾。如下图所示,如果这个环形部分整体为垃圾时,此时可以观察到其中的每个对象的引用计数值均为1,所以RC算法不再适用。7.2.2. Root Searching根可达1)R

2020-11-21 22:47:46 281

原创 GC-Java中的垃圾

7.1. 垃圾的基本介绍1)垃圾的定义在堆中没有任何引用指向的对象,或者仅包含循环引用的多个对象,就是垃圾。2)Java、C++的垃圾回收机制对比Java C++回收机制 自动回收垃圾 手动回收,可能会多次回收(非法访问)和忘记回收(内存泄漏)优势 开发效率高 执行效率高劣势 执行效率低 开发效率低3)垃圾回收区域分类根据分代模型的概念,垃圾回收可以分为两大类,而部分收集Partial GC又可以分为三类:部分收集Partial GC 新生代收集Minor GC 只收集新生代中的垃圾老

2020-11-21 22:45:56 161

原创 JVM常用指令

3 JVM常用指令3.1. 指令集介绍1)指令集的分类指令集大体上可分为两类:基于栈的指令集、基于寄存器的指令集。基于栈的指令集是利用出栈和进栈来实现的,基于寄存器的指令集是通过寄存器的特性来实现的,基于寄存器的指令集复杂但是高效。2)基于栈的指令集大多数指令并不包含操作数,只有一个操作码,指令参数都存放于操作数栈中,操作码的长度为一个字节(即0-255),所以指令集的操作码总数不会超过256条。3)样例介绍【样例程序1】程序代码、程序对应生成的字节码文件、局部变量表内容如下所示因为

2020-11-20 11:21:30 213

原创 指令重排序

2.3. 指令重排序2.3.1. 指令重排序的介绍1)指令重排序的类型在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分三种类型:编译器优化的重排序 编译器在不改变单线程程序语义的前提下(代码中不包含synchronized关键字),可以重新安排语句的执行顺序。指令级并行的重排序 现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。内存系统的

2020-11-17 23:05:57 5290

原创 数据一致性

2.2.1. 存储结构1)存储器的存储结构存储的层次结构可以分为两大类:一是CPU内部的,有寄存器、一级高速缓存、二级高速缓存;二是位于CPU共享的部分,包含:三级高速缓存、主存、硬盘、远程存储。数据的读取和存储都要经过高速缓存(Cache),CPU(CPU Core)与高速缓存间有一条特殊的快速通道。主存(Main Memory)与高速缓存都连接在系统总线(Bus)上,系统总线同时还用于其他组件的通信。在高速缓存出现后不久,系统变得更加复杂,高速缓存与主存间的速度差异被拉大,直到加入了另一级缓存,新

2020-11-15 11:16:12 1064

原创 运行时内存-CPU多级缓存

5.3. CPU多级缓存5.3.1. CPU缓存基本介绍1)CPU缓存出现的原因CPU的频率太快,快到主存跟不上,这样在处理器时钟周期内,CPU经常需要等待主存,浪费资源。所以缓存的出现,是为了缓解CPU和内存间速度的不匹配问题。(结论:CPU>缓存>主存)2)CPU缓存的意义1)时间局部性:如果某个数据被访问,那么在不久的将来它很有可能会被再次访问。2)空间局部性:如果某个数据被访问,那么与它相邻的数据很快也能被访问。3)多核系统中的缓存设置意义在典型的多核系统中,每一个核心都

2020-11-11 22:56:53 373

原创 运行时内存-硬件层数据一致性

5.2. 硬件层数据一致性1)存储器的存储结构存储的层次结构可以分为两大类:一是CPU内部的,有寄存器、一级高速缓存、二级高速缓存;二是位于CPU共享的部分,包含:三级高速缓存、主存、硬盘、远程存储。数据的读取和存储都要经过高速缓存(Cache),CPU(CPU Core)与高速缓存间有一条特殊的快速通道。主存(Main Memory)与高速缓存都连接在系统总线(Bus)上,系统总线同时还用于其他组件的通信。在高速缓存出现后不久,系统变得更加复杂,高速缓存与主存间的速度差异被拉大,直到加入了另一级缓存

2020-11-11 22:53:41 234

原创 运行时内存-Java内存模型

5 运行时内存5.1. Java内存模型5.1.1. 基本介绍1)java内存模型的意义java内存模型(Java Memory Model,JMM)是java虚拟机规范定义的,用来屏蔽掉java程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现java程序在各种不同的平台上都能达到内存访问的一致性。2)主要目标Java内存模型的主要目标是定义程序中变量的访问规则。即在虚拟机中将变量存储到主内存或者将变量从主内存取出这样的底层细节,通过这些规则来规范对内存的读写操作,保证了并发场景

2020-11-11 22:51:23 99

原创 对象的创建及回收

4 对象的创建及回收4.1. 对象的创建过程【Step 1】类加载,包括class loading、class linking(verification、preparation、resolution)、class initializing【Step 2】申请对象内存(保证线程安全)【Step 3】成员变量赋默认值【Step 4】对象必要的设置(例如对象头的设置)【Step 5】调用构造方法,成员变量顺序赋初始值,执行构造方法语句。4.2. 对象在内存中的存储布局1)观察虚拟机的配置通过指令

2020-11-06 23:24:44 269

原创 类加载-自定义类加载器

3.2.4. 自定义类加载器ClassLoader3.2.4.1. 自定义类加载器介绍1)自定义类加载器的作用a.安全。避免核心类库被随意篡改。此外,Bootstrap、Extension、Application类加载器只能加载指定路径下的类字节码,如果想要加载某些特定位置的类文件或者加载网络上的某个类文件时上述三种类加载器就不适用了,需要使用自定义的类加载器。b.加密。java字节码很容易被反编译,通过定制ClassLoader使得字节码先加密防止别人下载后反编译,这里的ClassLoader相当

2020-10-28 23:27:21 685 1

原创 双亲委派机制

3.2.2. 双亲委派机制1)双亲委派介绍双亲委派的理念:在当前类加载器的缓存中寻找是否加载过该类,没有则向父加载器中继续寻找,一直到顶层加载器为止,如果没有则再交由子加载器处理,一直到自定义类加载器为止,如果还没有找到则抛出ClassNotFindException。双亲委派的具体流程:加载类时Custom ClassLoader先检查自己的缓存,如果没有则向他的父加载器APPlication的缓存中查找,如果有就返回结果,没有就继续向APP的父加载器Extension的缓存中查找,如果有就返回结

2020-10-28 23:25:10 717

原创 类加载-类加载器的分类

3.2.1. 类加载器的分类1)类加载器的分类在加载类的时是通过不同的类加载器来加载不同的class文件,类加载器有四类:Bootstrap启动类加载器、Extension扩展类加载器、Application应用程序类加载器、Custom Class Loader自定义类加载器。前三种是系统自带的类加载器,最后是自定义的类加载器。2)样例程序public class TypeOfClassLoader { public static void main(String[] args) {

2020-10-28 23:20:40 1442 1

原创 类加载-类加载过程

3.1. 类加载介绍1)类加载的任务根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例。2)加载流程如下图所示,class文件的加载过程可以分为三步:loading(加载)、linking(链接)、initilizing(初始化)。【loading】loading是将class文件(二进制字节)加载进入内存。通过一个类的完全限定查找此类字节码文件,并利用字节码文件生成代表这个类的Class对象。在loading步骤中,cl

2020-10-27 23:21:03 438

原创 虚拟机基础概念-Class文件结构

2 Class文件结构2.1. Class文件的组成组成部分 功能Magic Number 文件标识符Minor version Jdk的版本号中的小版本号Major version Jdk版本号中的大版号constant_pool_count 常量池中常量的数量constant_pool 常量池的具体实现,常量池中具体有什么内容access_flags 整个class文件的修饰符this_class 当前类的名称super_class 当前类的父类名称interfaces_count

2020-10-18 09:23:05 176

原创 虚拟机基础概念-Classpath、path、JAVA_HOME

Classpath、path、JAVA_HOMEclasspath 环境变量classpath的作用是指定类搜索路径,与import、package关键字有关,要使用已经编写好类的前提是能够找到它们,JVM就是通过CLASSPATH来寻找类的.class文件。我们需要把jdk安装目录下的lib子目录中的dt.jar和tools.jar设置到CLASSPATH中,当然,当前目录“.”也必须加入到该变量中。Javac -c 路径 (可以指定class文件存放目录)java -cp 路径 (可以指定

2020-10-12 23:26:37 347

原创 虚拟机基础概念-JVM、JRE、JDK间的关系

JVMJVM即java虚拟机,它主要有一个功能就是将编译好的class文件进行解释执行,因为class文件不能由操作系统直接执行,需要有JVM解释方可执行。JREJRE是指java运行时环境,class文件在运行时需要调用各种java的类库,即JVM要想运行class文件必须要依赖JRE中的lib库,可以认为,JVM+lib组成JRE。JRE主要面向用户,主要是class文件的运行,假如有编译好的class文件和JRE,那么就可以直接运行class了。JDKJDK即java开发工具包。JDK主要面

2020-10-12 23:23:53 230

原创 java类-包装类

6.4 包装类6.4.1 包装类的概念Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下:基本类型 对应的包装类byte Byteshort Shortint Integerlong Longfloat Floatdouble Doublechar Characterboolean Boolean6.4.2 装箱与拆箱1)定义:基本数

2020-10-02 22:17:51 113

原创 java类-String类

6.3 String类6.3.1 String类的问题String创建的对象是常量,创建后其内容是不可改变的。因此,当每次进行字符串拼接时,就会创建许多字符串,既耗时又占用空间。6.3.2 StringBuilder类1)介绍:StringBuilder类是一个字符串容器,里面可以存放很多字符串,其本质上是由一个数组来存放字符串里面内容的,因此可以随时往数组中添加内容,StringBuilder类会自动维护数组的扩容。2)构造方法Public StringBuilder():构造一个空的Stri

2020-10-02 22:15:07 126

原创 java类-System类

6.2 System类6.2.1 currectTimeMillis()方法1)作用:返回以毫秒为单位的当前系统时间2) 使用格式:System.currectTimeMillis(),其返回值是long类型的数值6.2.2 arrayCopy()方法1)作用:将数组中指定位置的数据复制到另一个数组中2)格式:arrayCopy(Object src, int srcPot, Object dest, int destPos, int length ),其参数意义为src—目标数组,srcPot

2020-10-02 22:13:52 124

原创 java类-时间类

6.1 时间类6.1.1 Date类来源:java.util.Date类 表示特定的瞬间,精确到毫秒。1.构造方法1)无参构造格式:public Date(),赋值给实现对象当前系统时间的值,精确到毫秒。2)全参构造格式:public Date(long date),将基准时间加上date时间(毫秒为单位)赋值为实现对象。由于我国处于东八区,所以基础时间是1970年1月1日8时0分0秒。2.成员方法Long getTime():把日期转换为毫秒,这个方法就相当于System.currect

2020-10-02 22:12:42 175

原创 数据类型

5. 数据类型5.1 数据类型分类5.2 数组1)常用方法public String toString(数组):将参数数组变成字符串public void sort(数组):按照默认升序方式对数组进行排序。但如果是自定义的类,那么这个自定义类需要有Comparable或Comparator接口支持。2)优劣分析1)优势:数组里面可以存放任何数据类型的变量,如int、float、地址等等。2)劣势:数组一旦创建,数组长度不可改变。5.3 字符串5.3.1 创建方式创建字符串的常见3+1种

2020-09-28 23:14:49 200

原创 内部类

4 内部类4.1 内部类的总论1)内部类的定义内部类( Inner Class )就是定义在另外一个类里面的类。与之对应,包含有内部类的类被称为外部类。2) 内部类的分类可分为:成员内部类、静态内部类、局部内部类、匿名内部类3)内部类的优势a.内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类b.内部类的方法可以直接访问外部类的所有数据,包括私有的数据c.内部类所实现的功能使用外部类同样可以实现,只是有时使用内部类更方便d.实现多重继承e.实现回调功

2020-09-25 23:19:50 133

原创 关键字static和final

3 关键字3.1 static3.1.1 static定义成员变量时如果一个成员变量使用了static关键字,那这个这个成员变量就不再属于自己,而是属于所在的类,多个对象共享同一份数据值,不同对象均可以对该成员变量的值进行修改。static修饰的变量、方法、类可以在所属类没有实例对象的情况下被访问。根据类名称访问静态成员变量的时候,全程和对象没有关系,只和类有关系。因为方法中的静态成员变量存放在方法区的静态区中,是单独存放的,在内存中的存放如图所示。注:“static”关键字表明一个成员变量或者

2020-09-22 23:26:07 104

原创 java内存配置

1.1. 内存配置Java的内存划分分为5个部分:1) 虚拟机栈(VS Stack)虚拟机栈表示Java方法执行的线程内存模型,线程每调用一个方法就会为每个方法生成一个栈帧(Stack Frame),用来存储本地变量表、操作数栈、动态链接、方法出口等信息。每个方法被调用和完成的过程,都对应一个栈帧从虚拟机栈上入栈和出栈的过程。虚拟机栈的生命周期和线程是相同的局部变量:方法的参数,或是方法{}内部的变量。作用域:一旦超出作用域,立即从栈的内存中消失。2)堆(Headp)凡是new出的东西均存放

2020-09-15 14:28:53 688

原创 拾遗增补-InterruptedException 异常

6.5. InterruptedException 异常1)出现场景当一个方法后面声明可能会抛出InterruptedException 异常时,说明该方法是可能会花一点时间,但是可以取消的方法。抛InterruptedException的代表方法有:a. java.lang.Object 类的 wait 方法。执行wait方法的线程,会进入等待区等待被notify/notify All。在等待期间,线程不会活动。b. java.lang.Thread 类的 sleep 方法。执行sleep方法的

2020-09-09 23:37:14 213

原创 拾遗增补-死锁

6.8. 死锁1)死锁定义:多个线程在运行过程中因争夺资源而造成的一种僵局,当线程处于这种僵持状态时,如果没有外力作用,这些线程将无法继续进行。2) 死锁产生原因:a.系统资源的竞争:系统资源竞争会导致系统资源不足以及资源分配不当,导致死锁。b.线程的运行顺序不合适:线程在运行过程中请求和释放资源的顺序不当会导致死锁3) 产生死锁的必要条件a.互斥条件:线程对分配的资源有排他性控制,即在一段时间内某个资源仅为一个线程所占用。b.请求和操持条件:当线程因请求资源而阻塞时,对已经获得的资源保持不

2020-09-09 23:36:07 87

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除