Java-JVM总结
一、jvm说明部分
1、jvm内存结构图
2、内存结构说明
(1)程序计数器
用于存储程序在执行时当前线程所执行的字节码的行号指示器。字节码解释器工作是就通过改变这个计数器的值来选取下一条需要执行的字节码指令、分支,循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。在任何一个确定的时刻,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,这个区域属于线程私有。如果是Natvie方法,执行时计数器值设置为null
(2)本地方法栈
本地方法栈就是执行Natvie本地方法服务,如果线程请求的栈深度大于jvm所允许的深度,将抛出StackOverflowError异常,如果本地方法在可以动态扩展,当无法申请到足够的内存时会抛出OutOfMemoryError异常(本地方法栈也是线程私有的)
(3)方法区
方法区和java堆一样,是所有线程共享的内存区域,它用于存储已经被jvm加载的类信息、常量、静态变量,即编译器编译后的数据和代码。Jdk1.8启逻辑上属于非堆。根据jvm规范,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError
(4)JAVA栈
栈和程序计数器一样也是属于线程私有的,它的生命周期与线程相同。每个方法被执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在jvm栈中从入栈到出栈的过程。
局部变量表存放了编译时期的基本数据类型、对象引用类型(不等同于对象本身),和returnAddress类型(指向了一条字节码指令的地址)其中64位长度的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。如果线程请求的栈深度大于jvm所允许的深度,将抛出StackOverflowError异常,如果本地方法在可以动态扩展,当无法申请到足够的内存时会抛出OutOfMemoryError异常
(5)JAVA堆
Java堆是jvm管理的内存中最大的一块,java堆是被所有线程共享的一块内存区域,在jvm启动是初始化好,堆的唯一目的是用来存放对象实例,几乎所有的对象实例都在这里分配内存
Java堆是垃圾收集器管理的主要区域。不同的垃圾收集器在堆中细分有所不同,堆的大小可以通过Xmx和Xms控制
如果在堆中没有内存完成实例分配,并且堆也无法在扩展时,将抛出OutOfMemoryError异常
3、JAVA对象在内存中的布局
(1)创建Object
例子:Object o=new Object();
通过java -XX:+PrintCommandLineFlags -version 命令可以看出来通过-XX:+UseCompressedClassPointers 命令对对象进行了压测 -XX:+UseCompressedOops是对普通对象进行压缩的
综合上面的实验结果可知Object在jvm中总共占16字节 其实有4个字节是为了满足能被8整除,进行的补位
二、垃圾回收
1、垃圾定位
(1)引用计数:
对象在创建实例的时候会在堆内存申请内存时给对象引用记为1,当有其他对象新增对此对象的引用持有时,就把改对象的引用计数+1,释放引用时-1,直至引用计数减至0,该对象的内存就会被释放
(2)根可达算法:
从GC Root作为起点开始搜索,那么整个连通图中对象都是活的,对于GC Root无法达到的对象便是垃圾对象,随时可被GC回收。采用引用计数算法的系统只需在每个实例对象创建之初,通过计数器来记录所有的引用次数即可。而可达性算法,则需要再次GC时,遍历整个GC根节点来判断是否回收。
2、垃圾回收算法
(1)标记清除
位置不连续产生碎片
(2)拷贝算法
没有碎片,浪费空间
(3)标记整理
没有碎片,效率偏低
3、垃圾JVM内存模型
1、分代模型
新生代+老年代+永久代(jdk1.7)/元空间(jdk1.8)
永久代必须指定大小限制,元空间可以设置,也可以不设置,无上限(受限与物理机)
常用参数说明:
-Xmx 设置JVM堆区最大可用内存
-Xms 设置堆区JVM初始内存
-Xmn 设置年轻代大小.整个堆大小=年轻代大小 + 年老代大小 + 持久代大小.持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8.
-Xss 设置每个线程的堆栈大小 JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K
-XX:NewRatio=4 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代).设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
-XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值.设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxPermSize=16m:设置持久代大小为16m.
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄.如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概论.
默认年龄cms 6 ,G1 15,parallel Scavenge 15
1、OutOfMemory异常时输出文件
开启 -XX:+HeapDumpOnOutOfMemoryError
关闭 -XX:-HeapDumpOnOutOfMemoryError
//可以通过jinfo -flag [+|-]HeapDumpOnOutOfMemoryError 或 jinfo -flag HeapDumpOnOutOfMemoryError= 来动态开启或设置值
-XX:HeapDumpPath=/data/dump/jvm.dump//设置文件路径
2、在Full GC时生成dump文件
-XX:+HeapDumpBeforeFullGC //实现在Full GC前dump
-XX:+HeapDumpAfterFullGC //实现在Full GC后dump。
-XX:HeapDumpPath=e:dump //设置Dump保存的路径
新创建的对象超过eden区大小的对象会直接进入老年代,大多数的对象首先进行eden区,eden区满了以后进行一次YGC,YGC以后会将大多数研究表明大多数情况下90%的对象都会在这个区被回收掉,剩余少量的进入s1区,s1不够时再次发送YGC将eden+s1中剩余的对象复制到s2区,s2满后,触发YGC,将剩余eden+s2复制到s1区,再次过程中会将超过s1或s2 50%的大对象直接复制到老年代,除此之外剩余的对象在s1和s2来回复制还没有被回收年龄到了以后直接复制到老年代。老年代对象超过90%后发送1次FGC,FGC的发送会发送STW(Stop the World)在这个过程中会存在应用整个停止
System.gc();java中次函数会触发FGC ,FGC频繁发送说明系统出了严重的问题。
2、分区模型
常用参数说明
-XX:+UseG1GC 使用 G1 (Garbage First) 垃圾收集器
-XX:MaxGCPauseMillis=n 设置最大GC停顿时间(GC pause time)指标(target). 这是一个软性指标(soft goal), JVM 会尽量去达成这个目标.
-XX:InitiatingHeapOccupancyPercent=n 启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示"一直执行GC循环". 默认值为 45.
-XX:NewRatio=n 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n eden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n 提升年老代的最大临界值(tenuring threshold). 默认值为 15.
-XX:ParallelGCThreads=n 设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同.
-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:G1ReservePercent=n 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
-XX:G1HeapRegionSize=n 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.
每个Region 大小取值 1、2、4、8、16、32手工可以通过-XX:G1HeapRegionSize设置
新生代和老年代比例 5%-60%
超过单个region 50%的对象会放入 humongous
4、垃圾回收器
1、常见的垃圾回收器
(1)Serial
年轻代 串行(单线程)回收
(2)Parallel Scavenge
年轻代并行(多线程)回收
(3)ParNew
年轻代和parallel scavenge一样配合cms使用
(4)SerialOld
老年代串行回收
(5)ParallelOld
老年代并行回收
(6)ConcurrentMarkSweep(简称cms)
老年代并发的,垃圾回收和应用程序同时运行,降低stw的时间在200ms
采用著名的(三色标记)算法
容易漏标(浮动垃圾问题不大,下一次在来回收)和错标(采用增量更新)
Cms Incremental Update非常隐蔽问题(最严重问题)
并发标记,产生漏标
X1垃圾回收线程正在标记A,已经标记完成属性m1,正在标记属性m2
X2业务逻辑线程把属性m1的指向了白色对象C
X3垃圾回收线程把A标记为灰色
但是X1垃圾回收线程认为所有属性标记完成,把A设置为黑色,结果D被漏标了
(7)G1
并发回收,降低stw的时间在10ms
采用三色标记算法加SATB(快照)
(8)ZGC
Stw 1ms 可以pk c++目前还在测试阶段
(9)Shenandoah
和zgc一样
(10)Eplison
HotSpot测试使用
Jdk1.8默认的垃圾收集器是:parallel scavenge+parallelOld
cms有很多bug没有一个版本的jdk默认
5、JVM调优和问题排查
(1)HotSpot参数分类
标准 -开头,所有的hotSpot都支持
非标准 -X开头,特定版本hotSpot支持特定命令
不稳定 -XX 开头,下个版本可能取消
-Xmn年轻代 -Xms最小堆 -Xmx 最大堆 -Xss 栈空间
(2)常用命令
1.测试
java -Xms20M -Xmx20M -XX:+printGc com.test.FGcTest01
2、# jps 列出所有java进程
3、# jinfo xxx xxx进程号 列出java进程的配置信息:包括jvm参数,系统属性等
4、# jstat -gc xxx xxx进程号 可以查看堆内存各部分的使用量,以及加载类的数量
5、# jmap -histo:live xxx xxx进程号查看有多少对象产生(注意jmap执行会对进程产生很大影响,甚至卡顿 线上正在运行的系统不适合使用)
(3)dump文件分析工具
MAT(收费) /jhat/ jvisualvm(jdk提供在jdk\JDK8_64\bin目录下)
(4)jvm调优调什么
1、根据需求进行jvm规划和与调优
2、优化JVM运行环境卡顿、慢
3、解决JVM运行过程中出现的OOM问题(OutOfMemoryError)问题
(5)jvm调优步骤
1、熟悉业务场景(目的 :1响应时间,2系统吞吐量)
2、垃圾回收器组合
3、计算机内存
4、对cpu过高的进行排查
5、设定年龄代大小,升级年龄
6、设置日志参数 (1. -Xloggc:/data/logs/xxx-gc-%t.log -XX:+UseGcLogFileRotation -XX:NumberOfGCLogFile=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause 2.或者每天生产日志)
(6)内存溢出和内存泄漏
内存溢出指的是jvm内存不够,超出了最大内存值
内存泄漏值一个对象一直不能被回收,长时间不能回收最终造成内存溢出(例如在使用流的时候没有关闭等待问题)
(7)jvm分析工具
(1)jconsole远程连接
1.程序启动加入参数
java -Djava.rmi.server.hostname=192.168.1.10 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8088 -Dcom.sun.management,jmxremote.authenticate=false -Dcom.sun.management.jmxremote,ssl=false xxx
2.如果报 Local host name unknown: xxx的错误,修改 、etc/hosts 文件,吧ip加进去
3.关闭防火墙
4.在windows打开jconsole远程连接192.168.1.10:8088
(2)Jvisualvm
(3)Jprofiler
收费
(4)arthas在线排查工具(阿里工具)
命令 # jvm 观察jvm信息
命令#thread定位线程问题
命令#dashboard 观察系统情况
命令#heapdump+jhat分析
命令#jad反编译 (能反编译class,也可以动态生成class不用重启服务 redefine热替换 没有jmap功能)