一、虚拟机历史
三大主流虚拟机:Sun 的 HotSpot VM、BEA公司的JRockit VM 和 IBM公司的J9 VM
二、JVM内存结构
主要包括堆区,方法区(元数据空间),虚拟机栈(JAVA线程栈),本地方法栈,程序计数栈

- 堆:是虚拟机所管理的内存中最大的一块,主要是存放对象实例。参数设置:
-Xms1g -Xmx2g
堆= 新生代+老年代,比例是1:2, 新生代可以用-Xmn设置, 新生代=Eden+FromSurvior+ToSurvior,比例是8:1:1
分代是为了解决提升垃圾回收的速度
方法区:也叫永久区(永久代),用于存储类信息,常量、静态变量等数据,为了与Java堆区分开,也叫"非堆区"。 参数设置:
-XX:PermSize,-XX:MaxPermSize默认值是64MB元空间: 从JDK1.8开始,方法区变成元空间,直接占用本地内存,去掉方法区设置参数,新参数为
-XX:MaxMetaspaceSize默认值是21M,-XX:MetaspaceSize默认值-1,上限受限于宿主机内存
字符串存在永久代中,容易出现性能问题和内存溢出。
类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
永久代会为 GC 带来不必要的复杂度,并且回收效率偏低
直接内存:不是虚拟机运行时数据区的一部分。如果使用NIO,在java堆内可以用DirectByteBuffer对象直接引用并操作,直接使用堆外内存。受宿主机总内存限制,频繁使用也会抛OutOfMemoryError异常。参数设置:
-XX:MaxDirectMemorySize默认值与堆内存最大值一样运行时常量池:原本是方法区的一部分,用于存放编译期生成的各种字面量(“abcd”,“12345”等)和符号引用,从JDK1.7开始,运行时常量池移到堆中。
程序计数器:是当前线程所执行的字节码的行号指示器,较小的内存空间,唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
本地方法栈:该区域是为虚拟机使用到的本地方法服务
Java栈:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法返回地址等信息(从调用直到完成的过程,对应一个栈帧在java栈中的入栈出栈的过程)。参数设置:
-Xss
设置
-XX:+PrintCommandLineFlags参数,启动程序会先打印jvm启动参数
栈的特点
- 栈是线程私有的,即每个线程都会有一个栈空间,同栈中数据可以共享,但线程之间不能共享栈中数据
- 栈的生命周期短且小,超出变量作用域后,会自动释放所分配的内存空间,该内存空间立即能被使用
- 当栈内存空间不够,会抛出
java.lang.StackOverFlowError异常
主要是局部变量和函数形参,以及对象的引用存放在栈中
堆的特点
- 堆是线程共享的,一个JVM只有一个堆内存
- 堆的生命周期较长且大,当没有引用指向对象时,对象并不会马上回收,需要等待JVM垃圾收集器来回收内存
- 当堆中内存不足,则会抛出
java.lang.OutOfMemoryError异常
除了栈,元空间,其余都在堆中
运行时常量池
运行时常量池原先是方法区的一部分,从JDK1.7开始运行时常量池从方法区中移出,放在堆中。常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享,提升性能。
- 1.字符串常量池
字符串常量池是属于运行时常量池的一块,对于字符串,其对象的引用都是存储在栈中的,直接用双引号定义String str="test"存储在字符串常量池中,运行时new String("test"), 存储在堆中。
String str = "te";
String str1 = "test";
String str2 = "te" + "st";
String str3 = "test";
String str4 = str+"st";
String str5 = "te"+"st";
String newStr1 = new String("test");
String newStr2 = new String("test");
System.out.println(str1 == str3); //true str1和str3字符串常量池"test"
System.out.println(str1 == str2); //true
System.out.println(str1 == newStr1); // false.
System.out.println(newStr1 == newStr2); //false
System.out.println(str2 == str4); // false
System.out.println(str2 == str5); // true
'equals'相等的字符串,常量池只有一份,堆中有多份
- 2.包装类常量池技术的运用
java 8种基本数据类型都有自己的包装类,其中Byte,Short,Integer,Long,Character,Boolean都实现了常量池技术;而Byte,Short,Integer,Long类型在装箱时会缓存了范围[-128,127]的数据到数组中,Character会缓存[0,127]范围的数据到数组中进行缓存。查看对应valueOf()方法实现
Integer为例, valueOf方法源码如下:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
当整数在[-128,127]的范围在自动装箱时会直接使用常量池里面(不存在会创建一个),当整数不在[-128,127]范围内时,就会在堆中创建对象
int i = 100;
Integer i1 = 100; //范围是[-128,127]的数在自动装箱时加入常量池里面
Integer i2 = 100;
Integer i3 = new Integer(100);
Integer i4 = new Integer(100);
System.out.println(i==i1); //true 包装类型跟基本类型比较都会先拆箱转成基本类型再比较,因此比较的是值,类似使用equals
System.out.println(i1==i2); //true. 都指向常量池的100
System.out.println(i2==i3); //false
System.out.println(i3==i4); //false
案例分析(JDK1.8)
class Student{
private static final String PREFIX = "STUDENT:"; //静态字符串常量,字符串常量池
private String name;
private int age;
public Student(String n, int a) {
this.name = n;
this.age = a;
}
public void changeAge(int a) {
this.age = a;
}
}
public class Test {
public static void main(String[] args) {
Test test = new Test();
Student student = new Student("zhangsan", 12);
int num = 13;
student.changeAge(13);
}
}
-
Student,Test类及方法成员等类信息存放在元空间 -
Test test = new Test();test为对象引用,存放栈中,对象(new Test())存在堆中 -
Student student = new Student("zhangsan", 12);student为对象引用,存放栈中,对应的值也就是对象new Student("zhangsan", 12)存放在堆中,其中n,a为局部变量存储在栈中,n为字符串类型对象存放字符串常量池中,a为基本类型存储在栈中;name,age为成员变量,它们存储在堆中;当Student构造方法执行完之后,n,a将从栈中消失。 -
num为局部变量,且是基本类型,引用和值都存在栈中 -
main方法执行完成后,栈中test,num,student变量消失,值new Test(), new Student()将等待垃圾回收
三、常用垃圾回收器

Serial 串行回收器(新生代)
单线程垃圾收集器,采用复制算法、串行回收和STW机制进行内存回收,Serial Old 串行回收器(老年代)
单线程垃圾收集器,采用标记整理(压缩)算法、串行回收和STW机制进行内存回收,可以作为老年代CMS回收器的后备垃圾收集方案, 可以通过-XX:+UseSerialGC参数启用Serial回收器,表示新生代使用Serial,老年代使用Serial OldParNew回收器(新生代)
Parallel New是Serial的多线程版本垃圾回收器,采用复制算法、并行回收和STW机制进行内存回收,可以通过-XX:+UseParNewGC参数启用ParNew回收器,表示新生代使用ParNewCMS回收器 (老年代)
并发收集,采用标记清除算法,CMS的关注点在于尽可能缩短垃圾收集时用户线程停顿的时间
可以通过-XX:+UseConcMarkSweepGC开启,开启后-XX:+UseParNewGC会自动打开。(JDK9 该收集器被标记过时,JDK14被删除)Parallel Scavenge回收器(新生代)
采用复制算法,并行回收和STW机制,以吞吐量优先的垃圾回收器。可以让用户设置最大垃圾收集停顿时间-XX:MaxGCPauseMillis以及吞吐量大小-XX:GCTimeRatio, 是 jdk1.7 和 1.8 的默认的年轻代垃圾收集器。可以通过-XX:+UseParallelGC指定, 默认老年代使用Parallel Old收集器,如果老年代想采用串行回收器,可以增加-XX:-UseParallelOldGC配置。Parallel Old收集器(老年代)
Parallel Scavenge 收集器的老年代版本,基于标记-整理算法, 是 jdk1.7 和 1.8 的默认的老年代垃圾收集器。可以通过-XX:+UseParallelOldGC指定,默认年轻代使用Parallel Scavenge回收器Garbage First(G1)收集器
垃圾收集器技术发展历史上的里程碑式的结果,没有在物理空间上去区分新生代和老年代,而是把堆内存分割成很多不相关的区域(region,物理上不连续),使用不同区域来表示伊甸园区,幸存者区和老年代区,大对象区,尽量避免对整个Java堆进行垃圾收集,它会跟踪各个region里垃圾回收的价值大小(回收所获得的空间大小及所需时间的经验值),在后台维护一个优先列表(额外占用内存空间),每次根据允许收集时间,优先回收价值最大的region。可以通过-XX:+UseG1GC开启。JDK1.9 默认的垃圾收集器。适合堆内存很大的应用。
垃圾回收器 分类 作用位置 使用算法 特点 适用场景
Serial 串行 新生代 复制算法 响应速度优先 适用于单CPU环境下的Client模式
ParNew 并行 新生代 复制算法 响应速度优先 多CPU环境Server模式下与CMS配合使用
Parallel 并行 新生代 复制算法 吞吐量优先 适用于后台运算而不需要太多交互的场景
Serial Old 串行 老年代 标记-压缩算法 响应速度优先 单CPU环境下的Client模式
Parallel Old 并行 老年代 标记-压缩算法 吞吐量优先 适用于后台运算而不需要太多交互的场景
CMS 并发 老年代 标记-压缩算法 响应速度优先 适用于互联网或B/S业务
G1 并行与并发 新生代、老年代 复制算法 标记-压缩算法 响应速度优先 面向服务端应用
GC日志打印配置
- JDK 1.8
-Xms2G -Xmx2G -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -Xloggc:/logs/myGC.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/
- JDK1.9以上(包含JDK1.9)
-Xms2G -Xmx2G -Xlog:gc*=info,gc+heap=debug,gc+age=trace,safepoint:/logs/gc_%t.log:time,level,tags:filecount=5,filesize=20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/
从上面例子对比,可以看出,JDK1.8及更早版本,设置gc日志显得比较麻烦,JDK1.9开始,统一使用一个Xlog 参数来设置gc日志打印。
java -Xlog:help //查看参数所有所有选项
格式如下:
-Xlog[:[selections][:[output][:[decorators][:output-options]]]]
官网:https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/tutorial-Xlog/html/index.html
https://docs.oracle.com/en/java/java-components/enterprise-performance-pack/epp-user-guide/printing-jvm-information.html
https://docs.oracle.com/en/java/javase/11/jrockit-hotspot/logging.html#GUID-33074D03-B4F3-4D16-B9B6-8B0076661AAF