虚拟机和垃圾回收
1、Java程序执行流程
.java----->(编译)----->.class----->(解释,通过虚拟机来完成)----->解释成机器码后再执行
通过类加载器(ClassLoader)将 .class 文件加载到虚拟机(Java Vitual Machine JVM).
JVM的组成部分:
- 执行引擎(Execution Engine);
- 本地方法接口(Native Interface);
- 本地方法库(Native Libraries);
- 运行时数据区(Runtime Data Area); 包括:栈内存、堆内存等等。
2、虚拟机机
虚拟机(英语:virtual machine),在计算机科学中的体系结构里,是指一种特殊的软件,可以在计算机平台和终端用户之间创建一种环境,而终端用户则是基于虚拟机这个软件所创建的环境来操作其它软件。虚拟机(VM)是计算机系统的仿真器,通过软件模拟具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统,能提供物理计算机的功能。
系统虚拟机:如VMware
程序虚拟机:如Java虚拟机(解析字节码)
了解三大虚拟机:
- SUN 公司的 HotSpot;
- BEA公司的 JRockit; (WebLogic)
- IBM的 JVM;
从JDK 1.8开始,HotSpot和JRockit两者结合成现在的HotSpot;
3、运行时数据区
堆内存:保存所有引用数据的真实信息,属于共享区;
栈内存:基本类型、运算、指向堆内存的指针;
方法区:存储类的信息、常量池、方法数据、方法代码,也属于共享区;
程序计数器:是一个非常小的内存空间,用来保证程序依次执行;
本地方法栈:与虚拟机栈发挥的作用非常类似,只是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为使用到的本地操作系统方法(Native)服务;
堆和方法区是所有线程共享的;
栈、程序计数器、本地方法栈是每个线程独享的;
运行时数据区就是java内存管理,java能管理的地方只在java运行时数据区,其他的我们无法控制;
关键是在堆内存中,如果想要真正做到对程序理解,就需要对堆内存进行一定的控制。
堆解决的是数据存储的问题,即数据怎么放,放在哪。
方法区则是辅助堆栈的一次永久区(Perm),解决堆栈信息的产生,如:我们创建一个新的Person对象,那么Person类的一些信息(类信息、静态信息都存在于方法区中)
Person p1 = new Person();
Person p2 = new Person();
- Person类被实例化后,就被存储到了java堆内存中;
- 当我们要使用Person对象实例时都是用该实例对象的引用 p 即引用变量
4、栈内存
一个线程一个单独的栈(私有);
一个方法分配一个单独的栈帧;
当产生一个方法调用的时候,原本的方法会入栈,当方法执行完毕之后,方法将会进行栈帧的出栈,和垃圾回收无关。
5、对内存(垃圾回收的主战场)
注意:JDK 1.8 之后就不存在永久代了
堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,该内存区域存放了对象实例及数组(但不是所有的对象实例都在堆中)。其大小通过-Xms(最小值)和-Xmx(最大值)参数设置(最大最小值都要小于1G),前者为启动时申请的最小内存,默认为操作系统物理内存的1/64,后者为JVM可申请的最大内存,默认为物理内存的1/4,默认当空余堆内存小于40%时,JVM会增大堆内存到-Xmx指定的大小 。
新生代:
分为 eden区 + 两个大小相同的存活期 s0 , s1;
- 所有使用new关键字实例出来的对象,一定会在伊甸园区(eden)区进行保存,除非对象太大存不下;
- 存活期分为两个大小相等的存活区,存活区保存的一定是在伊甸园区保存很久,并且经过几次GC后还存活下来的活跃对象,存活区一定有两块大小相等的空间,目的是一块存活区未来晋升,另一块存活区为了对象回收。这两块空间一定有一块是空的,在新生代中使用的是Minor GC,这种GC采用的是复制算法。
老年代:
主要接受由新生代发送过来的对象,一般情况下经过了数次 Minor GC 之后还会保存下来的对象才会进入到老年代。每次进行Minor GC 后存活下来的对象,年龄都会 +1,到了一定的年龄后(默认是15),就将进入老年代
如果要保存的对象的大小超过了伊甸园区的大小,此对象也将直接保存在老年代之中,当老年代空间不足时,将引发“Major GC” 即 “Full GC” 全部由垃圾回收器回收。
6、JVM 垃圾回收流程
在整个 GC 流程里,最需要处理的就是新生代和老年代的内存清理操作。
-
当发现有一个新的对象产生,就需要给该对象分配内存空间,于是就需要为该对象进行内存空间的申请;
-
首先会判断伊甸园区是否有内存空间,如果正好有内存空间,则将新对象保存在伊甸园区;
-
如果伊甸园区的内存不足,那么会自动执行一次 Minor GC (小的垃圾回收),将伊甸园区无用的内存空间进行清理,当清理之后会继续判断内存空间是否充足? 如果充足的话就会把新建的对象保存在伊甸园区;
-
如果执行了一次 Minor GC 后内存空间仍然不足,那么这个时候就会进行存活区判断,如果存活区有内存空间剩余,则将伊甸园区的部分对象保存在存活区,那么随后会继续判断伊甸园区的内存空间是否足够,如果足够则在伊甸园区进行空间分配;
-
如果此时存活区也没有内存空间了,则开始判断老年代,如果此时老年代空间充足,则将存活区的活跃对象保存在老年代,而后存活区就会有空间剩余,随后伊甸园区将活跃对象保存在存活区中,而后在伊甸园区里为新对象开辟内存空间;
-
如果这个时候老年代的内存也满了的话,那么这个时候就会进行 Major GC(Full GC),进行老年代的内存清理;
-
如果老年代执行了 Full GC 后,依然无法进行对象的保存,就会产生 OOM异常 - - OutOfMemoryError;
7、虚拟机参数调优
堆内存空间调整参数
no | 参数名称 | 描述 |
---|---|---|
01 | -Xms | 设置初始分配大小,默认为物理空间的 1/64 |
02 | -Xmx | 最大分配内存,默认为物理空间的 1/4 |
03 | -XX:+PrintGCDetails | 输出详细的GC处理日志 |
04 | -XX:+PrintGCTimeStamps | 输出GC的时间戳信息 |
05 | -XX:+PrintGCDateStamps | 输出GC的时间戳信息(以日期的形式) |
06 | -XX:+PrintHeapAtGC | 在GC处理的前后打印堆内存信息 |
07 | -Xloggc:保存路径 | 设置日志信息保存文件 |
新生代内存调整参数:
no | 参数名称 | 描述 |
---|---|---|
01 | -Xmn | 设置新生代堆内存大小,默认是物理内存的 1/64 |
02 | -Xss | 设置每个线程栈大小,JDK 1.5之后默认为每个线程分配1M的栈大小,减小此数值可以产生更多的线程对象,但是不能无限生成。 |
03 | -XX:SurvivorRatio | 设置 eden 与 survivor空间的大小比例,默认为8:1:1,不建议修改 |
04 | -XX:NewSize | 设置新生代内存区大小 |
05 | -XX:NewRatio | 设置新生代与老年代的比率 |
老年代内存调整参数:
no | 参数名称 | 描述 |
---|---|---|
01 | -XX:NerRatio | 设置新生代与老年代的比率 |
02 | -XX:+UseAdaptiveSizePolicy | 控制是否采用动态控制策略,如果动态控制,则动态调整Java堆中各个区域的大小以及进入老年代的年龄 |
03 | -XX:PertenureSizeThreshold | 控制直接升入老年代的对象大小,大于这个值的对象会直接进入老年代 |