概况: 本文总结一些JVM杂乱的知识点,以供参考
调用一个方法会启用(创建)一个栈帧
。
栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
每个方法在执行的时候会创建一个栈帧(Stack Fram)
java栈是一块线程私有
的内存空间,一个栈,一般由三部分组成:局部变量表,操作数栈和帧数据区
选项 | 描述 |
---|---|
局部变量表 | 用于报错函数的参数 及局部变量 |
操作数据栈 | 主要保存计算过程的中间结果 ,同时作为计算过程中变量临时的存储空间 |
帧数据区 | 除了局部变量表和操作数栈以外,栈还需要一些数据来支持常量池的解析 ,这里帧数据保存着访问常量池的指针 ,方便程序访问常量池,另外,当函数返回或者出现异常时,虚拟机必须有一个异常处理表 ,方便发送异常的时候找到异常的代码。因此,异常处理表 也是帧数据区 的一部分 |
线程私有
,是属于底层,调用C语言、汇编等, 无法做调优。
内存最大的一块,一般也是针对这块进行调优
堆描述 |
---|
几乎所有的对象都存放在堆中,且堆是自动化管理 ,通过垃圾回收机制,垃圾对象会自动清理,不需要手动释放 |
根据垃圾回收机制不同,Java堆有可能有不同的结构,最为常见的就是将整个java堆分为新生代 和老年代 。 其中新生代存放新生的对象或者年龄不大的对象,老年代则存放老年对象 |
新生代分为eden区,s0区,s1区,s0和s1也被称为 from 和 to 区域。他们两块是大小相等,并且可以互换角色 的空间 |
补充: s0 和 s1 采用复制(copy)垃圾回收算法。
绝大多数情况下,对象首先分配在eden区,在一次新生代回收后,如果对象还存活,则会进入s0
或者s1区,之后每经过一次新生代回收,如果对象存活则它的年龄就+1. 默认达到15则进入老年代。
所有线程共享 ,用于存放已被虚拟机加载的类信息
、常量
、静态变量
、即时编译器编译后的代码
等数据
大小决定了系统可以保存多少个类。 方法区可以理解为永久区(perm)
JDK1.6/1.
7可以理解为permgen space (永久驻留区),里面还包括一些运行时的常量池信息,
字符串字面值. JDK1.8
开始已经没有这个概念,1.8称为元空间
,其大小只受物理内存限制
NIO
库允许
Java程序使用直接内存。速度
会优于
Java堆。大小不会受限于Xmx指定的最大堆大小
,受限于操作系统能给出的最大内存
名称 | 描述 |
---|---|
堆 | 解决的是数据存储的问题,即数据怎么放,放在哪儿 |
栈 | 解决程序运行问题,即程序如何执行,或者如何处理数据 |
方法区 | 是辅助堆栈的一块永久区(Perm),解决堆栈信息的产生,是先决条件 |
直接内存,一般不用设置。JVM自动优化。
有一个引用,计数就添加1, 但是: 会有循环引用问题
从roots对象(GC Roots
根对象)计算可达的对象。
copy效率还算高,但是当对象存活率较高时要进行较多复制操作,效率也会变低。
- 标准参数,所有JVM都支持
-X 非标准,每个JVM实现不尽相同
-XX 不稳定,下个版本可能取消
-XX : 属于系统级别(JVM)的配置。例如 GC日志信息,使用垃圾回收器
非 -XX 基本属于应用层面的配置。如分配堆、栈的大小等。
+ 启用
- 禁用
#堆设置
-Xms:初始堆大小 ,设置java启动时初始堆大小
-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:+PrintGC 使用这个参数,虚拟机启动后,只要遇到GC就会打印日志
-XX:+PrintGCDetails 可以查看信息信息,包括各个区的情况
-XX:+PrintGCTimeStamps
-Xloggc:filename
-XX:+PrintCommandLineFlags:可以将隐式或者显示传给虚拟机的参数输出。
通过实验练习,不必死记硬背,重在理解,使用时查询即可
更加全面的参数介绍可查看官网
不同的垃圾收集器可以采用不同的垃圾回收算法
是一个单线程
的收集器,但是不能利用多处理器,吞吐量也不是很高(up to approximately 100M)
指定参数:
-XX:+UseSerialGC
垃圾收集效率较高
,吞吐量也高
。处理线程进程较多
。
并发量大
使用时指定参数 -XX:+UseconcMarkSweepGC
优点: 停顿时间短
参数指定: -XX:+UseG1GC
停顿时间短,但是并发量也大。java9推荐使用
无监控不调优,根据监控来选择, 根据业务场景来进行选择。
虚拟机自动调整分配
无需调整优化,使用JVM默认即可。
方法结束,则栈帧消失,所以方法中new出来的对象,在其他地方有引用(逃逸了),就不能在栈上分配。
逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸
。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸
逃逸有两种情况:
情况 | 描述 |
---|---|
方法逃逸 | 当一个对象在方法中定义之后,作为参数传递到其它方法中 |
线程逃逸 | 如类变量或实例变量,可能被其它线程访问到 |
线程同步本身比较耗,如果确定一个对象不会逃逸出线程,无法被其它线程访问到,那该对象的读写就不会存在竞争,则可以消除对该对象的同步锁,通过
-XX:+EliminateLocks
可以开启同步消除
通过-XX:+EliminateAllocations
可以开启标量替换,-XX:+PrintEliminateAllocations
查看标量替换情况。
如果java虚拟机开了栈上分配
,则首先会进行栈上分配,如果栈上分配不了,则进入线程本地分配,
然后查看自己是否是一个对象,是否去老年代分配,如果不是特别大,则去eden区。
如果一个对象特别小,且开了栈上分配的优化,默认是开着的。
好处,放在栈上(栈帧),方法结束或者线程结束,则自动回收,都不用使用垃圾回收。
栈空间分配满了,则去找线程本地分配
;每一个线程执行的时候
都会给自己分配一部分专用内存
即线程本地内存。在eden中申请,默认 1%。
为什么会申请一个线程本地的区域呢?如果所有的线程new出来的对象都放在eden区,则eden则必须要加锁
。
如果每个线程都一个自己独立的区域
(同时也算大),则eden就不用再加锁。这样就提高对象分配效率。
特征:
1. 占用eden,默认1%
2. 多线程时不用竞争eden即可申请空间,提高效率
3. 小对象
4. 无需调整优化,使用JVM默认即可。
-XX:+DoEscapeAnalysis 开启逃逸分析,
-XX:-DoEscapeAnalysis 关闭逃逸分析,对象就不能分配在栈上默认情况下是开启逃逸分析的
-XX:+EliminateAllocations可以开启标量替换,
-XX:-EliminateAllocations 关闭
-XX:-UseTLAB 关闭线程本地内存
-XX:+UseTLAB 开启线程本地内存
不是栈上分配,不使用线程本地分配,直接在eden分配。
(一)源码
-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB -XX:+PrintGC
public class Test1 {
public static class User{
private int i ;
private String name;
public User(int i, String name){
this.i = i;
this.name = name;
}
}
public static void alloc(int i){
new User(i,"name"+i);
}
public static void main(String [] args){
long b = System.currentTimeMillis();
for(int i=0;i<1000000000;i++){
alloc(i);
}
long e = System.currentTimeMillis();
System.out.println("消耗时间为:" + (e - b));
}
}
(三)GC打印分析
[GC (Allocation Failure) 33279K->624K(125952K), 0.0034251 secs]
[GC (Allocation Failure) 33904K->632K(125952K), 0.0009171 secs]
[GC (Allocation Failure) 33912K->640K(125952K), 0.0006934 secs]
[GC (Allocation Failure) 33920K->648K(159232K), 0.0008063 secs]
[GC (Allocation Failure) 67208K->648K(159232K), 0.0018330 secs]
[GC (Allocation Failure) 67208K->632K(221696K), 0.0007944 secs]
[GC (Allocation Failure) 133752K->616K(221696K), 0.0012878 secs]
[GC (Allocation Failure) 133736K->648K(354816K), 0.0005041 secs]
[GC (Allocation Failure) 266888K->616K(354816K), 0.0013638 secs]
消耗时间为:733
描述:GC 第一次从 33279K –> 624K。
使用线程本地分配
(一)参数
-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+UseTLAB -XX:+PrintGC
(二)设置并运行
(三)运行结果分析
使用以后耗时变短。使用本地线程分配时,不用在eden分配对象时进行加锁,提高分配效率。
使用栈上分配
(一)参数
-XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+UseTLAB -XX:+PrintGC
(三)结果分析
使用栈分配后耗时明显变短
打印GC的细节
-XX:+PrintGCDetails
(一)源码
public class Test02 {
public static void main(String[] args) {
byte[] b= new byte[1024];
}
}
(二)参数
-XX:-DoEscapeAnalysis -XX:+EliminateAllocations -XX:+PrintGCDetails
(四)结果分析
Heap
PSYoungGen total 38400K, used 2662K [0x00000000d5e00000, 0x00000000d8880000, 0x0000000100000000)
eden space 33280K, 8% used [0x00000000d5e00000,0x00000000d6099b20,0x00000000d7e80000)
from space 5120K, 0% used [0x00000000d8380000,0x00000000d8380000,0x00000000d8880000)
to space 5120K, 0% used [0x00000000d7e80000,0x00000000d7e80000,0x00000000d8380000)
ParOldGen total 87552K, used 0K [0x0000000081a00000, 0x0000000086f80000, 0x00000000d5e00000)
object space 87552K, 0% used [0x0000000081a00000,0x0000000081a00000,0x0000000086f80000)
Metaspace used 2591K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 288K, capacity 386K, committed 512K, reserved 1048576K
不使用线程本地缓存分配
(一)参数
-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB -XX:+PrintGCDetails
(二)运行过程
使用Runtime 等java API大致估算使用内存情况
(一)源码
public class Test3 {
public static void main(String[] args) {
printMemoryInfo();
byte[] b = new byte[1024*1024];
System.out.println("---------");
printMemoryInfo();
}
public static void printMemoryInfo() {
System.out.println("total:" + Runtime.getRuntime().totalMemory());
System.out.println("free:" + Runtime.getRuntime().freeMemory());
}
}
(二)打印情况
total:128974848
free:126930104
---------
total:128974848
free:125881512
(三)分析结果
其中free部分内存差不多使用了1M(1024*1024)
内存溢出
-Xms10 程序起始的时候为其分配多少内存
-Xmx10M 最大内存分配多少
一般起始值要小于最大值。如果是调优则可设置相等或接近。
(一)参数
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/Test04.dump -Xms10M -Xmx10M -XX:+PrintGCDetails
(二)工具
使用visualVM 打开Test04.dump 这个文件。使用VM coredumps功能进行分析
下载安装这个软件. 地址
注意:在JDK中的bin
目录下也有一个jvisualvm.exe
步骤:
【File】
-> 【load】
可以参考官网visualVM文档
线程栈大小
(一)源码
package com.jvm;
/**
* 递归调用,没有让递归结束。每个方法调用都会创建一个栈帧。
* 所以栈空间一定会溢出
*
*/
public class Test5 {
static int count = 0;
static void r() {
count ++ ;
r();
}
public static void main(String[] args) {
try {
r();
} catch (Throwable t) {
System.out.println(count); //stackOverFlow
}
}
}
(二)JVM 参数调整
-Xss128k
通过调整不同Xss 大小。来设置递归的次数。
(一)参数
-Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags
-XX:+PrintCommandLineFlags 会将JVM输入的参数打印出来。
(二)源码
public class Test01 {
public static void main(String[] args) {
//-Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags
//查看GC信息
System.out.println("max memory:" + Runtime.getRuntime().maxMemory());
System.out.println("free memory:" + Runtime.getRuntime().freeMemory());
System.out.println("total memory:" + Runtime.getRuntime().totalMemory());
byte[] b1 = new byte[1*1024*1024];
System.out.println("分配了1M");
System.out.println("max memory:" + Runtime.getRuntime().maxMemory());
System.out.println("free memory:" + Runtime.getRuntime().freeMemory());
System.out.println("total memory:" + Runtime.getRuntime().totalMemory());
byte[] b2 = new byte[4*1024*1024];
System.out.println("分配了4M");
System.out.println("max memory:" + Runtime.getRuntime().maxMemory());
System.out.println("free memory:" + Runtime.getRuntime().freeMemory());
System.out.println("total memory:" + Runtime.getRuntime().totalMemory());
}
}
(三)结果输出
-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC
max memory:20316160
free memory:5312688
total memory:6094848
[GC (Allocation Failure) [DefNew: 763K->192K(1856K), 0.0018667 secs] 763K->528K(5952K), 0.0019340 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
分配了1M
max memory:20316160
free memory:4471136
total memory:6094848
[GC (Allocation Failure) [DefNew: 1249K->0K(1856K), 0.0017924 secs][Tenured: 1552K->1552K(4096K), 0.0027280 secs] 1585K->1552K(5952K), [Metaspace: 2587K->2587K(1056768K)], 0.0046291 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
分配了4M
max memory:20316160
free memory:4539496
total memory:10358784
Heap
def new generation total 1920K, used 69K [0x00000000fec00000, 0x00000000fee10000, 0x00000000ff2a0000)
eden space 1728K, 4% used [0x00000000fec00000, 0x00000000fec115b8, 0x00000000fedb0000)
from space 192K, 0% used [0x00000000fedb0000, 0x00000000fedb0000, 0x00000000fede0000)
to space 192K, 0% used [0x00000000fede0000, 0x00000000fede0000, 0x00000000fee10000)
tenured generation total 8196K, used 5648K [0x00000000ff2a0000, 0x00000000ffaa1000, 0x0000000100000000)
the space 8196K, 68% used [0x00000000ff2a0000, 0x00000000ff824190, 0x00000000ff824200, 0x00000000ffaa1000)
Metaspace used 2594K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 288K, capacity 386K, committed 512K, reserved 1048576K
(四)分析
总结:
将初始堆的大小
和最大堆的大小
设置相等。垃圾回收次数
,从而提高性能。解释: 当分配给初始值比较小时,当内存不够且没有达到最大值时,会不断的申请内存。
新生代的配置:-Xmn: 可以设置新生代的大小,设置一个比较大的新生代会减少老年代,这个参数对系统性能以及GC行为有很多影响,新生代大小一般会设置整个堆空间的 1/3 到 1/4左右。
-XX:SurvivorRatio:
用来设置新生代中eden
空间和from/to
空间的比例。
含义:-XX:SurvivorRation = eden/from = eden/to.
(from = to 大小一致)
(一)源码
public class Test02 {
public static void main(String[] args) {
//第一次配置
//-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
//第二次配置
//-Xms20m -Xmx20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
//第三次配置
//-XX:NewRatio=老年代/新生代
//-Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
byte[] b = null;
//连续向系统申请10MB空间
for(int i = 0 ; i <10; i ++){
b = new byte[1*1024*1024];
}
}
}
(二)分析
老年代设置占堆比例较大是应该的,很多大对象,以及其他从新生代跑过来的对象在此,做一个应用程序的支撑。如果新生代设置太小,则内存不够以后,会转向老年代
老年代产生GC, 引起Full GC,这样会影响性能。
(三) 小节
6. 总结
描述 |
---|
不同的堆分布情况,对系统执行会产生一定的影响,在实际工作中,应该根据系统的特点做出合理的配置,基本策略:尽可能将对象预留在新生代,减少老年代的GC次数 |
除了可以设置新生代的绝对大小(-Xmn),还可以使用(-XX:NewRatio)来设置新生代和老年代的比例:-XX:NewRation = 老年代/新生 |
java虚拟机提供了参数
-Xss
来指定线程的最大栈空间,整个参数也直接决定了函数可调用的最大深度
(一) 参数
-Xss1m
(二) 源码
public class Test04 {
//栈调用深度
private static int count;
public static void recursion(){
count++;
recursion();
}
public static void main(String[] args){
try {
recursion();
} catch (Throwable t) {
System.out.println("调用最大深入:" + count);
t.printStackTrace();
}
}
}
在java程序的运行过程中,如果堆空间不足,则会抛出内存溢出的错误(Out of Memory) OOM一旦发生在生产环境上,可能引起业务中断。java虚拟机提供了
-XX:+HeapDumpOnOutOfMemoryError
使用该参数可以在内存溢出时导出整个堆信息,与之配合使用的还有参数-XX:HeapDumpPath
,可以设置导出堆的存放路径
内存分析工具: Memory Analyzer 1.5.0 (是Eclipse中的插件,与Eclipse集成)
地址:http://download.eclipse.org/mat/1.5/update-site/
(一)参数
-Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/Test03.dump
(二)源码
public class Test03 {
public static void main(String[] args) {
//-Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/Test03.dump
//堆内存溢出
Vector v = new Vector();
for(int i=0; i < 5; i ++){
v.add(new Byte[1*1024*1024]);
}
}
}
(三)结果
java.lang.OutOfMemoryError: Java heap space
Dumping heap to d:/Test03.dump ...
Heap dump file created [1215404 bytes in 0.017 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.base.Test03.main(Test03.java:10)
CATALINA_OPTS="
-server
-Xms4g
-Xmx4g
-Xss512k
-XX:+AggressiveOpts //表示虚拟机能用上的优化全启用。
-XX:+UseBiasedLocking //锁优化
-XX:PermSize=64M //永久区。1.8取消了
-XX:MaxPermSize=256M //最大永久区大小
-XX:+DisableExplicitGC //在 程序代码中不允许有显示的调用“System.gc()”。每次在到操作结束时手动调用 System.gc() 一下,付出的代价就是系统响应时间严重降低,就和关于 Xms,Xmx 里的解释的原理一样,这样去调用 GC 导致系统的 JVM 大起大落。
-XX:+UseConcMarkSweepGC:使用CMS缩短响应时间,并发收集,低停顿
-XX:+UseParNewGC:并行收集新生代的垃圾
-XX:+CMSParallelRemarkEnabled:在使用 UseParNewGC 的情况下,尽量减少 mark 的时间。
-XX:+UseCMSCompactAtFullCollection:在使用 concurrent gc 的情况下,开启对老年代的压缩,使碎片减少
-XX:LargePageSizeInBytes:指定 Java heap 的分页页面大小,内存页的大小不可设置过大, 会影响 Perm 的大小。
-XX:+UseFastAccessorMethods:使用 get,set 方法转成本地代码,原始类型的快速优化。
-Djava.awt.headless=true:在linux/unix 环境下经常会碰到一个 exception 导致你在 winodws 开发环境下图片显示的好好可是在 linux/unix 下却显示不出来,因此加上这个参数以免避这样的情况出现。
垃圾回收系统
是jvm的重要组成部分,java堆
则是垃圾回收器的重点工作区域
,最核心的组件,负责执行虚拟机的字节码
注意:JDK1.8 永久代被修改为元空间(Metaspace)
了。
可以阅读相关资料:
1. HotSpot Virtual Machine Garbage Collection Tuning Guide Release 8
2. Java虚拟机-JVM各种参数配置大全详细
3. 基于JVM(内存) 和Tomcat性能调优
4. Tomcat7优化前及优化后的性能对比
5. Tomcat 调优及 JVM 参数优化
6. JVM参数;
7. JDK 官网
8. 马士兵JVM性能调优公开课
9. 编译器中的 逃逸分析
10. JVM内存分配
11. The Structure of the Java Virtual Machine
(一)理解概念,不必死记硬背,动手实验即可。 本文的内容正确性需要考究,实验出真知;
(二) 本文列举的案例,层次不够好,同时没有做很详细的分析;
(三)不同JDK版本、环境运行结果不尽相同;