JVM调优知识集锦
2019-11-21 19:30:38 1 举报
AI智能生成
JVM知识集锦
作者其他创作
大纲/内容
JVM调优知识集锦
GC 算法 垃圾收集器
对象存活分析
引用计数
缺点:无法解决循环引用的问题
可达性分析
GC Roots
虚拟机栈中引用的对象
方法区中类静态属性实体引用的对象
方法区中常量引用的对象
本地方法栈中JNI引用的对象
垃圾收集算法
标记-清除算法
缺点
效率低
产生内存碎片
复制算法
此算法将内存缩小为原来的一半,持续复制导致效率降低
标记-压缩算法
标记后将所有存活的对象向一端移动
分代收集算法
分为新生代和老生代,使用不同算法回收
垃圾收集器
Serial收集器
1.使用一个线程回收
2.新生代、老年代使用串行回收
3.新生代复制算法、老年代标记-压缩算法
4.收集过程中会服用暂停
ParNew收集器
1.多线程回收
2.新生代并行、老年代串行
Parallel收集器
1.自适应策略调节
3.新生代复制算法、老年代标记-压缩
Parallel Old收集器
1.多线程
2.标记-整理算法
3.老年代并行
CMS收集器
优缺点
优点:并发收集、低停顿
缺点:产生大量空间碎片、开发阶段会降低吞吐量
步骤
1.初识标记(CMS initial mark)服务暂停
2.并发标记(CMS concurrent mark)服务不暂停
3.重新标记(CMS remark)服务暂停
4.并发清除(CMS concurrent sweep)服务不暂停
G1收集器
1.空间整合——标记-整理算法2.可预测停顿
1.初始标记(Initial-Mark)服务暂停
2.Root Region Scanning 回收survivor区
3.Concurrent Marking 在整个堆中进行并发标记
4.Remark 在标记(服务暂停),使用初识快照算法SATB
5.Copy/Clean Up 多线程清除失活对象(服务暂停)
6.集中回收
垃圾收集日志格式
Young GC日志
Full GC 日志
分支主题
JVM 调优
调优命令
jps
显示指定系统内所有的HotSpot虚拟机进程
命令格式:jps[options][hostid]
命令参数:(可不写)option 操作格式 hostid 进程id
option参数
-l 输出主类全名或jar路径
-q 只输出LVMID
-m 输出jvm启动时传递给main()的参数
-v 输出jvm启动时显示指定的JVM参数
jstat
监视虚拟机运行时状态信息(类装载、内存、垃圾收集、JIT编译等)
命令格式:jstat[option] LVMID [interval] [count]
命令参数
option:操作参数
LVMID:本地虚拟机进程ID
interval:连续输出的时间间隔
count:连续数次的次数
-class 监视类装载、卸载梳理、总空间以及消耗时间
-compiler 输出JIT编译过的方法数量耗时等
-gc 垃圾回收堆的行为统计
-gccapacity 在gc的基础上输出Java堆 各区域使用到的最大、最小空间
-gcutil 在gc基础上输出已使用空间占总空间的百分比
-gccause 同gcutil,附加最近两次垃圾回收事件的原因
-gcnew 统计新生代的行为
-gcnewcapacity 新生代与其相应的内存空间的统计
-gcold 统计旧生代的行为
-gcoldcapacity 永生代行为统计
-printcompilation Hotspot编译方式统计
jmap
生成heap dump文件、查询finalize执行队列、查询java堆和永久代的详细信息
命令格式:jmap
-dump 生成堆转储快照
-finalizerinfo 显示F-Queue队列等待Finalize线程执行finalizer方法的对象
-heap 显示Java堆详细信息
-histo 显示堆中对象的统计信息
-permstat 打印Java堆内存的永久保存区域的类加载器的智能统计信息
-F 当-dump无响应时,强制生成dump快照
jhat
与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看
命令格式:jhat [option] [dumpfile]
-stack false|true 关闭|打开对象分配调用栈跟踪
-refs false|true 关闭|开启对象引用跟踪
-port port-number 设置jhat HTTP Server的端口号,默认7000
-exclude exclude-file 执行对象查询时需要排除的数据成员列表文件
-baseline exclude-file 指定一个基准堆转储
-debug int 设置debug级别
-version 启动后只显示版本信息就退出
-J<flag> 传入启动参数,比如-J-Xmx512m
jstack
用于生成java虚拟机当前时刻的线程快照
命令格式
-F:当正常输出请求不被响应时,强制输出线程堆栈
-l 除堆栈外,显示关于锁的附加信息
-m 如果调用到本地方法,可以显示C/C++的堆栈
命令格式:jstack [option] LVMID
jInfo
实时查看和调用虚拟机运行参数
命令格式:jinfo [option][args]LVMID
-flag 输出指定args参数的值
-flags 不需要args参数,输出所有JVM参数的值
-sysprops 输出系统属性
args:JVM参数
调优工具
JDK自带工具
jconsole
是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具。jconsole使用jvm的扩展机制获取并展示虚拟机中运行的应用程序的性能和资源消耗等信息。
位置:jdk/bin 下面的jconsole.exe
VisualVM
VisualVM是一个工具,它提供了一个可视界面,用于查看Java虚拟机(Java Virtual Machine,JVM)上运行的基于Java技术的应用程序(Java应用程序)的详细信息
位置:jdk/bin下面jvisualvm.exe
第三方工具
MAT
eclipse插件形式
GChisto
GChisto是一款专业分析gc日志的工具,可以通过gc日志来分析:MinorGC、full gc的时间、频率等等,通过列表、报表、图表等不同形式来反应gc的情况。虽然界面略显粗糙,但是功能还是不错的。
gcviewer
GC Easy
一个Web工具,在线使用十分方便。地址:http://gceasy.io
类的加载机制
什么是类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后再堆区创建一个java.lang.Class对象,用来封装在方法区内的数据结构
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或者存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误
加载.class文件的方式
1.从本地系统中直接加载
2.通过网络下载.class文件
3.通过zip,jar等归档文件中加载.class文件
4.从专有数据库中提取.class文件
5.将java源文件动态编译为.class文件
类的生命周期
加载
查找并加载类的二进制数据,在加载阶段,虚拟机需要完成以下三件事
1.通过一个类的全限定名称来获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3.在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
连接
验证
文件格式验证
验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头,主版本号是否在当前虚拟机的处理范围之内,常量池中的常量是否有不被支持的类型
元数据验证
对字节码描述的信息进行语义分析(注意:对比JavaC编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了Java.lang.Object之外。
字节码验证
通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的
符号引用验证
确保解析动作能正确执行
验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引起的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配
这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。
解析
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
初始化
初始化、为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。
类变量初始值设定方式
声明类变量时指定初始值
使用静态代码块为类变量指定初始值
JVM初始化步骤
1.假如这个类还没有被加载和连接,则程序先加载并连接该类。
2.如果该类的直接父类还没有被初始化,则先初始化其直接父类。
3.假如类中有初始化语句,则系统执行依次这些初始化语句。
类初始化时机
创建类的实例,new的方式
访问某个类或接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射
初始化某个类的子类,其父类也会被初始化。
Java虚拟机启动会被标明为启动类的类,直接用Java.exe命令来运行某个主类。
使用
结束
结束的时机
执行了System.exit()方法
程序正常执行结束
程序在执行过程中遇到了异常或错误而异常终止
由于操作系统出现错误而导致Java虚拟机进程终止
类的加载器
启动类加载器(BootStrap ClassLoader)
Bootstrap Loader(引导类加载器)是用C语言实现的
负责加载存放JDK\\jre\\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的Java.开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被程序直接引用的。
扩展类加载器(Extension ClassLoader)
该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\\jre\\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader)
该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自己定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
如何自定义类加载器
继承ClassLoader类,实现findClass方法即可
注意点
1.类的名称,需要全限定名称
2.最好不要重写loadClass方法,因为这样容易破坏双亲委派模型
3.能被AppClassLoader类加载的类不能放在类路径下,否则由于双亲委派机制存在,会导致被AppClassLoader直接加载。
JVM加载机制
全盘负责
当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
父类委托
先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
缓存机制
缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效。
应用程序都是由这三类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:
1.在执行非执行代码之前,自动验证数字签名
2.动态地创建符合用户特定需要的定制化构建类
类的加载
三种加载方式
1.命令行启动应用时候由JVM初始化加载
2.通过Class.forName()方法动态加载
3.通过ClassLoader.loadClass()方法动态加载
加载方式区别
Class.forName():将类的.class文件加载到JVM中之外,还会对类进行解释,执行类中的static块
ClassLoader.loadClass():只干一件事情,就是将.class文件加载到JVM中,不会执行static中的内容,只有在newInstance才会去执行static块。
双亲委派模型
流程
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
机制
1.当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成
2.ExtClassLoader加载一个Class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
3.如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
4.若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,ze
意义
系统类防止内存中出现多份同样的字节码
保证Java程序安全稳定运行
JVM内存结构
堆内存(多线程共享)
年轻代
Eden空间
From Survivor空间
To Survivor空间
Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例分配
老年代
控制参数
-Xms设置堆的最小空间大小
-Xmx设置堆的最大空间大小
-XX:NewSize设置新生代最小空间大小
-XX:MaxNewSize设置新生代最大空间大小
-XX:PermSize设置永久代最小空间大小
-XX:MaxPermSize设置永久代最大空间大小
-Xss:设置每个线程的堆栈大小
根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常
方法区(多线程共享)
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来的。
根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
方法区有时候被称为持久代(PermGen)
栈(线程私有)
Java虚拟栈
栈帧
局部变量表
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double),对象引用(reference类型,它可能是一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)
操作栈
动态链接
方法出口
Java虚拟机栈(Java Virtual Machine Stacks)
也是线程私有的,它的什么周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个帧栈(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个帧栈在虚拟机栈中从入栈到出栈的过程
规范
在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常
程序计数器(线程私有)
程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的信号指示器
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
OutOfMemoryError
Java heap space
对象不能被分配到堆内存中
PermGen space
类或者方法不能被加载到持久代,它可能出现在一个程序加载很多类的时候,比如引用了很多第三方的库
Requested array size exceeds VM limit
创建的数组大于堆内存的空间
request <size> bytes for <reason> Out of swap space
分配本地堆分配失败,JNI、本地库或者Java虚拟机都会从本地堆中分配内存空间。
<reason> <stack trace>(Native method)
同样是本地方法内存分配失败,只不过是JNI或者本地方法或者Java虚拟机发现
StackOverflowError
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
0 条评论
回复 删除
下一页