JVM深度优化之HotSpot虚拟机
JVM(Java Virual Machine)处于jdk中最底层,屏蔽OS,提供完整的Java运行环境(虚拟计算机),操作系统装入JVM通过Java.exe完成。
首先,在谈java虚拟机之前,先了解一下jdk和jre的区别
- jre:Java运行环境,即使没有jdk也可以独立运行.class文件
- jdk:运行调试(提供工具包)
下面进入正题
1.首先了解一下JVM的内存结构
方法区:
又称为永久代、或者非堆----->存储虚拟机加载的类信息,存放常量,字段、方法、接口等描述信息,运行时的常量池也在方法区中产生。
栈: 描述额度是虚拟机的方法执行的内存模型,记录当前java执行方法的内存模型
- 栈帧结构:JVM中每一个方法的执行都会创建一个栈帧,每一个方法从调用到方法的结束(包含异常退出),都对应着一个栈帧从入栈到出栈的过程。
- 每一个线程都会创建一个栈帧,所以他是线程安全的(生命周期与线程相同,是线程私有的)。
- 局部变量表:存放八种基本类型和引用类型,局部变量表是在编译的时候就分配了空间大小,不可改变。
堆:Java堆,GC堆(垃圾回收堆),JVM中管理内存最大的一块区域,是线程共享的,所谓的JVM优化主要就是针对永久代和JAVA堆进行优化
主要对堆进行优化的原因是因为JAVA的垃圾回收机制,当然这里只针对HotSpot版本的虚拟机
- GC:垃圾回收 导致JVM暂时停止对外服务,直至整个垃圾回收结束,如果程序够大甚至可能是整个服务器处于挂起状态,GC的垃圾回收又虚拟机决定,不定时的进行垃圾回收
- Full GC:属于全局垃圾回收,一次性清理全部的引用,此种垃圾回收占用时间最长,可能会导致服务器挂起,这种垃圾回收的时机是当老生代(后面会提到)或者 永久代空间被沾满后,会自动进行Full GC垃圾回收,也可手动调用Runtime.gc()启动垃圾回收(不建议)。
JVM 的的垃圾回收机制可能会导致内存泄漏,所以我们对JVM的优化是尽量使垃圾回收的次数达到最少,理想状态下是不进行垃圾回收(当然只是理想状态,实际上,一定会进行垃圾回收)。
程序计数器:
在JVM中是内存最小的一块,它的作用是当前线程所执行的字节码的行号指示器,。JVM中字节码解释器根据程序计数器来选区下一条需要执行的代码。
JVM优化:
就如同上面说的,我们对JVM优化主要就是集中在对堆和方法区(永久代)的优化上。
JVM代的划分:
- 方法区:永久代(Permanent generation)
- 年轻代:(Young generation)存放在堆中
- 老年代:(Old generation):存放在堆中
首先:先说堆内存对于垃圾回收的划分:
- 堆中对代的划分分为二个部分:(年轻代又称新生代)、老年代
- 年轻代又划分为Eden(伊甸园)、和两个小内存区域:survivor1和survivor2
- 在初始化参数上Eden的大小和survivor1、survivor1比例是 8:1:1
那么当满足什么样的情况,JVM才会使用垃圾回收机制呢?
GC: 一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发GC
从上面的介绍其实你可以了解到,当JVM创建对象时,会将对象放置到Eden区。当新生对象,放置到Eden时会触发垃圾回收,Eden触发垃圾回收后,会将依旧存活的对象随机放置到survivor区,两块内存区域只会被使用一块,当进行多次垃圾回收后,依旧存活的对象会被放置到老年代的区域。
Full GC:执行的时机为:老年代(Old )被写满、永久代(Perm)被写满、System.gc()被显式调用
当对象过多存活,老年代空间不足时会触发Full GC 回收,当永久代空间不足时也会触发这个垃圾回收
了解了两种垃圾回收的机制之后,其实我们可以大致猜到,我们既然想要减少垃圾回收的次数,就一定要给他们足够的空间就可以减少垃圾回收的次数,那么如何来改变代的大小呢?
- 堆设置
- -Xms:初始堆大小
- -Xmx:最大堆大小
- -XX:NewSize=n:设置年轻代大小
- -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
- -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
- -XX:MaxPermSize=n:设置永久代大小
收集器设置
- -XX:+UseSerialGC:设置串行收集器
- -XX:+UseParallelGC:设置并行收集器
- -XX:+UseParalledlOldGC:设置并行年老代收集器
- -XX:+UseConcMarkSweepGC:设置并发收集器
- -XX:+UseG1GC 设置G1收集器
垃圾回收信息
- -verbose:gc
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintGCTimeStamps
- -Xloggc:filename、
并行收集器设置
- -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
- -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
- -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
- -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
- -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
我们以该如何使用呢?
在工具的如(Eclipse)根目录下的.ini加入一下配置
- -verbose:gc
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintGCDateStamps
- -Xloggc:D:\jvm.log-------------------------->自定义路径,打印日志的路径
更改配置为:
- -Xms512m 设置堆最小空间
- -Xmx512m 设置堆最大空间
- -XX:MaxPermSize=512m 设置永久代空间
启动Eclipse后查看日志,再次增大空间再次查看日志,会发现垃圾回收次数明显减少,如何查看,自行百度不进行赘述。
Tomcat JVM优化
Windows系统中修改环境变量
参数如下:
-verbose:gc -Xms1400m -Xmx1400m -XX:PermSize=400m
-XX:MaxPermSize=400m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:D:\jvm.log
Linux配置如下
Linux中在/etc/profile中加入
- Export -verbose:gc -Xms1400m -Xmx1400m -XX:PermSize=400m
- -XX:MaxPermSize=400m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:D:\jvm.log
或者直接修改环境变量
垃圾回收算法:
Mark-Sweep(标记-清除)
它是最基础的垃圾回收算法,其他算法都是基于这种思想。标记-清除算法分为“标记”,“清除”两个阶段:首先标记出需要回收的对象,标记完成后统一清除对象
缺点:
1:标记和清除的效率不高
2:标记之后会产生大量不连续的内存碎片
- Copying(复制)算法
它将可用内存分为两块,每次只用其中的一块,当这块内存用完以后,将还存活的对象复制到另一块上面,然后再把已经使用的内存空间一次清理掉
优点:
- 不会产生内存碎片
- 只要移动堆顶的指针,按顺序分配内存即可,实现简单,运行高效
缺点:
- 内存缩小为原来的一半
- Mark-Compact(标记-整理)算法
标记操作和”标记-清除“算法一样,后续操作变成不直接清理对象,而是在清理无用对象的时候完成让所有存活的对象都像一端移动,并更新对象的指针
优点:不会产生内存碎片
缺点:在“标记-清除”基础上还要进行对象的移动,成本相对较高
- Generational Collection(分代收集)算法(重点)
是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法
目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法
注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类
- 垃圾收集器
Parallel Scavenge(并行收集器)
Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,它主要是为了达到一个可控的吞吐量
Parallel Old(年老代并行收集器)
Parallel Old是Parallel Scavnge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法
CMS(并发收集器)
CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法
G1
G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型