1 JVM内存模型
1.1概述
虚拟机就是一款软件,可分为系统虚拟机和程序虚拟机。
Visual Box,Vmare就属于系统虚拟机,他们完全是对物理虚拟机的仿真,提供一个可运行完整操作系统的软件平台。
程序虚拟机的代表就是Java虚拟机,专门为执行单个计算机程序而设计,在java虚拟机中执行的指令会转为java字节码指令。
无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。java发展至今出现过很多虚拟机,现在用的广泛的是HotSpot虚拟机(底层C、C++)。
1.2体系结构
1.2.1 类加载子系统
负责从文件系统或者网络中加载Class信息,加载的信息存放在方法区。
JVM中类的加载是由类加载器(ClassLoader)和它的子类来实现的。
1.2.2 运行时数据区
堆解决的是数据存储的问题,即数据怎么放,放在哪;
栈解决的是运行问题,即程序如何执行,或者说如何处理数据;
方法区则是辅助堆栈的永久区。解决堆栈信息的产生,是先决条件。
如我们创建一个User对象,那么User类的一些信息(类信息,静态信息)都存在于方法区中,
User类被实例化出来后,被存储到java堆中一块内存空间,
当我们去使用的时候,使用的是User对象的引用,如User user = new User();这里的user就是存放在java栈中,即User的一个引用。
1 方法区:就是存放类信息,常量信息,常量池信息,包括字符串字面量和数字常量等。
java方法区和堆一样,是所有线程共享的内存区域,它保存系统的类信息,比如类的字段,方法,常量池等。方法区的大小决定了系统可以保存多少个类,如果系统定义太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误。
2 java堆:在java虚拟机启动的时候建立java堆,它是java程序最主要的内存工作区域,几乎所有的对象实例都存放到java堆中,堆空间是所有线程共享的。
java堆是和java应用程序关系最密切的内存空间,几乎所有的对象都存放在其中,并且java堆完全是自动化管理的,通过垃圾回收机制,垃圾对象会自动清理,不需要显示释放。
根据垃圾回收机制的不同,java堆可能有不同的结构。最常见的就是将整个java堆分为新生代和老年代。其中新生代存放新生的对象或者年龄不大的对象,老年代则存放老年对象。
新生代分为eden区,s0区,s1区,s0和s1也被称为from和to区域。他们是两块大小相等并且角色可以互换的空间。
绝大多数情况下,对象首先分配在eden区,在一次新生代回收后,如果对象还存活,则会进入s0或s1区,之后每一次新生代回收,如果对象存活则它的年龄就加1,当对象到达一定的年龄后,则进入老年代。
3 Java栈:每个线程都有一个私有的栈,一个线程的java栈在线程创建的时候被创建,java栈中保存着局部变量,方法参数,同时java方法的调用,返回值等。
4 本地方法栈:本地方法栈和java栈非常类似,最大的不同为本地方法栈用于本地方法(native)调用,如果线程在运行过程中需要调用本地方法,本地方法在运行过程中也需要内存空间,这些内存空间主要分配在本地方法栈。
5 程序计数器:是一块很小的内存空间,是线程私有的,存放程序正在运行的代码的内存位置(指令行号),用于多线程切换恢复时告诉cpu执行哪一行代码。
1.2.3 字节码执行引擎
虚拟机最核心的组件就是执行引擎,负责执行被分配给运行时数据区的字节码,执行引擎读取字节码并逐个执行。一般会先进行编译然后执行。
-
解释器 – 解释器更快地解释字节码,但执行速度很慢。解释器的缺点是,当一个方法被多次调用时,每次都需要一个新的解释。
-
JIT编译器
– JIT编译器消除了解释器的缺点。执行引擎将在转换字节码时使用解释器的帮助,但是当它发现重复的代码时,它使用JIT编译器,JIT编译整个字节码并将其更改为本机代码。此本机代码将直接用于重复的方法调用,从而提高系统的性能。
-
中间代码生成器 – 生成中间代码
-
代码优化器 – 负责优化上面生成的中间代码
-
目标代码生成器 – 负责生成机器代码或本地代码
-
分析器 – 一个特殊的组件,负责寻找热点,即方法是否被多次调用。
-
-
垃圾收集器:收集和删除未引用的对象。可以通过调用
System.gc()
触发垃圾收集,但不能保证执行。JVM的垃圾收集收集创建的对象。
2 虚拟机参数配置
2.1概述
进行虚拟机参数配置,主要就是围绕着堆,栈,方法区进行配置。
2.2堆分配参数
-Xms:配置java程序启动时初始堆大小。
-Xmx:设置java程序能获得的最大堆大小。
-Xmx20m -Xms5m -XX:+PrintCommandLineFlags:可以将隐式或显式传给虚拟机的参数输出
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:5209224 total memory:6094848 [GC (Allocation Failure) [DefNew: 864K->192K(1856K), 0.0017340 secs] 864K->548K(5952K), 0.0028302 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 分配了1M max memory:20316160 free memory:4451768 total memory:6094848 [GC (Allocation Failure) DefNew: 1248K->0K(1856K), 0.0023057 secs 1604K->1572K(5952K), [Metaspace: 2678K->2678K(1056768K)], 0.0046370 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 分配了4M max memory:20316160 free memory:4519712 total memory:10358784 Heap def new generation total 1920K, used 68K [0x00000000fec00000, 0x00000000fee10000, 0x00000000ff2a0000) eden space 1728K, 3% used [0x00000000fec00000, 0x00000000fec11218, 0x00000000fedb0000) from space 192K, 0% used [0x00000000fedb0000, 0x00000000fedb0000, 0x00000000fede0000) to space 192K, 0% used [0x00000000fede0000, 0x00000000fede0000, 0x00000000fee10000) tenured generation total 8196K, used 5668K [0x00000000ff2a0000, 0x00000000ffaa1000, 0x0000000100000000) the space 8196K, 69% used [0x00000000ff2a0000, 0x00000000ff829278, 0x00000000ff829400, 0x00000000ffaa1000) Metaspace used 2685K, capacity 4486K, committed 4864K, reserved 1056768K class space used 295K, capacity 386K, committed 512K, reserved 1048576K
总结:在实际工作中,可以将初始堆的大小与最大堆大小设置相等,这样的好处是可以减少程序运行时的垃圾回收次数,提高性能。
-Xmn:可以设置新生代的大小,设置一个比较大的新生代会减少老年代的大小,这个参数对系统性能和GC行为有很大影响,新生代大小一般会设置整个堆空间的1/4或者1/3左右。
-XX:SurvivorRatio:用来设置新生代中eden空间和from/to空间的比例,含义:-XX:SurvivorRatio=eden/from=eden/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 (Allocation Failure) [DefNew: 511K->255K(768K), 0.0010678 secs] 511K->433K(20224K), 0.0011099 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 768K, used 596K [0x00000000fec00000, 0x00000000fed00000, 0x00000000fed00000) eden space 512K, 66% used [0x00000000fec00000, 0x00000000fec553f8, 0x00000000fec80000) from space 256K, 99% used [0x00000000fecc0000, 0x00000000fecffff8, 0x00000000fed00000) to space 256K, 0% used [0x00000000fec80000, 0x00000000fec80000, 0x00000000fecc0000) tenured generation total 19456K, used 10417K [0x00000000fed00000, 0x0000000100000000, 0x0000000100000000) the space 19456K, 53% used [0x00000000fed00000, 0x00000000ff72c688, 0x00000000ff72c800, 0x0000000100000000) Metaspace used 2679K, capacity 4486K, committed 4864K, reserved 1056768K class space used 295K, capacity 386K, committed 512K, reserved 1048576K
第二次配置:
[GC (Allocation Failure) [DefNew: 2981K->1571K(5376K), 0.0021293 secs] 2981K->1571K(18688K), 0.0021891 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 4713K->1024K(5376K), 0.0021999 secs] 4713K->1570K(18688K), 0.0022352 secs] [Times: user=0.00 sys=0.02, real=0.00 secs] [GC (Allocation Failure) [DefNew: 4158K->1024K(5376K), 0.0089731 secs] 4704K->1570K(18688K), 0.0089992 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] Heap def new generation total 5376K, used 3209K [0x00000000fec00000, 0x00000000ff300000, 0x00000000ff300000) eden space 3584K, 60% used [0x00000000fec00000, 0x00000000fee223f0, 0x00000000fef80000) from space 1792K, 57% used [0x00000000ff140000, 0x00000000ff240010, 0x00000000ff300000) to space 1792K, 0% used [0x00000000fef80000, 0x00000000fef80000, 0x00000000ff140000) tenured generation total 13312K, used 546K [0x00000000ff300000, 0x0000000100000000, 0x0000000100000000) the space 13312K, 4% used [0x00000000ff300000, 0x00000000ff388a38, 0x00000000ff388c00, 0x0000000100000000) Metaspace used 2683K, capacity 4486K, committed 4864K, reserved 1056768K class space used 295K, capacity 386K, committed 512K, reserved 1048576K
第三次配置:
[GC (Allocation Failure) [DefNew: 2959K->1571K(5120K), 0.0019348 secs] 2959K->1571K(18816K), 0.0019911 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 4711K->1024K(5120K), 0.0016902 secs] 4711K->1570K(18816K), 0.0017192 secs] [Times: user=0.00 sys=0.01, real=0.00 secs] [GC (Allocation Failure) [DefNew: 4156K->1024K(5120K), 0.0004409 secs] 4702K->1570K(18816K), 0.0004801 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 5120K, used 3204K [0x00000000fec00000, 0x00000000ff2a0000, 0x00000000ff2a0000) eden space 3456K, 63% used [0x00000000fec00000, 0x00000000fee210f0, 0x00000000fef60000) from space 1664K, 61% used [0x00000000ff100000, 0x00000000ff200010, 0x00000000ff2a0000) to space 1664K, 0% used [0x00000000fef60000, 0x00000000fef60000, 0x00000000ff100000) tenured generation total 13696K, used 546K [0x00000000ff2a0000, 0x0000000100000000, 0x0000000100000000) the space 13696K, 3% used [0x00000000ff2a0000, 0x00000000ff328a38, 0x00000000ff328c00, 0x0000000100000000) Metaspace used 2683K, capacity 4486K, committed 4864K, reserved 1056768K class space used 295K, capacity 386K, committed 512K, reserved 1048576K
总结:不同的堆分布情况,对系统执行会产生一定的影响,在实际工作中,应该根据系统的特点做出合理的配置,基本策略:尽可能将对象预留在新生代,减少老年代的GC次数。除了可以设置新生代的大小,还可以设置新生代和老年代的比例:
-XX:NewRatio=老年代/新生代
2.3栈配置
Java虚拟机提供了参数-Xss来指定线程的最大栈空间,这个参数也直接决定了函数可调用的最大深度。
package com.bjsxt.base001; public class Test04 { //-Xss1m //-Xss5m //栈调用深度 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(); } } }
执行结果:
-Xss1m :
调用最大深入:23250 java.lang.StackOverflowError at com.bjsxt.base001.Test04.recursion(Test04.java:13) at com.bjsxt.base001.Test04.recursion(Test04.java:13)
...
-Xss5m:
调用最大深入:127908 java.lang.StackOverflowError at com.bjsxt.base001.Test04.recursion(Test04.java:13) at com.bjsxt.base001.Test04.recursion(Test04.java:13)
...
2.4方法区配置
-XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。
-XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。
由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大。
2.5 GC相关参数
-XX:+PrintGC
‐XX:+PrintGCDetails
‐XX:+PrintGCDateStamps
‐XX:+PrintGCTimeStamps
‐XX:+PrintGCCause
‐XX:+UseGCLogFileRotation ‐XX:NumberOfGCLogFiles=10 ‐XX:GCLogFileSize=100M
‐Xloggc:路径
2.6 JVM参数汇总查看命令
java -XX:+PrintFlagsInitial 表示打印出所有参数选项的默认值
java -XX:+PrintFlagsFinal 表示打印出所有参数选项在运行程序时生效的值