jvm:跨语言的平台(一次编译,到处运行)
jvm:自动内存管理,自动垃圾回收
Java 生命周期
加载
加载是一个读取class文件,将其转化为某种静态数据结构存储在方法区内,并在堆汇总生成一个便于用户调用的java.long.class类型的对象的过程
Jvm 运行原理
1、运行时数据区
jvm在执行java程序的过程中,会将管理的内存分为若干个不同的数据区域。即:
1.程序计数器(PC寄存器)
2.虚拟机栈
3.本地方法栈
4.堆空间
5.方法区
其中:
线程私有的:程序计数器,虚拟机栈,本地方法栈
线程共享的:堆,方法区,直接内存
1.1 程序计数器(PC寄存器)
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变计数器的值来选取下一行执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。
java虚拟机的多线程是通过线程轮流切换并分配CPU的时间片的方式实现的,因此在任何时刻一个处理器都只会处理一个线程,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,因此这类内存区域为**“线程私有”**的内存。
说明:程序计数器是唯一一个在java虚拟机规范中没有规定OOM的区域
程序计数器主要有两个作用:
1.字节码解释器通过改变程序计数器的值来读取指令,实现代码的流程控制
2.在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行的位置
1.2 虚拟机栈
java虚拟机栈也是线程私有的,它的生命周期和线程相同,虚拟机描述的是 Java 方法执行的内存模型:每个方法被执行,便会向栈中放入一个栈帧,每个栈帧中都拥有局部变量表、操作数栈、动态链接、方法出口、附加信息。每个方法被调用直到完成,则会对应着一个栈帧从入栈到出栈。
局部变量表:
数字数组,用于存储方法参数,方法体内的局部变量,包括各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)
局部变量表在编译阶段就确定了最大变量的大小
槽(slot):局部变量表中的最小单位。对于64位长度的long和double占用2个slot,32位占用1个slot。
如果当前帧是由构造方法或者实例方法创建的,那么该对象的引用this会放在index为0的slot处,其余参数顺序存放
slot可以被重复利用。
操作数栈
操作数栈 和局部变量区一样,操作数栈也是被组织成一个以字长为单位的数组。但是和前者不同的是,它不是通过索引来访问,而是通过压栈和出栈来访问的。主要用于保存变量计算过程的中间结果,同时作为变量的临时存储空间。
虚拟机在操作数栈中存储数据的方式和在局部变量区中是一样的:如int、long、float、double、reference和returnType的存储。对于byte、short以及char类型的值在压入到操作数栈之前,也会被转换为int。
动态链接(指向运行时常量池中该栈帧的引用)
虚拟机运行的时候,运行时常量池会保存大量的符号引用,这些符号引用可以看成是每个方法的间接引用。如果代表栈帧A的方法想调用代表栈帧B的方法,那么这个虚拟机的方法调用指令就会以B方法的符号引用作为参数,但是因为符号引用并不是直接指向代表B方法的内存位置,所以在调用之前还必须要将符号引用转换为直接引用,然后通过直接引用才可以访问到真正的方法。
如果符号引用是在类加载阶段或者第一次使用的时候转化为直接应用,那么这种转换成为静态解析。
如果是在运行期间转换为直接引用,那么这种转换就成为动态连接。
方法返回地址
方法的返回分为两种情况:
如果方法是正常退出的,则调用者的PC计数器的值就可以作为返回地址;
如果是因为异常退出的,则是需要通过异常处理表来确定。
不过无论是那种方式的方法结束,在退出当前方法时都会跳转到当前方法被调用的位置。
如果方法是正常退出的,则调用者的PC计数器的值就可以作为返回地址;
如果是因为异常退出的,则是需要通过异常处理表来确定。
本地方法栈
堆是Java 虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域。
每个JVM实例对应一个堆空间,JVM启动时创建,且空间大小确定。
此内存区域的唯一目的就是存放对象实例,(几乎)所有的对象实例以及数组都在堆上分配
设置堆空间的大小:-Xms 起始内存 -Xmx 最大内存对象与数组不可能存储在栈中,因为栈帧内的局部变量表保存了对象或数组的地址引用,这些引用指向在堆中的内存位置
JAVA 堆
堆是Java 虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域。
每个JVM实例对应一个堆空间,JVM启动时创建,且空间大小确定。
此内存区域的唯一目的就是存放对象实例,(几乎)所有的对象实例以及数组都在堆上分配
设置堆空间的大小:-Xms 起始内存 -Xmx 最大内存对象与数组不可能存储在栈中,因为栈帧内的局部变量表保存了对象或数组的地址引用,这些引用指向在堆中的内存位置
Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。
堆:新生代
新生代由Eden Space和两块相同大小的Survivor Space(通常又称S0和S1或From和To)构成。几乎所有的Java对象都是在Eden区被new出来的,同时,绝大部分的Java对象都在新生代销毁。
1、 可通过-Xmn参数来指定新生代的大小;也可以通过-XX:SurvivorRation来调整Eden Space及SurvivorSpace的大小。2、 新生代的初始值NewSize默认为1M,最大值需要设置,可以通过参数-XX:NewSize和-XX:MaxNewSize或-Xmn进行设置;
3、为老年代与新生代的大小比值,默认为2:1;
SurvivorRatio为新生代中Eden和Survivor的大小比值,默认为8:1
4、 Eden : from : to = 8 :1 : 1
可以通过参数-XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块Survivor 区域是空闲着的。
- new的对象先放伊甸园区。此区有大小限制。
- 当伊甸园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC), 将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载新的对象放到伊甸园区(只有当Eden区满的时候会触发YGC/MinorGC)。
- Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。
- 年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到老年代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。
- 这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。(复制之后有交换,谁空谁为To)
- Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
堆:老年代
用于存放经过多次新生代GC仍然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:
大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就不在新生代分配,而是直接在老年代分配。
大的数组对象,且数组中无引用外部对象。
如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
Minor GC/YGC、Major GC、Full GC
堆是GC收集垃圾的主要区域。GC 分为:Minor GC、FullGC 、 Major GC 。
Minor GC 是发生在新生代中的垃圾收集动作,所采用的是复制算法。
- 当对象在 Eden ( 包括一个 Survivor 区域,这里假设是 from 区域 ) 出生后,
- 在经过一次 Minor GC 后,如果对象还存活,并且能够被另外一块 Survivor 区域所容纳( 上面已经假设为 from 区域,这里应为 to 区域,即 to 区域有足够的内存空间来存储 Eden 和 from 区域中存活的对象 ),
- 则使用复制算法将这些仍然还存活的对象复制到另外一块 Survivor 区域 ( 即 to 区域 ) 中,并且将这些对象的年龄设置为1,
- 然后清理 Eden 以及 Survivor 区域 ( 即from 区域 ),以后对象在 Survivor 区每熬过一次 Minor GC,就将对象的年龄 + 1,
- 当对象的年龄达到某个值时 ( 默认是 15 岁),这些对象就会成为老年代。
- 但这也不是一定的,对于一些较大的对象 ( 即需要分配一块较大的连续内存空间 ) 则是直接进入到老年代。
Major GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法。
Major GC的速度- -般会比Minor GC慢10倍以上,STW的时间更长。Major GC 发生的次数不会有 Minor GC 那么频繁,并且做一次Major GC 要比进行一次 Minor GC 的时间更长,会导致STW。
标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。Full GC会发生在堆和方法区一起清除。
总结:
1、年轻代是对象的诞生、成长、消亡的区域,一个对象在这里产生、应用,最后被垃圾回收器收集、结束生命。
2、老年代放置长生命周期的对象,通常都是从Survivor区域筛选拷贝过来的Java对象。当然,也有特殊情况,我们知道普通的对象会被分配在TLAB上;如果对象较大,JVM会 试图直接分配在Eden其他位置上;如果对象太大,完全无法在新生代找到足够长的连续空闲空间,JVM就会直接分配到老年代。
3、当GC只发生在年轻代中,回收年轻代对象的行为被称为MinorGC。 当GC发生在老年代时则被称为MajorGC或者FullGC。4、一 般MinorGC的发生频率要比MajorGC高很多,即老年代中垃圾回收发生的频率将大大低于年轻代。
方法区
- 方法区(Method Area) 与Java堆一 样,是各个线程共享的内存区域。它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码等数据
- 方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。
- 方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展。
- 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误: java. lang . OutofMemoryError:Metaspace。
- 关闭JVM就会释放这个区域的内存。
- 相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。
类型信息:全限定名、直接超类的全限定名、类的类型还是接口类型、访问修饰符、直接超接口的全限定名的有序列表
字段信息:字段名、字段类型、字段的修饰符
方法信息:方法名、方法返回类型、方法参数的数量和类型(按照顺序)、方法的修饰符
其他信息:除了常量以外的所有类(静态)变量、一个指向ClassLoader的指针、一个指向Class对象的指针、常量池(常量数据以及对其他类型的符号引用)
注意:
jdk8中永久区被移除了,取而代之的是元数据区。不同的是元数据区使用的是本地内存,与永久区不同,在不指定大小的情况下,虚拟机会耗尽所有可用的系统内存。
3.类加载子系统
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最 终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。
3.1 加载阶段(loading)
在加载阶段,Java虚拟机需要完成以下三件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
加载阶段既可以使用Java虚拟机里内置的引导类加载器来完成,也可以由用户自定义的类加载器去完成,通过定义自己的类加载器去控制字节流的获取方式(重写一个类加载器的findClass()或loadClass()方法),实现根据自己的想法来赋予应用 程序获取运行代码的动态性。
对于数组类而言,情况就有所不同,数组类本身不通过类加载器创建,它是由Java虚拟机直接在内存中动态构造出来的。
3.2 连接阶段(Linking)
3.2.1 验证
验证阶段是确定Class文件的字节流中包含的信息符合全部约束要求
1.文件格式验证:第一阶段要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理
2.元数据验证:第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java语言规范》的要 求
3.字节码验证:第三阶段主要目的是通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的
4.符号引用验证:最后一阶段可以看作是对类自身以外(常量池中的各种符号 引用)的各类信息进行匹配性校验,
3.2.2 准备
准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)在方法区分配内存并设置类变量初始值的阶段。
类变量的初始值为数据类型的0或null
常量的初始值为赋的值
3.2.3 解析
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程
1.类或接口的解析
2.字段解析
3.方法解析
4.接口方法解析
- 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何 形式的字面量,只要使用时能无歧义地定位到目标即可。
- 直接引用(Direct References):直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
3.3 初始化阶段
初始化阶段就是执行类构造器<clinit>()方法的过程。
- ()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。
- 保证在子类的()方法执行前,父类的()方法已经执行 完毕。
- 由于父类的()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值 操作
- 如果一个类中没有静态语句块,也没有对变量的 赋值操作,那么编译器可以不为这个类生成()方法
- 必须保证一个类的()方法在多线程环境中被正确地加锁同步
3.4 类加载器
比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提
3.4.1 双亲委派机制
对于JVM来说,存在两种不同的加载器:
1.启动类加载器(Bootstrap ClassLoader):只加载核心类库
2.继承自抽象类 java.lang.ClassLoader的类加载器。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,将请求委派给父类加载器去完成,直至最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
4.垃圾收集器(GC)
如果不及时对内存中的垃圾进行清理,那么,这些垃圾对象所占的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。甚至可能导致内存溢出。|
4.1 对象已死?
在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就 是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(“死去”即不可能再被任何途径使用的对 象了。
4.1.1 引用计数器
判断对象是否存活的算法是这样的:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不 能再被使用的。
优点:判定效率也很高,回收没有延迟;实现简单,垃圾对象便于辨识。
缺点:➢它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
➢每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。
➢引用计数器有一 个严重的问题,即无法处理循环引用的情况。这是一条致命缺陷,导致jvm并没有采用这个方法。
4.1.2 可达性分析算法(根搜索算法)
当前主流的商用程序语言都是通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。
算法的基本思路:
- 通过一系列称为“GC Roots”的根对象作为起始节点集,根据引用关系搜索被根对象集合所连接的目标对象是否可达。
- 使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Reference Chain)
- 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。
- ➢在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。
4.2 垃圾收集算法
4.2.1分代收集算法
部分收集(Partial GC):指目标不是完整收集整个Java堆的垃圾收集,其中又分为:
■新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。 这种情况复制算法的回收整理,速度是最快的。
■老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。
■混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收 集器会有这种行为。 ·
整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。
4.2.2 标记-清除算法
标记一清除算法( Mark-Sweep )是一-种非常基础和常见的垃圾收集算法。
执行过程:
当堆中的有效内存空间(available memory) 被耗尽的时候,就会停止整个程序(也被称为stop the world),然后进行两项工作,第一项则是标记, 第二项则是清除。
●标记: Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
●清除: Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收。
缺点:效率不高;内存空间的碎片化问题;进行GC时,用户线程需要停止工作
4.2.3 标记-复制算法
优点:
1. 没有标记和清除过程,实现简单,运行高效
2.复制过去以后保证空间的连续性,不会出现“碎片”问题。
缺点:
1.此算法的缺点也是很明显的,就是需要两倍的内存空间。
2.如果内存中多数对象都是存 活的,这种算法将会产生大量的内存间复制的开销
4.2.4 标记-整理算法
优点:
1.消除了标记-清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可。
2.消除了复制算法当中,内存减半的高额代价。
缺点:
1.从效率上来说,标记-整理算法要低于复制算法。
2.移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址。
3.移动过程中,需要全程暂停用户应用程序。即: STW
4.3.5 G1收集器
Garbage First(简称G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。
虽然G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的 Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的 旧对象都能获取很好的收集效果。
G1收集器的运作过程大致可划分为以下四个步骤:
初始标记(Initial Marking):
仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
并发标记(Concurrent Marking):
从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
最终标记(Final Marking):
对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下来的最后那少量的SATB记录。
筛选回收(Live Data Counting and Evacuation):
负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行 完成的。
优势:
并行与并发;
分代收集(新生代和老年代区分不明显);
空间整合;(复制算法、标记-压缩算法)
限制收集范围,可预测的停顿。
什么时候需要调优
- 非计算密集型任务cpu占用过高
- 老年代已使用空间大于70%
- Full GC频繁
- 单次GC时间大于1秒
- 出现OOM
- 程序的响应速度明显变慢
情况
- 非计算密集型任务cpu占用过高:有用户线程cpu过高、gc线程cpu过高。用户线程cpu过高一般是出现了死循环,需要查看线程堆栈、结合arthas的watch命令等找出问题根源。gc线程过高一般是Full GC频繁,Full GC频繁一般是因为老年代或方法区(元空间)内存不足。需要对gc日志分析、对堆转储文件分析,确定是哪些对象实例占用了过多内存、反射的类是否过多、被多个类加载器加载的类是否过多等,决定是增加内存大小还是对源码进行处理等。
- 老年代已使用空间大于70%:有可能发生了内存泄漏、有可能是设置的堆大小无法满足正常业务的需求。需要对堆转储文件分析,确定是哪些对象实例占用了过多内存,并检查是否发生了内存泄漏。对堆大小无法满足正常业务的需求,应调大堆大小,通过-Xms和-Xmx来设置。
- Full GC频繁:Full GC频繁一般是因为老年代或方法区(元空间)内存不足。需要对gc日志分析、对堆转储文件分析,确定是哪些对象实例占用了过多内存、反射的类是否过多、被多个类加载器加载的类是否过多等,决定是增加内存大小还是对源码进行处理等。
- 单次GC时间大于1秒:
- 老年代内存过大。老年代中累积了大量对象时才触发full gc,这次gc的任务很重,耗费的时间很长。通过测试,选择一个合适的老年代大小。
- 年轻代过小。年轻代过小,当年轻代空间不足时对象就会分配到老年代,而这些对象可能是“短命”的,但会停留在老年代很久。当full gc时,gc的任务会很重。因此,可以增大年轻代的大小。通过增大-XX:MaxTenuringThreshold=n来设置对象的晋升到老年代的年龄门槛,减缓对象进入老年代。
- 创建对象的速度过快。对创建对象过快的代码进行优化,防止短时间内大量创建对象,以致年轻代空间不足而提前进入老年代。
- gc线程数过少。gc线程数过少,无法充分利用多核cpu的并行处理。gc日志中 user、sys、real时间需要关注。若real时间 与 (user时间/gc线程数+sys)相差不远,则gc线程数合适。
- 进程被swap出内存。当物理内存不足时,系统会将一些内存存于磁盘的swap区,而swap区的访问速度慢很多。检查进程是否被放到swap区了,若是,应该增大物理内存,减少其他进程对内存的占用。
- 合适的垃圾收集器。若非jvm专家,建议使用G1。通过-XX:MaxGCPauseMillis来设置最大停顿时间。
- 出现OOM:老年代或方法区(元空间)内存不足。开启-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<path>。利用mat(memory analizer tool)对堆转储文件进行分析,确定是哪些对象实例占用了过多内存、反射的类是否过多、被多个类加载器加载的类是否过多等,决定是增加内存大小还是对源码进行处理等。
- 程序的响应速度明显变慢:检查是否存在上面的四种情况或可能出现了死锁。
调优工具
jps
:可查看java进程的id、主类名、启动参数等。jstat
:可查看堆的情况、gc的统计数据等。
例如
- jstat -gc 1000 10 # 每隔1000ms输出一次gc和堆情况,共输出10次
- jstat -gcutil 1000 10 # 每隔1000ms输出一次gc和堆的已使用百分比,共输出10次。utilization 利用率
- jstat -gcnewcapacity 1000 10 # 输出gc和年轻代的情况
- 其他用法请自行百度搜索jstat详细用法
jmap
:可用于查看堆整体情况、对象的histogram图、类加载器统计信息、finalizer的信息、dump出堆转储文件。
jmap -dump:live,format=b,file=heap.hprof
- jhat:对堆转储文件进行分析,可以查看所有类、所有类的实例数量、GC Roots能到达的对象、堆实例histogram图、支持OQL(Object Query Language)。
- jstack:查看某个java进程的所有的线程的堆栈信息、每个线程的锁信息。能发现死锁。
- mat(memory analizer tool):对堆转储文件进行分析,可以查看堆实例histogram图、每个对象到GC Roots的链、内存泄漏分析等。内存泄露分析很强大。推荐使用。
- arthas:阿里开源的java诊断程序,功能丰富强大,能方便的监控某个方法、发现死锁、热更新代码等。推荐使用。
- jvisualvm:可查看启动参数、系统属性、cpu的情况、加载、卸载的类的情况、线程数量、堆空间情况、元空间的情况、动态采用堆对象的histogram图。可以下载其他插件来丰富功能,如visualgc插件运行动态可视化观察堆的各个区域的变化。
- jprofiler:收费软件。优秀的java性能分析器。能查看cpu、堆、线程、类等的整体情况。能筛选出增量的堆对象,并对增量的对象进行分析,是jprofiler的一大特色。能动态观察monitor和锁的情况。能观察实时堆对象、能创建堆快照并进行分析。还有很多功能,十分强大。
参数
-XX被称为不稳定参数,对该类型参数的设置对jvm的性能有较大影响。
-XX语法规则
布尔值
-XX:+ 设置该参数值为true
-XX:- 设置该参数值为false
数字、字符串值
-XX:<param-name>=<value>
一、windows环境调整JVM
找到本地环境JDK javahome的配置地址 比如 C盘下jdk目录下的jvm.cfg文件
C:\Program Files\Java\jdk1.7.0_67\jre\lib\amd64\jvm.cfg
用文本编辑器打开,假如我们需要把虚拟机内存调整至 最小2G最大4G,那么我们只需要在空白处写入
-Xms2048m
-Xmx4096m
-XX:PermSize=1024M
-XX:MaxPermSize=2048M
保存即可
调整 tomcat内存大小 ,也调整为 最小2G最大4G
打开Tomcat根目录下的bin文件夹,编辑catalina.bat,将其中的%CATALINA_OPTS%(共有四处)替换为:
-Xms2048m -Xmx4096m -XX:PermSize=1024M -XX:MaxPermSize=2048M
二、Linux下修改JVM内存大小:
找到本地环境JDK javahome的配置地址jdk目录下的jvm.cfg文件
用文本编辑器打开,假如我们需要把虚拟机内存调整至 最小2G最大4G,那么我们只需要在空白处写入
-Xms2048m
-Xmx4096m
-XX:PermSize=1024M
-XX:MaxPermSize=2048M
保存即可
调整 tomcat内存大小 ,也调整为 最小2G最大4G
要添加在tomcat 的bin 下catalina.sh 里,位置cygwin=false前,添加以下设置
-Xms2048m -Xmx4096m -XX:PermSize=1024M -XX:MaxPermSize=2048M