所谓虚拟机,就是一台虚拟的机器。它是一款软件,用来执行一系列虚拟计算机指令,大体上虚拟机可以分为系统虚拟机和程序虚拟机,大名鼎鼎的VisualBox/VMare就属于系统虚拟机,他们完全是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台。程序虚拟机代表就是Java虚拟机,它专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令,我们称为Java字节码指令。无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。Java发展至今,出现过很多虚拟机,最初Sun使用的一款叫Classic的Java虚拟机,到现在应用最广泛的是HotSpot虚拟机,除了Sun以外,还有BEA的JRockit,目前JRockit和HotSpot都被Oracle收入旗下,大有整合的趋势。
类加载子系统:负责从文件系统或者网络中加载Class信息,加载的信息存放在一块称之为方法区的内存空间。
方法区:(所有线程共享这个区的数据)就是存放类信息、常量信息、常量池信息、包括字符串字面量和数字常量等。
Java堆:在Java虚拟机启动的时候建立Java堆,它是Java程序中最主要的k内存工作区域,几乎所有的对象实例都存放在Java堆中,堆空间是所有线程共享的。
直接内存:Java的NIO库允许Java程序使用直接内存,从而提高性能,通常内存速度会优于Java堆。读写频繁的场合可能会考虑使用。
Java栈:每个虚拟机线程都有一个私有的栈,一个线程的java栈在线程创建的时候被创建,java栈中保存着局部变量、方法参数、同时java的方法调用、返回值等。
本地方法栈:本地方法栈和Java栈非常类似,最大不同为本地方法栈用于本地方法调用。java虚拟机允许java直接调用本地方法(通常使用C编写)
垃圾回收系统:垃圾收集系统是Java的核心,也是必不可好的,java有一套自己进行垃圾清理的机制,开发人员无需手工清理,文章下面会有详细说明。
PC寄存器:(Program Counter)寄存器也会每个线程私有的空间,java虚拟机会为每个线程创建PC寄存器,在任意时刻,一个Java线程总是在执行一个方法,这个方法被称为当前方法,如果当前方法不是本地方法,PC寄存器就会执行当前正在执行的命令,如果是本地方法,则PC寄存器值为undefined,寄存器存放如当前执行环境指针、程序计数器、操作栈指针、计算的变量指针等信息。
执行引擎:虚拟机最核心的组件就是执行引擎了,它负责执行虚拟机的字节码。一般用户先进行编译成机器码后执行。
堆:解决的是数据存储的问题,即程序如何执行,或者说如何处理数据。
栈:解决程序的运行问题,即程序如何执行,或者说如何处理数据。
方法区:辅助堆栈的快永久区,解决堆栈信息的产生,是先决条件。
我们创建一个新的对象,User:那么User类的一些信息(类信息、静态信息都存在于方法区中)
简单来说:堆存对象,栈存对象的引用,方法区(线程共享)存的是对象中的常量数据
这里插一个知识点:数据共享,线程共享 (https://blog.csdn.net/qq_39404258/article/details/82459543)
数据共享的有:栈、寄存器、PC 线程共享的有:堆、全局变量、静态变量、方法区
java堆是和java应用程序关系最密切的内存空间,几乎所有的对象都存放在堆中,并且java堆完全是自动化管理的,通过垃圾回收机制,垃圾对象会自动清理,不需要显示地释放。
根据垃圾回收机制不同,java堆有可能拥有不同的结构。最为常见的就是将整个java堆分为新生代和老年代。其中新生代存放新生对象或者年龄不大的对象,老年代则存放老年对象。
新生代分别为eden(伊甸园)区,s0区,s1区,s0和s1也被称为from和to区域,他们是两块大小相等并且可以互换角色的空间。绝大多数情况下,对象首先分配在eden区,在一次新生代回收后,如果对象还存活,则会进入s0或者s1区,之后每经过一次新生代回收,如果对象存活则它的年龄就加1,当对象达到一定年龄后,则进入老年代(tenured)。
s0 和 s1 大小一样,并且可以相互转换角色。
如何确定新生代对象、老年代对象:
因为java有自动的垃圾回收机制,每当回收对象一次该对象就改变一次标识,相当于标识对象的年龄。
java中新生代的from(s0)和to(s1)空间就是使用,复制算法:其核心思想就是将内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存留对象复制到未被使用的内存块中去,之后去清除之前正在使用的内存块中所有的对象,反复去交换两个内存的角色,完成垃圾收集。
java栈是一块线程私有的内存空间,一个栈,一般由三部分组成:局部变量表、操作数栈和帧数据区。
局部变量表:用户报错函数的参数及局部变量。
操作数栈:主要保存计算过程的中间结果,同事作为计算过程中变量临时的存储空间。
帧数据:除了局部变量表和操作数栈以外,栈还需要一些数据来支持常量池的解析,这里帧数据区保存着访问常量池的指针,方便程序访问常量池,另外,当函数返回或者出现异常时,虚拟机必须有一个异常处理表,方便发送异常的时候找到异常的代码,因此异常处理表也是帧数据区的一部分。
Java方法区和堆一样,方法区是一块所有线程共享的内存区域,它保存系统的类信息,比如类的字段、方法、常量池等。方法区的大小决定了系统可以保存多少个类,如果系统定义太多的类,导致方法区溢出。虚拟机同样会抛出内存溢出错误。方法区可以理解为永久区(Perm) 。
在虚拟机运行的过程中,如果可以跟踪系统的运行状态,那么对于问题的故障排查会有一定的帮助,为此,虚拟机提供了一些跟踪系统状态的参数,使用给定的参数执行java虚拟机,就可以在系统运行事时打印相关日志,用于分析实际问题。我们进行虚拟机参数配置,其实主要就是围绕着堆、栈、方法区进行配置。
垃圾回收机制可以配置不同的垃圾回收策略。
-XX:+PrintGC 使用这个参数,虚拟机启动后,只要遇到GC就会打印日志。
-XX:+UseSerialGC 配置串行回收器。
-XX:+PrintGCDetails 可以查看信息信息,包括各个区的情况。
-Xms:设置Java应用程序启动时的初始堆大小
-Xmx:设置Java应用程序能获得的最大堆大小
-Xmx20m -Xms5m -XX:+PrintConnandLineFlags: 设置最大堆内存20M,初始化堆内存为5M,可以将隐式或者显示传给虚拟机的参数输出。
示例[Test01]
总结:在实际工作中,我们可以直接将初始化的堆大小与最大堆大小设置相等,这样的好处是可以减少程序运行时的垃圾回收次数,从而提高性能。
如果是eclipse则点击右键,run as --> configuration
下面是idea图:
设置参数为:
-XX:+PrintGC -Xmx20m -Xms5m -XX:+UseSerialGC -XX:+PrintGCDetails
Test01代码:
package com.jvm;
/**
* @author: wangqinmin
* @date: 2019/7/23 14:50
* @description: 仰天大笑出门去,我辈岂是蓬蒿人
*/
public class Test01 {
public static void main(String[] arge) {
/**
* -XX:+PrintGC 使用这个参数,虚拟机启动后,只要遇到GC就会打印日志。
* -XX:+UseSerialGC 配置串行回收器。
* -XX:+PrintGCDetails 可以查看信息信息,包括各个区的情况。
* -Xms:设置Java应用程序启动时的初始堆大小
* -Xmx:设置Java应用程序能获得的最大堆大小
* -Xmx20m -Xms5m -XX:+PrintConnandLineFlags: 设置最大堆内存20M,初始化堆内存为5M,可以将隐式或者显示传给虚拟机的参数输出。
* 示例[Test01]
* 总结:在实际工作中,我们可以直接将初始化的堆大小与最大堆大小设置相等,这样的好处是可以减少程序运行时的垃圾回收次数,从而提高性能。
*/
// 使用这个参数进行配置。
//-XX:+PrintGC -Xmx20m -Xms5m -XX:+UseSerialGC -XX:+PrintGCDetails
System.out.println("jvm启动后遇到GC就会打印日志,设置最大堆内存20M,初始化堆内存5M,使用串行回收器,查看jvm各个区的情况");
double a = (double) Runtime.getRuntime().maxMemory();
System.out.println("最大内存:" + a / 1024 / 1024 / 1024 + "G");
System.out.println();
// 查看GC信息
System.out.println("max memory:" + Runtime.getRuntime().maxMemory() + "B,转换为KB:" + Runtime.getRuntime().maxMemory() / 1024 + "KB,转换为M:" + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "M");
System.out.println("free memory:" + Runtime.getRuntime().freeMemory() + "B,转换为KB:" + Runtime.getRuntime().freeMemory() / 1024 + "KB,转换为M:" + Runtime.getRuntime().freeMemory() / 1024 / 1024 + "M");
System.out.println("total memory:" + Runtime.getRuntime().totalMemory() + "B,转换为KB:" + Runtime.getRuntime().totalMemory() / 1024 + "KB,转换为M:" + Runtime.getRuntime().totalMemory() / 1024 / 1024 + "M");
System.out.println();
// 查看完信息后,实例化一个1M大小的对象
byte[] b1 = new byte[1 * 1024 * 1024];
// 查看GC信息
System.out.println("示例化一个1M的对象");
System.out.println("max memory:" + Runtime.getRuntime().maxMemory() + "B,转换为KB:" + Runtime.getRuntime().maxMemory() / 1024 + "KB,转换为M:" + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "M");
System.out.println("free memory:" + Runtime.getRuntime().freeMemory() + "B,转换为KB:" + Runtime.getRuntime().freeMemory() / 1024 + "KB,转换为M:" + Runtime.getRuntime().freeMemory() / 1024 / 1024 + "M");
System.out.println("total memory:" + Runtime.getRuntime().totalMemory() + "B,转换为KB:" + Runtime.getRuntime().totalMemory() / 1024 + "KB,转换为M:" + Runtime.getRuntime().totalMemory() / 1024 / 1024 + "M");
System.out.println();
// 查看完信息后,实例化一个4M大小的对象
byte[] b2 = new byte[4 * 1024 * 1024];
// 查看GC信息
System.out.println("示例化一个4M的对象");
System.out.println("max memory:" + Runtime.getRuntime().maxMemory() + "B,转换为KB:" + Runtime.getRuntime().maxMemory() / 1024 + "KB,转换为M:" + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "M");
System.out.println("free memory:" + Runtime.getRuntime().freeMemory() + "B,转换为KB:" + Runtime.getRuntime().freeMemory() / 1024 + "KB,转换为M:" + Runtime.getRuntime().freeMemory() / 1024 / 1024 + "M");
System.out.println("total memory:" + Runtime.getRuntime().totalMemory() + "B,转换为KB:" + Runtime.getRuntime().totalMemory() / 1024 + "KB,转换为M:" + Runtime.getRuntime().totalMemory() / 1024 / 1024 + "M");
System.out.println();
/**
控制台打印数据:
*"C:\Program Files\Java\jdk1.8.0_152\bin\java.exe" -XX:+PrintGC -Xmx20m -Xms5m -XX:+UseSerialGC -XX:+PrintGCDetails "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.1.4\lib\idea_rt.jar=6067:C:\Program Files\JetBrains\IntelliJ IDEA 2018.1.4\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_152\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar;E:\2019MyProject\dust\out\production\dust1;C:\Program Files\JetBrains\IntelliJ IDEA 2018.1.4\lib\junit-4.12.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2018.1.4\lib\hamcrest-core-1.3.jar" com.jvm.Test01
* [GC (Allocation Failure) [DefNew: 1664K->191K(1856K), 0.0010359 secs] 1664K->672K(5952K), 0.0010621 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
* 最大内存:0.0189208984375G
*
* max memory:20316160B,转换为KB:19840KB,转换为M:19M
* free memory:4973104B,转换为KB:4856KB,转换为M:4M
* total memory:6094848B,转换为KB:5952KB,转换为M:5M
*
* 示例化一个1M的对象
* max memory:20316160B,转换为KB:19840KB,转换为M:19M
* free memory:3924512B,转换为KB:3832KB,转换为M:3M
* total memory:6094848B,转换为KB:5952KB,转换为M:5M
*
* [GC (Allocation Failure) [DefNew: 1661K->76K(1856K), 0.0011371 secs][Tenured: 1696K->1773K(4096K), 0.0013115 secs] 2142K->1773K(5952K), [Metaspace: 3412K->3412K(1056768K)], 0.0024785 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
* 示例化一个4M的对象
* max memory:20316160B,转换为KB:19840KB,转换为M:19M
* free memory:4285616B,转换为KB:4185KB,转换为M:4M
* total memory:10358784B,转换为KB:10116KB,转换为M:9M
*
* Heap
* def new generation total 1920K, used 79K [0x00000000fec00000, 0x00000000fee10000, 0x00000000ff2a0000)
新生代中的eden区:
* eden space 1728K, 4% used [0x00000000fec00000, 0x00000000fec13cc0, 0x00000000fedb0000)
新生代中的s0区:
* from space 192K, 0% used [0x00000000fedb0000, 0x00000000fedb0000, 0x00000000fede0000)
新生代中的s1区:
* to space 192K, 0% used [0x00000000fede0000, 0x00000000fede0000, 0x00000000fee10000)
老年代区tenured:
* tenured generation total 8196K, used 5869K [0x00000000ff2a0000, 0x00000000ffaa1000, 0x0000000100000000)
* the space 8196K, 71% used [0x00000000ff2a0000, 0x00000000ff85b520, 0x00000000ff85b600, 0x00000000ffaa1000)
元空间Metaspace:
* Metaspace used 3461K, capacity 4500K, committed 4864K, reserved 1056768K
类空间 class space:
* class space used 374K, capacity 388K, committed 512K, reserved 1048576K
*
* Process finished with exit code 0
*/
}
}
然后确定即可,开始运行Test01程序。
新生代的配置
-Xmn: 可以设置新生代的大小,设置一个比较大的新生代会减少老年代的大小,这个参数对系统性能以及GC行为有很大影响,新生代大小一般会设置整个堆空间的1/3到1/4左右。
-XX:SurvivorRatio: 用来设置新生代中eden空间和from/to空间的比例。含义:-XX:SurvivorRatio=eden/from=eden/to
示例【Test02】
package com.jvm;
/**
* @author: wangqinmin
* @date: 2019/7/23 17:57
* @description: 仰天大笑出门去,我辈岂是蓬蒿人
*/
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:NewRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
byte[] b = null;
// 连续向系统申请10M空间
for (int i = 0; i < 10; i++) {
b = new byte[1 * 1024 * 1024];
}
}
}
总结:不同的堆分布情况,对系统执行会产生一定影响,在实际工作中,应该根据系统的特点做出合理的配置,基本策略:尽可能将对象预留在新生代,减少老年代的GC次数。
除了可以设置新生代的绝对大小(-Xmn),还可以使用(-XX:NewRatio)设置新生代和老年代的比例:-XX:NewRatio=老年代/新生代
第一次配置:
-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:NewRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
某些参数认识:
-Xss:设置线程栈的大小
-XX:MinHeapFreeRatio:设置堆空间最小空闲比例。当对空间的空闲内存小于这个数值时,JVM便会扩展堆空间
-XX:MaxHeapFreeRatio:设置堆空间的最大空闲比例。当堆空间的空闲内存大于这个数值时,便会压缩堆空间,得到一个较小的堆
-XX:NewSize:设置新生代的大小
-XX:NewRatio:设置老年代与新生代的比例,它等于老年代大小除以新生代大小
-XX:SurvivorRatio:新生代中eden区与survivior区的比例
-XX:MaxPermSize:设置最大的持久区的大小
-XX:PermSize:设置永久区的初始值
-XX:TargetSurvivorRatio:设置survivior区的可使用率。当survivior区的空间使用率达到这个数值时,会将对象送入老年代
在java程序的运行过程中,如果堆空间不足,则会抛出内存溢出的错误( Out Of Menory)OOM,一旦这类问题发生在生产环境,可能引起严重的业务中断,Java虚拟机提供了-XX:+HeapDumpOnOutOfMemoryError,使用该参数可以在内存溢出时导出整个堆信息,与之配合使用的还有参数-XX:HeapDumpPath=d:/Test03.dump 这个相当于设置导出具体位置,并使用java自带的工具jdk的bin目录下的jvisualvm.exe打开文件,进行阅览。
示例【Test03】
package com.jvm;
import java.util.Vector;
/**
* @author: wangqinmin
* @date: 2019/7/23 18:11
* @description: 仰天大笑出门去,我辈岂是蓬蒿人
*/
public class Test03 {
/**
* 使用jvisualvm来分析dump文件:
* jvisualvm是JDK自带的Java性能分析工具,在JDK的bin目录下,文件名就叫jvisualvm.exe。
* jvisualvm可以监控本地、远程的java进程,实时查看进程的cpu、堆、线程等参数,对java进程生成dump文件,并对dump文件进行分析。
* 像我这种从服务器上dump下来文件也可以直接扔给jvisualvm来分析。
* 使用方式:直接双击打开jvisualvm.exe,点击文件->装入,在文件类型那一栏选择堆,选择要分析的dump文件,打开。
*
* @param args
*/
public static void main(String[] args) {
// 内存溢出和内存溢出原因存放位置
// -Xms2m -Xmx2m -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]);
}
}
}
package com.jvm;
/**
* @author: wangqinmin
* @date: 2019/7/23 19:11
* @description: 仰天大笑出门去,我辈岂是蓬蒿人
*/
public class Test04 {
// -Xss1m
// 调用最大深度:21047
// -Xss5m
// 调用最大深度:123178
/**
* 栈调用深度
*/
private static int count;
public static void main(String[] args) {
try {
recursion();
} catch (Throwable t) {
System.out.println("调用最大深度:" + count);
t.printStackTrace();
}
}
public static void recursion() {
count++;
recursion();
}
}
方法区和java堆一样,方法区是一块所有线程共享的内存区域,它用于保存系统的类信息, 方法区(永久区)可以保存多少信息可以对其进行配置,在默认情况下,-XX:MaxPermSize为64M, 如果系统运行时生产大量的类,就需要设置一个相对合适的方法,以免出现永久区内存溢出的问题。
-XX:PermSize=64M -XX:MaxPermSize=64M
直接内存配置(据说jdk1.7之后可以不用配置): 直接内存也是java程序中非常重要的组成部分,特别是广泛用在NIO中,直接内存跳过了java堆,是java程序可以直接访问原生堆空间, 因此在一定程度上加快了内存空间的访问速度,但是说直接内存一定就可以提高内存访问速度也不见得,具体情况具体分析。 相关配置参数:-XX:MaxDirectMemorySize,如果不设置默认值为最大堆空间即-Xmx.直接内存使用达到上限时,就会触发垃圾回收,如果 不能有效的释放空间,也会引起系统的OOM(Out of Memory 内存泄漏)。
简单说一下:jdk1.7以后就没有Client 和 Server 虚拟机工作模式了。 Client 与 Server 模式的区别; Client模式启动快,运行期间性能低,基本做测试使用。 Server模式启动慢,优化策略较好,运行性能远远快与Client模式。
与JVM不错的博客(有总结,了解): https://www.cnblogs.com/redcreen/archive/2011/05/04/2036387.html
垃圾回收,简称GC,需要先澄清什么是垃圾,类比日常生活中的垃圾,我们会把他们丢入垃圾桶,然后倒掉,GC中的垃圾,特指存在于内存中、不会再被使用的对象,而回收就是相当于把垃圾 “倒掉” 。垃圾回收有很多算法:如引用计数算法、标记压缩算法、复制算法、分代、分区的思想。
进一步了解垃圾回收算法:
https://blog.csdn.net/newchenxf/article/details/78071804