什么是JVM?
JVM就是Java Virtual Mechine的缩写。它是一种基于计算设备的规范,是一台虚拟机,即虚构的计算机。 是通过在实际的计算机上仿真模拟各种计算机功能来实现的。是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。
JDK(Java Development Kit),是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。 JRE(Java Runtime Environment)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。
JVM内存模型:
JAVA不需要像C/C++一样程序完全控制内存,使用内存直接new就可以,不需要销毁,由JVM来进行 清理,开发程序更简单,当然,在内存利用上更容易浪费。虚拟机会把它所管理的内存划分为多个不同 的数据区,称为运行时数据区。如下图:
方法区:存储虚拟机运行时加载的类信息、常量、静态变量和即时编译的代码,因此可以把这一部分考虑为 一个保存相对来说数据较为固定的部分,常量和静态变量在编译时就确定下来进入这部分内存,运行时类信 息会直接加载到这部分内存,所以都是相对较早期进入内存的。
堆区:是JVM所管理的内存中最大的一块。主要用于存放对象实例,每一个存储在堆中的Java对象都会是这个对象的类的一个副本,它会复制包括继承自它父类的所有非静态属性。而所谓的垃圾回收也主要是在堆区进行。 根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑是上连续的即可,就像我们的磁盘空间一样。在实现上,既可以实现成固定大小的,也可以是可扩展的。
JVM栈区(虚拟机栈区):主要是存放一些对象的引用和编译期可知的基本数据类型,这个区域是线程私有的, 即每个线程都有自己的栈,与线程生命周期相同。可能会出现两种异常情况: 如果线程请求的栈深度大于虚拟机锁所允许的深度,则抛出StackOverflowError异常 如果虚拟机栈可以动态扩展,扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
本地方法栈:本地方法在运行时存储数据产生的栈区域。是JVM运行Native方法的空间,和JVM栈的作用是类似的,由于很多Native方法都是C语言实现的,所以它通常又叫C栈。和JVM栈一样,也会抛出StackOverflowError和OutOfMemoryError异常。
程序计数器:是用来记录程序运行到什么位置的,是线程私有的。
JVM内存回收机制:JVM区域分为堆区和非堆区,根据不同区域的特点采用不同的回收算法,从而提高垃圾回收效率。
垃圾回收器:主要回收的是堆区中未使用的内存区域,并对相应的区域进行整理。在堆区中,又根据对象内存的存活时间或者对象大小,分为“年轻代”和“年老代”。“年轻代”中的对象是不稳定的易产生垃圾,而“年老代”中的对象比较稳定,不易产生垃圾。
JVM回收算法介绍:
一、引用计数式内存回收:
引用计数(Reference Count)式内存回收机制是Objective-C以及Swift语言中正在使用的内存回收机制。只要有引用,那么引用计数就加1。当引用计数为0时,该块内存就会被回收。这种内存清理方式容易形成“引用循环”。
在Objective-C的引用计数中循环引用而造成内存泄露的问题,可以将变量声明成weak或者strong类型。当出现“强引用循环”时,我们将其中的一个引用设置为weak类型即可,然后这种强引用循环就被打破了,也就不会造成“内存泄露”的问题。
二、复制式内存回收:
复制式回收:其核心就是“复制”,但前提是有条件复制。在垃圾回收时,将“活对象”复制到另一块空白的堆区,然后将之前的区域一并清除。“活对象”就是指沿着对象的引用链可以到“栈”上的对象。当然在将活对象复制到新的“堆区”后,也要将栈区的引用进行修改。 当内存垃圾特别多 ,垃圾回收的效率还是比较高的,因为复制的对象比较少,清除时直接将旧的堆空间进行清理即可。 当垃圾比较少的时候,复制大量的活对象,效率还是比较低的; 堆的存储空间进行分半,总有一半是空闲的,堆空间的利用率不高。
三、标记-压缩回收算法:
标记-压缩回收算法:在垃圾少时的工作效率比较高,而垃圾多的情况下,工作效率反而不高,这就与“复制式”形成了互补。 第一步就是标记,需要将堆区中的“活对象”进行标记 标记完成后,开始进行压缩了,将活对象压缩到“堆区”的一段,然后将剩余的部分进行清除。 如果垃圾太多碎片化严重时,移动的“活对象”较多,效率比较低。 这种方式可以与“复制式”结合使用,根据当前堆区的垃圾状态来选择哪种回收方式。正好与“复制式”形成优势互补。 将“复制式”、“标记-压缩式”的回收方式进行整合的算法,就是“分代式”垃圾回收机制。
四、分代式垃圾回收:
分代”即根据对象易产生垃圾的状态或者对象的大小将其分为不同的代,可分为“年轻代”、“年老代”和“永久代”(元空间)。元空间 不在堆中。不同代演变如下图:
JVM GC(Garbage Collection)触发条件:
堆内存GC : JVM使用较高的频率对年轻的对象(Young generation)进行YGC,而对老年代(Tenured Generation)较少(Tenured generation)进行Full GC。
非堆内存不GC : GC不会在主程序运行期对PermGen Space进行清理,所以如果你的应用中有很多CLASS(特别是动态生成类,当然permgen space存放的内容不仅限于类)的话,就很可能出现PermGen Space错误。
JVM 主要参数配置说明:
-server :服务器模式
-Xms:初始堆大小,默认为物理内存的1/64(<1GB);默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制,一般是直接设置为-Xmx大小,不需要再计算增加内存。
-Xmx:最大堆大小,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
-Xmn:新生代的内存空间大小,一般默认就好,推荐配置为整个堆的3/8,实际是多大,需要进行配置测试确定。
-XX:SurvivorRatio:新生代中Eden区域与Survivor区域的容量比值,默认值为8。两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10。
-XX:MetaspaceSize=8m 无数据配置(1.8后,取消了永久代)
-XX:MaxMetaspaceSize=50m 无数据配置
JVM 配置说明:
-Xss:每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。应根据应用的线程所需内存大小进行适当调整。在相同物理内存下,大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。
-XX:+HeapDumpOnOutOfMemoryError: 设置当首次遭遇内存溢出时导出此时堆中相关信息
-XX:HeapDumpPath: 指定导出堆信息时的路径或文件名,设置
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof,当发生内存溢出时,自动生成dump文件,可用于分析原因。 日志相关:
-Xloggc :记录日志文件位置,如-Xloggc:/app/logs/system-center_.gclog
-XX:+PrintGCDateStamps:打印日志详情
-XX:+PrintGCApplicationStoppedTime:打印GC停顿时间
-XX:PrintHeapAtGC :打印GC前后的详细堆栈信息
示例1: java -jar test.jar 示例2: java -server -Xms512m -Xmx512m -jar /app/erp/vms-storage.jar 示例3: java -server -Xms4G -Xmx4G -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps -Xloggc:/app/logs/syslog-center_.gclog -jar /app/erp/syslog-center.jar 示例4: java -server -Xms4G -Xmx4G -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps -Xloggc:/app/logs/syslog-center_.gclog -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -jar /app/erp/syslog-center.jar
jstat命令使用:
jstat命令是使用频率比较高的命令,主要用来查看JVM运行时的状态信息,包括内存状态、垃圾回收等。
命令格式:
jstat [option] LVMID [interval] [count] 其中LVMID是进程id,interval是打印间隔时间(毫秒),count是打印次数(默认一直打印)
option参数解释:
-class class loader的行为统计
-compiler HotSpt JIT编译器行为统计
-gc 垃圾回收堆的行为统计
-gccapacity 各个垃圾回收代容量(young,old,perm)和他们相应的空间统计
-gcutil 垃圾回收统计概述
-gccause 垃圾收集统计概述(同-gcutil),附加最近两次垃圾回收事件的原因
-gcnew 新生代行为统计
-gcnewcapacity 新生代与其相应的内存空间的统计
-gcold 年老代和永生代行为统计
-gcoldcapacity 年老代行为统计
-gcpermcapacity 永生代行为统计
-printcompilation HotSpot编译方法统计
常用示例及打印字段解释:
字段解释:
S0 survivor0使用百分比
S1 survivor1使用百分比
E Eden区使用百分比
O 老年代使用百分比
M 元数据区使用百分比
CCS 压缩使用百分比
YGC 年轻代垃圾回收次数,回收一次大于0.1秒需要关注
YGCT 年轻代垃圾回收消耗时间
FGC 老年代垃圾回收次数,频繁FGC需要关注,最好的情况下是不出现,一般来说,一个小时出现一次可以接受。实际怎么样算频繁要看实际应用。
FGCT 老年代垃圾回收消耗时间
GCT 垃圾回收消耗总时间
jstack是用来查看JVM线程快照的命令,线程快照是当前JVM线程正在执行的方法堆栈集合。使用jstack命令可以定位线程出现长时间卡顿的原因,例如死锁,死循环等。jstack还可以查看程序崩溃时生成的core文件中的stack信息。
命令格式:
jstack [-l] <pid> (连接运行中的进程)
jstack -F [-m] [-l] <pid> (连接挂起的进程)
jstack [-m] [-l] <executable> <core> (连接core文件)
jstack [-m] [-l] [server_id@]<remote server IP or hostname> (连接远程debug服务器)
option参数解释: -F 当使用jstack <pid>无响应时,强制输出线程堆栈。
-m 同时输出java和本地堆栈(混合模式)
-l 额外显示锁信息
jmap是用来生成堆dump文件和查看堆相关的各类信息的命令,例如查看finalize执行队列,heap的详细信息和使用情况。
命令格式:
jmap [option] <pid> (连接正在执行的进程)
jmap [option] <executable <core> (连接一个core文件)
jmap [option] [server_id@]<remote server IP or hostname> (链接远程服务器)
option参数解释: <none> to print same info as Solaris pmap
-heap 打印java heap摘要
-histo[:live] 打印堆中的java对象统计信息
-clstats 打印类加载器统计信息
-finalizerinfo 打印在f-queue中等待执行finalizer方法的对象
-dump:<dump-options> 生成java堆的dump文件
dump-options:
live 只转储存活的对象,如果没有指定则转储所有对象 format=b 二进制格式
file=<file> 转储文件到<file>
-F 强制选项 就是什么情况下都可以输出。
使用示例1 : jmap -heap 89917 //输出堆信息,如下图:
使用示例2: jmap -dump:format=b,file=test_data.dump 89917 //把内存输出到文件,用于分析
举个栗子:
一个写数据库的应用,并发测试最高TPS 500多,需要进行优化。(在分析问题之前 一定要先确认环境是正常的!)
优化排过程: 1、先检查应用资源及数据库资源是否占用高;
2、检查GC情况;
3、资源正常,GC正常,性能上不去,就有可能是线程阻塞或者线程串行等待了,使用jstack查看线程状态 jstack 112594 >data1_test,输出到文件。查看BLOCKED线程;
4、很多问题在阻塞的,那么可能是什么原因呢?检查一下mysql线程池设计值,根据经验,mysql连接数在100-300性能最高。