>Java >Java베이스 >jvm에 성능 조정이 필요한 이유는 무엇입니까?

jvm에 성능 조정이 필요한 이유는 무엇입니까?

青灯夜游
青灯夜游원래의
2020-04-21 15:21:194435검색

jvm에 성능 조정이 필요한 이유는 무엇입니까?

JVM 조정 목표: 더 높은 처리량 또는 더 낮은 대기 시간을 얻으려면 더 작은 메모리 공간을 사용하십시오.

온라인에 접속하기 전에 프로그램을 테스트하거나 실행하는 동안 과도한 CPU 로드, 요청 지연, tps 감소, 심지어 메모리 누수 등 크고 작은 JVM 문제가 발생할 수 있습니다(가비지 수집에 점점 더 많은 시간이 소요됨). ) 오랫동안 가비지 수집 빈도가 점점 높아지고 각 가비지 수집에서 정리되는 가비지 데이터가 점점 줄어들고 메모리 오버플로로 인해 시스템이 충돌하므로 JVM을 조정해야 합니다. 프로그램은 정상적인 작동을 전제로 더 나은 결과를 얻을 수 있습니다. 높은 사용자 경험과 운영 효율성.

다음은 몇 가지 중요한 지표입니다.

  • 메모리 사용량: 프로그램의 정상적인 작동에 필요한 메모리 양입니다.

  • 대기 시간: 가비지 수집으로 인한 프로그램 일시 중지 시간입니다.

  • 처리량: 사용자 프로그램 실행 시간과 사용자 프로그램 및 가비지 수집이 차지하는 총 시간의 비율입니다.

물론 CAP 원리와 마찬가지로 프로그램의 작은 메모리 공간, 낮은 지연 시간, 높은 처리량을 동시에 만족시키는 것은 불가능하며, 튜닝 시 고려하는 방향도 다릅니다. 또한 튜닝에 앞서 실제 시나리오와 결합하여 명확한 최적화 목표를 갖고 성능 병목 현상을 찾아 목표 방식으로 병목 현상을 최적화한 후 최종적으로 다양한 모니터링 도구를 통해 튜닝 결과가 목표를 충족하는지 테스트를 수행해야 합니다.

JVM Tuning Tool

(1) 튜닝이 의존하고 참조할 수 있는 데이터에는 시스템 실행 로그, 스택 오류 메시지, gc 로그, 스레드 스냅샷, 힙 덤프 스냅샷 등이 포함됩니다.

① 시스템 작동 로그: 시스템 작동 로그는 프로그램 코드에 인쇄된 로그로, 코드 수준에서 시스템 작동 트랙(실행 방법, 입력 매개변수, 반환 값 등)을 설명합니다. 시스템에 문제가 있는 경우, 시스템 동작 로그는 가장 먼저 살펴보아야 할 것이 로그이다.

②스택 오류 정보: 시스템에서 예외가 발생하면 스택 정보를 기반으로 초기에 문제를 찾을 수 있습니다. 예를 들어 "java.lang.OutOfMemoryError: Java heap space"를 기반으로 이를 확인할 수 있습니다. 힙 메모리 오버플로; "java.lang.StackOverflowError"에 따라 스택 오버플로로 판단할 수 있으며, "java.lang.OutOfMemoryError: PermGen space"에 따라 메소드 영역 오버플로로 판단할 수 있습니다.

3GC 로그: 프로그램 시작 시 -XX:+PrintGCDetails 및 -Xloggc:/data/jvm/gc.log를 사용합니다. 프로그램이 실행되는 동안 gc의 자세한 프로세스를 기록하거나 "-verbose: gc" 매개변수입니다. gc 로그는 콘솔에 인쇄됩니다. 기록된 gc 로그는 각 메모리 영역의 gc 빈도, 시간 등을 분석하여 문제를 찾아 목표 최적화를 수행할 수 있습니다.

예를 들어 다음 GC 로그는

2018-08-02T14:39:11.560-0800: 10.171: [GC [PSYoungGen: 30128K->4091K(30208K)] 51092K->50790K(98816K), 0.0140970 secs] [Times: user=0.02 sys=0.03, real=0.01 secs]
2018-08-02T14:39:11.574-0800: 10.185: [Full GC [PSYoungGen: 4091K->0K(30208K)] [ParOldGen: 46698K->50669K(68608K)] 50790K->50669K(98816K) [PSPermGen: 2635K->2634K(21504K)], 0.0160030 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
2018-08-02T14:39:14.045-0800: 12.656: [GC [PSYoungGen: 14097K->4064K(30208K)] 64766K->64536K(98816K), 0.0117690 secs] [Times: user=0.02 sys=0.01, real=0.01 secs]
2018-08-02T14:39:14.057-0800: 12.668: [Full GC [PSYoungGen: 4064K->0K(30208K)] [ParOldGen: 60471K->401K(68608K)] 64536K->401K(98816K) [PSPermGen: 2634K->2634K(21504K)], 0.0102020 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

총 4개의 GC 로그입니다. 로그의 첫 번째 줄을 보면 "2018-08-02T14:39:11.560-0800"이 UTC 표준시 형식으로 정확합니다. 밀리초 수준으로, "-XX:+PrintGCDateStamps" 매개변수는 gc 로그 다음에 이 타임스탬프를 인쇄하도록 구성됩니다. "10.171"은 JVM 시작부터 gc 발생까지 경과된 시간(초)입니다. 로그 텍스트의 첫 번째 줄 시작 부분에 있는 "[GC"는 이 GC에서 Stop-The-World가 발생하지 않았음을 나타냅니다(사용자 스레드가 일시 중지됨). 로그 두 번째 줄 시작 부분에 있는 "[Full GC" 텍스트는 이 GC에서 Stop-The-World가 발생했음을 나타내므로 [GC 및 Full GC]는 신세대 및 구세대와 관련이 없으며 System을 호출하는 경우에는 관련이 있습니다. .gc()를 직접 실행하면 [Full GC(System)이 표시됩니다. 다음 "[PSYoungGen" 및 "[ParOldGen"은 GC가 발생하는 영역을 나타냅니다. 표시된 특정 이름은 가비지 수집기와도 관련이 있습니다. 예를 들어 여기서 "[PSYoungGen"은 병렬 Scavenge 수집기를 나타내고 "[ParOldGen"은 GC를 나타냅니다. Serial Old 콜렉터는 또한 Serial 콜렉터에 "[DefNew"를 표시하고 ParNew 콜렉터에는 "[ParNew" 등을 표시합니다. 다음 "30128K->4091K(30208K)"는 이번 gc 이후 이 영역의 메모리 사용 공간이 30128K에서 4091K로 줄어들어 전체 메모리 크기가 30208K임을 나타냅니다. 각 영역의 gc 설명 뒤에 "51092K->50790K(98816K), 0.0140970초" 이번 가비지 컬렉션 이후 전체 힙 메모리의 메모리 사용량이 51092K에서 50790K로 줄었고, 전체 힙 메모리의 전체 공간도 줄었습니다. 98816K였습니다. gc에는 0.0140970초가 걸렸습니다.

4 스레드 스냅샷: 이름에서 알 수 있듯이 스레드 스냅샷을 기반으로 특정 순간의 스레드 상태를 확인할 수 있습니다. 시스템에 요청 시간 초과, 무한 루프, 교착 상태 등이 있을 수 있습니다. 스레드 스냅샷을 기반으로 문제를 파악합니다. 가상 머신과 함께 제공되는 "jstack pid" 명령을 실행하면 현재 프로세스의 스레드에 대한 스냅샷 정보를 덤프할 수 있습니다. 더 자세한 사용 및 분석을 위한 예제는 이미 많이 있습니다. 자세한 내용은 다루지 않겠습니다. 참고용으로 블로그를 게시하세요. http://www.cnblogs.com/kongzhongqijing/articles/3630264.html

⑤힙 덤프 스냅샷: "-XX:+HeapDumpOnOutOfMemory"를 사용할 수 있습니다. "-XX:" 프로그램이 시작되면 HeapDumpPath=/data/jvm/dumpfile.hprof", 프로그램에서 메모리 오버플로가 발생하면 해당 시점의 메모리 스냅샷을 파일 형식으로 덤프합니다(직접 할 수도 있습니다). 프로그램이 실행 중일 때 언제든지 jmap 명령을 사용하여 메모리 스냅샷을 덤프합니다. 이후 해당 시점의 메모리 사용량을 분석합니다.

(2) JVM 튜닝 도구

①用 jps(JVM process Status)可以查看虚拟机启动的所有进程、执行主类的全名、JVM启动参数,比如当执行了JPSTest类中的main方法后(main方法持续执行),执行 jps -l可看到下面的JPSTest类的pid为31354,加上-v参数还可以看到JVM启动参数。

3265 
32914 sun.tools.jps.Jps
31353 org.jetbrains.jps.cmdline.Launcher
31354 com.danny.test.code.jvm.JPSTest
380

 ②用jstat(JVM Statistics Monitoring Tool)监视虚拟机信息 
jstat -gc pid 500 10 :每500毫秒打印一次Java堆状况(各个区的容量、使用容量、gc时间等信息),打印10次

S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
11264.0 11264.0 11202.7  0.0   11776.0   1154.3   68608.0    36238.7     -      -      -      -        14    0.077   7      0.049    0.126
11264.0 11264.0 11202.7  0.0   11776.0   4037.0   68608.0    36238.7     -      -      -      -        14    0.077   7      0.049    0.126
11264.0 11264.0 11202.7  0.0   11776.0   6604.5   68608.0    36238.7     -      -      -      -        14    0.077   7      0.049    0.126
11264.0 11264.0 11202.7  0.0   11776.0   9487.2   68608.0    36238.7     -      -      -      -        14    0.077   7      0.049    0.126
11264.0 11264.0  0.0    0.0   11776.0   258.1    68608.0    58983.4     -      -      -      -        15    0.082   8      0.059    0.141
11264.0 11264.0  0.0    0.0   11776.0   3076.8   68608.0    58983.4     -      -      -      -        15    0.082   8      0.059    0.141
11264.0 11264.0  0.0    0.0   11776.0    0.0     68608.0     390.0      -      -      -      -        16    0.084   9      0.066    0.149
11264.0 11264.0  0.0    0.0   11776.0    0.0     68608.0     390.0      -      -      -      -        16    0.084   9      0.066    0.149
11264.0 11264.0  0.0    0.0   11776.0   258.1    68608.0     390.0      -      -      -      -        16    0.084   9      0.066    0.149
11264.0 11264.0  0.0    0.0   11776.0   3012.8   68608.0     390.0      -      -      -      -        16    0.084   9      0.066    0.149

jstat还可以以其他角度监视各区内存大小、监视类装载信息等,具体可以google jstat的详细用法。

③用jmap(Memory Map for Java)查看堆内存信息 
执行jmap -histo pid可以打印出当前堆中所有每个类的实例数量和内存占用,如下,class name是每个类的类名([B是byte类型,[C是char类型,[I是int类型),bytes是这个类的所有示例占用内存大小,instances是这个类的实例数量:

num     #instances         #bytes  class name
----------------------------------------------
  1:          2291       29274080  [B
  2:         15252        1961040  <methodKlass>
  3:         15252        1871400  <constMethodKlass>
  4:         18038         721520  java.util.TreeMap$Entry
  5:          6182         530088  [C
  6:         11391         273384  java.lang.Long
  7:          5576         267648  java.util.TreeMap
  8:            50         155872  [I
  9:          6124         146976  java.lang.String
 10:          3330         133200  java.util.LinkedHashMap$Entry
 11:          5544         133056  javax.management.openmbean.CompositeDataSupport

执行 jmap -dump 可以转储堆内存快照到指定文件,比如执行 jmap -dump:format=b,file=/data/jvm/dumpfile_jmap.hprof 3361 可以把当前堆内存的快照转储到dumpfile_jmap.hprof文件中,然后可以对内存快照进行分析。

④利用jconsole、jvisualvm分析内存信息(各个区如Eden、Survivor、Old等内存变化情况),如果查看的是远程服务器的JVM,程序启动需要加上如下参数:

"-Dcom.sun.management.jmxremote=true" 
"-Djava.rmi.server.hostname=12.34.56.78" 
"-Dcom.sun.management.jmxremote.port=18181" 
"-Dcom.sun.management.jmxremote.authenticate=false" 
"-Dcom.sun.management.jmxremote.ssl=false"

下图是jconsole界面,概览选项可以观测堆内存使用量、线程数、类加载数和CPU占用率;内存选项可以查看堆中各个区域的内存使用量和左下角的详细描述(内存大小、GC情况等);线程选项可以查看当前JVM加载的线程,查看每个线程的堆栈信息,还可以检测死锁;VM概要描述了虚拟机的各种详细参数。(jconsole功能演示) 

jvm에 성능 조정이 필요한 이유는 무엇입니까?

下图是jvisualvm的界面,功能比jconsole略丰富一些,不过大部分功能都需要安装插件。概述跟jconsole的VM概要差不多,描述的是jvm的详细参数和程序启动参数;监视展示的和jconsole的概览界面差不多(CPU、堆/方法区、类加载、线程);线程和jconsole的线程界面差不多;抽样器可以展示当前占用内存的类的排行榜及其实例的个数;Visual GC可以更丰富地展示当前各个区域的内存占用大小及历史信息(下图)。(jvisualvm功能演示) 

jvm에 성능 조정이 필요한 이유는 무엇입니까?

⑤分析堆转储快照

前面说到配置了 “-XX:+HeapDumpOnOutOfMemory” 参数可以在程序发生内存溢出时dump出当前的内存快照,也可以用jmap命令随时dump出当时内存状态的快照信息,dump的内存快照一般是以.hprof为后缀的二进制格式文件。 
可以直接用 jhat(JVM Heap Analysis Tool) 命令来分析内存快照,它的本质实际上内嵌了一个微型的服务器,可以通过浏览器来分析对应的内存快照,比如执行 jhat -port 9810 -J-Xmx4G /data/jvm/dumpfile_jmap.hprof 表示以9810端口启动 jhat 内嵌的服务器:

Reading from /Users/dannyhoo/data/jvm/dumpfile_jmap.hprof...
Dump file created Fri Aug 03 15:48:27 CST 2018
Snapshot read, resolving...
Resolving 276472 objects...
Chasing references, expect 55 dots.......................................................
Eliminating duplicate references.......................................................
Snapshot resolved.
Started HTTP server on port 9810
Server is ready.

在控制台可以看到服务器启动了,访问 http://127.0.0.1:9810/ 可以看到对快照中的每个类进行分析的结果(界面略low),下图是我随便选择了一个类的信息,有这个类的父类,加载这个类的类加载器和占用的空间大小,下面还有这个类的每个实例(References)及其内存地址和大小,点进去会显示这个实例的一些成员变量等信息: 

jvm에 성능 조정이 필요한 이유는 무엇입니까?

jvisualvm也可以分析内存快照,在jvisualvm菜单的“文件”-“装入”,选择堆内存快照,快照中的信息就以图形界面展示出来了,如下,主要可以查看每个类占用的空间、实例的数量和实例的详情等: 

jvm에 성능 조정이 필요한 이유는 무엇입니까?

还有很多分析内存快照的第三方工具,比如eclipse mat,它比jvisualvm功能更专业,出了查看每个类及对应实例占用的空间、数量,还可以查询对象之间的调用链,可以查看某个实例到GC Root之间的链,等等。可以在eclipse中安装mat插件,也可以下载独立的版本(http://www.eclipse.org/mat/downloads.php ),我在mac上安装后运行起来老卡死~下面是在windows上的截图(MAT功能演示):

jvm에 성능 조정이 필요한 이유는 무엇입니까?

(3) JVM 튜닝 경험

JVM 구성 측면에서는 일반적으로 기본 구성을 먼저 사용할 수 있습니다(일부 기본 초기 매개변수는 시스템 작동 상태에 따라 테스트 중에 일반 애플리케이션이 보다 안정적으로 실행되도록 할 수 있습니다). (세션 동시성), 세션 시간 등), gc 로그, 메모리 모니터링 및 사용된 가비지 수집기를 기반으로 합리적으로 조정합니다. 이전 세대 메모리가 너무 작을 경우 메모리가 너무 많으면 빈번한 Full GC가 발생할 수 있습니다. 규모가 크면 Full GC 시간이 특히 길어집니다.

그럼 신세대, 구세대 등 가장 적합한 JVM 구성은 무엇일까요? 튜닝은 물리적인 메모리가 확실할 때 New Generation 설정이 클수록 Old Generation이 작아지고 Full GC 빈도는 높아지지만 Full GC 시간은 짧아지는 과정입니다. 반대로 New Generation 설정은 크기가 작을수록 Old Generation의 크기가 커지고 Full GC의 빈도는 낮아지지만 각 Full GC에 소요되는 시간이 늘어납니다. 제안 사항은 다음과 같습니다.

  • -Xms 및 -Xmx 값을 동일하게 설정합니다. 힙 크기는 기본 여유 힙 메모리가 40% 미만인 경우 기본적으로 -Xms로 지정됩니다. JVM은 -Xmx에 지정된 크기로 힙을 확장합니다. 여유 힙 메모리가 70%보다 크면 JVM은 -Xms에 지정된 크기로 힙을 줄입니다. Full GC 이후에 메모리 요구 사항을 충족할 수 없으면 동적으로 조정됩니다. 이 단계는 상대적으로 리소스를 많이 소모합니다.

  • 객체가 신세대에서 더 오랫동안 생존할 수 있도록 신세대를 최대한 크게 설정합니다. 각 Minor GC는 객체가 이전 세대에 들어갈 가능성을 방지하거나 지연하기 위해 가능한 한 많은 가비지 객체를 수집해야 합니다. Full GC의 빈도를 줄이기 위한 생성입니다.

  • 구세대에서 CMS 컬렉터를 사용한다면 신세대는 너무 클 필요가 없습니다. 왜냐하면 CMS의 병렬 수집 속도도 매우 빠르고, 동시 마킹과 동시 클리어 단계에서 시간이 많이 걸리기 때문입니다. 수집 프로세스는 사용자 스레드와 동시에 실행될 수 있습니다.

  • 메소드 영역의 크기를 설정할 때 1.6 이전에는 시스템 실행 시 동적으로 추가되는 상수, 정적 변수 등을 고려해야 하지만 1.7에서는 해당 크기만 수용할 수 있으면 됩니다. 시작 시 및 나중에 동적으로 로드되는 클래스 정보입니다.

코드 구현 측면에서 프로그램 대기 및 메모리 누수와 같은 성능 문제는 JVM 구성에 발생할 수 있는 문제 외에도 코드 구현과도 많은 관련이 있습니다. 배열: 지나치게 큰 개체 또는 새 세대에 배열을 수용할 공간이 충분하지 않은 경우, 수명이 짧은 대형 개체인 경우 미리 Full GC가 시작됩니다.

  • 한 번에 데이터베이스에서 많은 양의 데이터를 가져오거나, Excel에서 많은 수의 레코드를 읽는 등 많은 양의 데이터를 동시에 로드하는 것을 피하고 일괄적으로 읽을 수 있습니다. 가능한 한 빨리 참조를 삭제하세요.

  • 컬렉션에 개체에 대한 참조가 있는 경우 해당 개체를 사용한 후 가능한 한 빨리 컬렉션에 있는 참조를 삭제해야 합니다. 이러한 쓸모없는 개체는 노후화를 방지하기 위해 최대한 빨리 재활용해야 합니다.

  • 적절한 시나리오(예: 캐싱 구현)에서 소프트 참조와 약한 참조를 사용할 수 있습니다. 예를 들어 소프트 참조를 사용하여 ObjectA에 인스턴스를 할당합니다. SoftReference objectA=new SoftReference(); 목록에 포함됩니다. 재활용 범위는 두 번 재활용됩니다. 이 재활용에 필요한 메모리가 충분하지 않으면 메모리 오버플로 예외가 발생합니다.

    무한 루프가 발생하지 않도록 하세요. 무한 루프가 발생한 후 루프 본체에 많은 인스턴스가 반복적으로 생성되어 메모리 공간이 빠르게 채워질 수 있습니다.

  • 외부 리소스(데이터베이스, 네트워크, 장비 리소스 등)를 오랫동안 기다리는 것을 피하고, 개체의 수명 주기를 단축하며, 결과를 제때 반환할 수 없는 경우 노후화를 피하도록 노력하세요. 비동기 처리를 적절하게 사용할 수 있습니다.

  • (4) JVM 문제 해결 기록 사례

  • JVM 서비스 문제 해결 https://blog.csdn.net/jacin1/article/details/44837595

잦은 Full GC 프로세스의 잊지 못할 문제 해결 http: //caogen81 .iteye.com/blog/1513345

온라인 FullGC의 빈번한 문제 해결 https://blog.csdn.net/wilsonpeng3/article/details/70064336/

[JVM] 온라인 애플리케이션 문제 해결 https:/ /www.cnblogs.com /Dhouse/p/7839810.html

JVM의 FullGC 문제 해결 과정 http://iamzhongyong.iteye.com/blog/1830265

JVM 메모리 오버플로로 인한 높은 CPU 문제 해결 사례 https://blog .csdn.net/nielinqi520/article/details/78455614

Java 메모리 누수 문제 해결 사례 https://blog.csdn.net/aasgis6u/article/details/54928744

(5) 공통 JVM 매개변수 참조:

Parameter Description Instance
-Xms 초기 힙 크기, 기본 물리적 메모리의 1/64 -Xms512M
-Xm x 최대 힙 크기, 기본값 물리적으로 메모리의 1/4 -Xms2G
-Xmn 신세대 메모리 크기, 공식 권장 사항은 전체 힙의 3/8입니다 -Xmn512M
-Xss 스레드 스택 크기 , jdk1.5 이후 기본값은 1M, 이전 기본값은 256k -Xss512k
-XX:NewRatio=n 신세대와 구세대의 비율을 설정합니다. 예를 들어 is 3은 젊은 세대와 기성 세대의 비율이 1:3이라는 뜻이고, 젊은 세대는 전체 젊은 세대와 기성 세대의 1/4을 차지한다는 뜻입니다 -XX:NewRatio=3
-XX:SurvivorRatio=n 젊은 세대의 두 생존자 영역에 대한 에덴 영역의 비율입니다. 두 개의 생존자 영역이 있습니다. 예: 8은 Eden: Survivor=8:1:1을 의미하며 하나의 Survivor 영역은 전체 젊은 세대의 1/8을 차지합니다 -XX:SurvivorRatio=8
-XX:PermSize=n Initial 영구 생성 값, 기본값은 물리적 메모리의 1/64입니다 -XX:PermSize=128M
-XX:MaxPermSize=n 최대 영구 생성 값, 기본값은 물리적 메모리의 1/4입니다. 물리적 메모리 -XX:MaxPermSize=256M
-verbose:class 콘솔에 클래스 로딩 정보 인쇄
-verbose:gc 콘솔에 가비지 수집 로그 인쇄
-XX:+PrintGC GC 로그 인쇄, 간단한 콘텐츠
-XX:+PrintGCDetails GC 로그 인쇄, 세부 콘텐츠
-XX:+PrintGCDateStamps 시간 추가 밟아 넣다 GC 로그
-Xloggc:파일 이름 gc 로그 경로 지정 -Xloggc:/data/jvm/gc.log
-XX:+UseSerialGC Young 세대 세트 직렬 수집기 Serial
-XX :+UseParallelGC 젊은 세대는 병렬 수집기 Parallel Scavenge를 설정합니다.
-XX:ParallelGCThreads=n Parallel Scavenge가 수집할 때 사용되는 CPU 수를 설정합니다. 병렬 수집 스레드 수입니다. -XX:ParallelGCThreads=4
-XX:MaxGCPauseMillis=n 병렬 청소 재활용의 최대 시간 설정(밀리초) -XX:MaxGCPauseMillis=100
-XX:GCTimeRatio= ㄴ 병렬 청소 가비지 수집 시간을 프로그램 실행 시간의 백분율로 설정합니다. 공식은 1/(1+n) -XX:GCTimeRatio=19
-XX:+UseParallelOldGC Old Age를 병렬 수집기로 설정 ParallelOld Collector
-XX:+UseConcMarkSweepGC 구세대 동시 수집기 CMS를 설정하세요
-XX:+CMSIncrementalMode CMS 수집기를 단일 CPU 상황에 적합한 증분 모드로 설정하세요.

이 기사는 PHP 중국어 웹사이트, java tutorial 칼럼에서 가져온 것입니다. 배우기를 환영합니다!

위 내용은 jvm에 성능 조정이 필요한 이유는 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.