1.前言
①.在java8中,永久代已经被移除了,被一个称为元空间的区域所取代,元空间的本质和永久代类似;
②.元空间和永久代之间最大的区别在于:
永久代使用的是jvm的堆内存,但是java8以后的元空间并不在虚拟机中,而是使用本机物理内存;
③.默认情况下,元空间的大小仅受本地(物理)内存的限制;类的元数据放入native memory(本地内存),字符串池和类的静态变量放入jvm堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而是由系统的实际可用空间来控制;
2.JVM参数类型
2.1.标配参数
①.-version
②.-help
③.java -showversion
2.2.X
参数
①.-Xint //解释执行
②.-Xcomp //第一次使用就编译成本地代码
③.-Xmixed //混合模式
2.3.XX
参数
2.3.1.Boolean类型
1>.语法:
-XX:+[或者-]某个属性值
///其中属性前面的"+“表示开启,”-"表示关闭
例如: IDEA中配置是否打印GC收集细节的功能:
IDEA->edits configurations->选择对应的application->在"vm options"选项中添加配置"
-XX:+PrintGCDetails
"->运行程序,观察控制台
例如: IDEA中配置是否使用串行垃圾回收器的功能:
IDEA->edits configurations->选择对应的application->在"vm options"选项中添加配置"
-XX:+UseSerialGC
"->运行程序,观察控制台
扩展:jvm中的一些常用命令
①.jps -l //查看正在运行的java程序(进程)
②.jinfo -flag 属性 进程id //查看某个进程id对应的程序是否开启了某个属性/功能
③.jinfo -flags 进程id //查看某个程序所有的配置信息
例如
2.3.2.KV设值类型
1>.语法
-XX:属性key=属性值value
例如:在IDEA中配置MetaspaceSize最大元空间大小
IDEA->edits configurations->选择对应的application->在"vm options"选项中添加配置"
-XX:MetaspaceSize=1024m
"->运行程序,观察控制台
2>.查看某个属性的默认值
①.运行java程序,并且保持一直在运行;
②.命令使用"jps -l
“命令查看java程序进程;
③.命令行使用”jinfo -flag 属性名称 进程id
"查看某个属性的默认值;
- 如图:
3.盘点家底
3.1.-XX:+PrintFlagsInitial
1>.查看jvm初始默认值大小
2>.语法:
java -XX:+PrintFlagsInitial -version
或者
java -XX:+PrintFlagsInitial
3>.例如:
注意: 如果某个属性值前面是":="符号,那就表示该属性值被人为/jvm修改过了,否则就是默认初始值!
3.2.-XX:+PrintFlagsFinal
1>.主要查看jvm修改更新
2>.语法:
java -XX:+PrintFlagsFinal -version
或者
java -XX:+PrintFlagsFinal
3>.例如:
注意:如果某个属性值前面是":="符号,那就表示该属性值被人为/jvm修改过了,否则就是默认初始值!
4>.例如:运行java命令的同时打印出参数
java -XX:+PrintFlagsFinal -XX:MetaspaceSize=512m 编译后class文件名称
3.3.-XX:+PrintCommandLineFlags
1>.打印命令行参数
2>.语法:
java -XX:+PrintCommandLineFlags -version
或者
java -XX:+PrintCommandLineFlags
3>.例如:
4.常用的JVM基本配置参数
4.1.代码中获取初始/默认的堆内存大小
public class JVMTest {
public static void main(String[] args) {
//java虚拟机的内存总量/初始大小,默认占用物理内存的1/64
long totalMemory = Runtime.getRuntime().totalMemory();
//java虚拟机试图使用的最大内存量,默认占用物理内存的1/4
long maxMemory = Runtime.getRuntime().maxMemory();
System.out.println("total_memory(-Xms) = " + (totalMemory / (double) 1024 / 1024) + "MB");
System.out.println("max_memory(-Xmx) = " + (maxMemory / (double) 1024 / 1024) + "MB");
}
}
4.2.常用参数说明
4.2.1.-Xms
1>.JVM初始化堆内存大小,默认为物理内存的1/64,
等价于"-XX:InitialHeapSize"
注意:正式环境中必须设置与{-Xmx}参数值相同,避免GC和应用程序争抢内存,理论值的峰值和峰谷忽高忽低导致程序产生停顿,也可以避免每次垃圾回收完成后JVM重新分配内存
4.2.2.-Xmx
1>.JVM初始化最大堆内存大小,默认为物理内存的1/4,
等价于"-XX:MaxHeapSize"
4.2.3.-Xss
1>.设置单个线程(运行时)栈的大小,一般默认值为512K~1024K,
不同的平台/系统他的值可能会不一样,等价于"-XX:ThreadStackSize"
4.2.4.-Xmn
1>.堆内存中年轻代/新生代大小,一般使用默认即可,默认占用整个堆内存的1/3,
老年代占用2/3,新生代中又分为:Eden,S0,S1区,分别占用新生代的8/10,1/10,1/10;
4.2.5.永久区分配参数(针对jdk1.7版本),他们表示一个系统中可以容纳多少个类型
①.-XX:PermSize //设置永久区的初始空间大小
②.-XX:MaxPermSize //设置永久区的最大空间
4.2.6.设置元空间大小(针对jdk1.8)
-XX:MetaspaceSize
(jdk1.8)元空间的本质和(jdk1.7)永久代类似,都是对JVM规范中方法区的实现,不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存,因此默认情况下,元空间大小仅受本地内存限制;
4.2.7.常用配置案例
-Xms10m -Xmx10m -XX:MetaspaceSize=1024m -XX:+PrintFlagsFinal -XX:+PrintGCDetails
4.2.8.-XX:+PrintGCDetails
1>.输出详细GC收集日志信息
①.GC日志分析
②.FullGC日志分析(老年代满了,会触发Full GC,回收整个堆内存)
将日志以文件的方式输出到指定的位置
-Xloggc:log/gc.log
扩展:GC什么时候开始?
①.GC 经常发生的区域是堆区,堆区还可以细分为新生代、老年代,新生代还分为一个Eden区和两个Survivor区;
②.对象优先在Eden中分配,当Eden中没有足够空间时,虚拟机将发生一次 Minor GC,因为Java大多数对象都是朝生夕灭,所以Minor GC非常频繁,而且速度也很快;
③.Full GC发生在老年代的GC,当老年代没有足够的空间时即发生 Full GC,发生 Full GC 一般都会有一次 Minor GC;大对象直接进入老年代,如很长的字符串数组,虚拟机提供一个"XX:PretenureSizeThreadhold"参数,令大于这个参数值的对象直接在老年代中分配,避免在Eden区和两个Survivor区发生大量的内存拷贝;
④.发生 Minor GC时,虚拟机会检测之前每次晋升到老年代的对象的平均大小是否大于老年代的剩余空间大小,如果大于,则进行一次 Full GC,如果小于,则查看"HandlePromotionFailure"设置是否允许担保失败,如果允许,那只会进行一次Minor GC,如果不允许,则改为进行一次Full GC;
4.2.9.-XX:SurvivorRatio
1>.设置新生代中Eden区和2个survivor区的空间比例
-XX:SurvivorRatio=4
//即Eden:S0:S1=4:1:1,survivorRatio值就是设置Eden区的比例占多少,s0和s1相同
默认比例为: Eden:S0:S1=8:1:1 //一般使用默认即可!
4.2.10.-XX:NewRatio
1>.设置新生代(Eden+2s)和老年代在堆结构的占比
-XX:NewRatio=4
//新生代占1,老年代占4,年轻代占整个堆的1/5,NewRatio值就是设置老年代的占比,新生代为1
默认: -XX:NewRatio=2 //新生代占1,老年代占2,年轻代占整个堆的1/3;官方推荐,新生代占用堆的3/8!
4.2.11.-XX:MaxTenuringThreshold
1>.设置垃圾对象(不可达对象)最大年龄,即新生代中的对象经过了多次垃圾回收之后仍然存活,才能进入老年代;默认是15;
2>.该值如果设置为0的话,则年轻代对象不经过Survivor区直接进入老年代中,对于老年代比较多的应用,可以提高效率;如果将此值设置为一个较大值,则年轻代对象会在survivor区中进行多次复制,这样可以增加对象在年轻代的存活时间,增加对象在年轻代中就可被回收的概率,同时减少Full GC的次数;
3>.说明:
①.JVM为每个对象定义了一个对象年龄(Age)计数器,对象在Eden出生,当Eden区空间满的时候会触发第一次GC进行垃圾回收,把还活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区和From区,对这两个区域进行垃圾回收,经过这次回收后还存活的对象则被会直接复制到SurvivorTo区(如果有对象的年龄已经达到了老年的标准,则将对象复制到老年代中),同时将这些对象的年龄+1;
②.然后清空Eden区和survivorFrom区中的对象,也就是复制之后有交换,谁空谁是survivorTo区;
③.最后,survivorTo区和survivorFrom区进行互换,原来的survivorTo区就变成了下一次GC时的survivorFrom区,部分对象会在survivorFrom区和survivorTo区之间复制来复制去,如此交换15次(由jvm参数maxTenuringThreshold决定,默认值为15),最终如果还是存活,就存入到老年代中;
扩展:jvm动态对象年龄判定
①.虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄;
例如:
I).MaxTenuringThreshold为15;
II).年龄1的对象占用了33%;
III).年龄2的对象占用33%;
IV).年龄3的对象占用34%;
年龄1的占用了33%,年龄2的占用了33%,累加和超过默认的TargetSurvivorRatio(50%),年龄2和年龄3的对象都要晋升;
②.分析uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) { //survivor_capacity是survivor空间的大小 // -XX:TargetSurvivorRatio:目标存活率,默认为50% //①.通过这个比率来计算一个期望值,desired_survivor_size size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100); //②.用一个total计数器,累加每个年龄段对象大小的总和 size_t total = 0; uint age = 1; while (age < table_size) { total += sizes[age];//sizes数组是每个年龄段对象大小 //③.当total大于desired_survivor_size 停止 if (total > desired_survivor_size) break; age++; } //④.用当前age和MaxTenuringThreshold 对比找出最小值作为结果 uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold; ... }
//观察上面的代码可以发现,年龄从小到大进行累加,当加入某个年龄段后,累加之和超过了(survivor区域*TargetSurvivorRatio)的时候,就从这个年龄段往上的年龄的对象进行晋升;
由此可知,动态对象年龄判断,主要是被TargetSurvivorRatio这个参数来控制.而且算的是年龄从小到大的累加之和,而不是某个年龄段对象的大小;
4.2.12.-XX:+TraceClassLoading
1>.监控类的加载过程
4.2.13.-XX:+HeapDumpOnOutOfMemoryError -XX:+HeapDumpPath=f:/a.dump
1>.当程序出现OOM时,将自动生成dump文件到指定的目录
4.2.14.-XX:OnOutOfMemoryError
1>.当程序出现OOM时,执行指定的脚本文件
例如:
-XX:OnOutOfMemoryError=D:/toos/jdk1.7/bin/printstack.bat %p