定义
首先什么是jvm呢?
jvm也就是java Virtual Machine(java虚拟机)的缩写。一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能实现的,java虚拟机有自己完善的硬件架构,如处理器、堆栈等,还具有相应的指令系统。
跨平台
也正是jvm的存在所以才出现了跨平台无障碍的天下第一语言java(哈哈哈)
大家应该都知道计算机底层运行的代码是‘机器码’,不同系统生成的‘机器码’是不同的。
在下载jdk的时候我们根据需要选择了不同的操作系统以及位数,从而有了不同的jvm也就在在软件层面屏蔽了我们在不同系统在底层硬件以及指令上的区别
jvm区域划分
直接上图
栈(线程)
在web应用程序中每一条线程都会拥有各自对应的栈区,而中存放的是每个线程中的局部变量。
- 问:栈(线程)采用的数据格式是什么呢?特点是什么呢?
- 答:栈,FILO先进后出
那么这时候我们就需要知道一个栈帧的概念
当线程去执行某块代码的时候,该线程可能多个方法,那么该线程在栈(线程)区内会给每个方法分配栈帧内存区域,也就是说每一个方法都有对应的栈帧内存区域
直接上栈(线程)区图,方便大家理解
由上图我们可以看出jvm会给每个线程都分配一块对应的内存区域,该区域单独属于该线程,也就是栈区。
该线程首先执行A方法就会先把A方法压入栈区中,即入栈;执行完A方法后,同时将A方法的栈帧区域销毁,同时属于A栈帧区的数据也被销毁,即出栈,再执行B方法,。
那么栈区储存着一些信息
- 程序计数器:程序计数器是每一个线程独有的,用来存放线程执行的行号。那为什么要有程序计数器:因为多线程的存在,在cpu切换的时候,程序计数器记录要执行的行号,就不用下次重新执行浪费时间了。那程序计数器中的行号是怎么改变的:字节码执行引擎修改
当cpu切换的时候就会将当前的线程挂起去执行其他线程此时就会记录行号,等待下次执行
- 栈帧区(该线程中执行的方法都有一块属于该方法的独立的栈帧区域,该栈帧区域中分别包含局部变量表,顾名思义也就是该方法所用到的局部变量,操作数栈也就是局部变量的值,还有动态链接和方法出口,动态链接其实就是指针,而方法出口指的是字方法完成后回到主线程的哪个位置),例如在xx方法中有这么一段代码
int a = 1;
先将操作常量值压入操作数栈,然后将变量a压入局部变量栈,然后将1赋值给a
在使用变量a的时候首先将值1装载到操作数栈,操作(从栈顶依次弹出)结束后将操作的结果压入操作数栈
一般来说我们的方法中用的参数不仅仅有常量,还包括大量的对象,那么对象和常量在栈中有啥区别呢
有上述我们都知道局部变量的操作常量都存在操作数栈,经过赋值将其联系到一起进行操作。
但是变量对象是存在堆中的,而局部变量表中的的参数是类似指针的东西,指向堆中的对象。
栈帧
栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。说简单就是通过EBP(栈帧指针,请注意不是ESP)寄存器访问局部变量、参数、函数返回地址等的手段
EBP 寄存器又被称为帧指针(Frame Pointer);
ESP 寄存器又被称为栈指针(Stack Pointer);
栈帧一般包括:
- 函数的返回地址和参数 临时变量:
- 包括函数的非静态局部变量以及编译器自动生成的其他临时变量
- 函数调用的上下文
可参考:https://www.jianshu.com/p/b666213cdd8a
方法区(元空间)
大多数用的JVM都是Sun公司的HotSpot。在HotSpot上把GC分代收集扩展至方法区,或者说使用永久代来实现方法区。因此,我们得到了结论,永久代是HotSpot的概念,方法区是Java虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现
方法区在jdk1.8之前称为永久区,在jdk1.8之后被称为元空间,主要存放一些静态变量,常量,类元信息等
本地方法栈
比如线程中的start方法,查看源代码。可以看到一个类似接口的没有方法体的方法用nativ修饰就是本地方法,他的底层是有实现的但是不是java代码,是c语言实现的。
前提环境:java刚出现,公司的大部分系统是用c语言。当两者发生交互的时候,就用到了本地方法。通过java的执行引擎,去调用c语言的库函数包,也就是dll文件,相当于我们java中的jar包。
现在可以用http,rpc,thrift
堆
堆是jvm中的重点,因为gc就是在堆中发生的。
那么首先来了解堆得内存模型
new出来的对象一般会存放在伊甸园区,
假如初始堆设置为600M,默认老年代占堆得三分之二,年轻代占三分之一。年轻代又分为伊甸园区和survivor区。eden区占五分之四,
当Eden区放满了会发生 minorGC,由字节码执行引擎单独开辟一个线程去minorGC。minorGC就是将垃圾对象全部清除
垃圾对象:线程一旦执行完,处于线程中的局部变量,对象等都被释放,即指向堆区的指针不存在了。即堆中没有指针指向的的对象就是垃圾对象
GC ROOT根节点:
- 虚拟机(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的native方法)中引用的对象
jvm是如何找到这些垃圾对象的:首先找出所有的gc root根节点,再根据这些变量的指针指向的堆中的对象,如果存在引用就不是垃圾对象,如果不在引用链条上就是垃圾对象。
这个引用链条由栈(线程)中的栈帧局部变量表指向堆中对象开始
GC方法:
一个完成的GC:
经历一个minor GC 后仍然存活的对象将放入Survivor区的From区,该对象的分代年龄+1,只要经历过一次后就会+1(该数据在对象头的内存区中)。再次发生GC的时候form区会放入to区,Eden区存活的对象也会放入to区。再次GC的时候to区存活的对象再次放入from区。就是如此循环(Survivor区总有一块区域是空着的)。
当survivor区中的某一区已经存在对象了,那么在发生minorGC的时候,survivor区存有对象的区域也会发生GC。
老年区:
当分代年龄达到15次会直接移到老年代。
当大对象在to区放不下也会直接放入老年代。
包括线程池、缓存对象(系统初始化搞得静态变量的对象)、spring容器中的bean注解、service注解
当老年区的空间放满了会发生FULL GC .
当老年区满了但是又都在用就会发成OOM,内存溢出
STW : stop the world。当发生FullGc的时候就会发生STW。即停掉应用程序。会发生很多奇妙的事情
jvm调优也就是fullFC发生的几率小一点,stw的时间短一点。
jvm参数
jvm参数设置:首先要对系统流量评估、日活用户数量、可能发生的数据量、分为高峰期和平时期,计算每秒的数据量,计算每条数据对象的大小(一个byte是8位),放大20倍计算每秒生成多大的对象,同时还有其他操作会涉及到再放大10倍,
-X :非标准选项
-XX:非稳定选项
在选项名前用 “+” 或 “-” 表示开启或关闭特定的选项,例:
-XX:+UseCompressedOops:表示开启 压缩指针
-XX:-UseCompressedOops:表示关闭 压缩指针
设置堆大小
默认是 老年代占堆得三分之二,年轻代占三分之一。老年代+年轻代=堆大小
年轻代又分为伊甸园区和survivor区。eden区占五分之四,FROM和TO区平分剩下的两个区域
对于服务器部署,-Xms并-Xmx经常设置为相同的值。
请参阅位于的Java SE HotSpot虚拟机垃圾收集优化指南中的“人体工程学”部分http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html。
-Xms 大小
设置堆的初始大小(以字节为单位)。此值必须是1024的倍数且大于1 MB。追加字母k或K表示千字节,m或M表示兆字节,g或G表示千兆字节。
下示例显示如何使用各种单位将分配的内存大小设置为6 MB:
-Xms6291456
-Xms6144k
-Xms6m
如果未设置此选项,则初始大小将设置为为老一代和年轻一代分配的大小之和。可以使用-Xmn选项或-XX:NewSize选项设置年轻代的堆的初始大小。
-Xmx 大小
指定内存分配池的最大大小(以字节为单位)。此值必须是1024的倍数且大于2 MB。
下面的示例演示如何使用各种单位将分配的最大内存大小设置为80 MB:
-Xmx83886080
-Xmx81920k
-Xmx80m
该-Xmx选项等效于-XX:MaxHeapSize。
设置堆中Eden区大小
-Xmn 大小
设置年轻一代(苗圃)的堆的初始大小和最大大小(以字节为单位)。
堆的年轻代区域用于新对象。与其他区域相比,在该区域执行GC的频率更高。如果年轻一代的大小太小,则会执行许多次要的垃圾回收。如果大小太大,那么将仅执行完整的垃圾收集,这可能需要很长时间才能完成。Oracle建议您将年轻代的大小保持在整个堆大小的一半到四分之一之间。
以下示例显示如何使用各种单位将年轻一代的初始大小和最大大小设置为256 MB:
-Xmn256m
-Xmn262144k
-Xmn268435456
取而代之的是的-Xmn选项组中的两个堆年轻一代的初始和最大大小,您可以使用-XX:NewSize设置初始大小和-XX:MaxNewSize设置的最大尺寸。
设置栈(线程)区大小
-Xss 大小
设置线程堆栈大小(以字节为单位)。默认值取决于平台:
Linux / ARM(32位):320 KB
Linux / i386(32位):320 KB
Linux / x64(64位):1024 KB
OS X(64位):1024 KB
Oracle Solaris / i386(32位):320 KB
Oracle Solaris / x64(64位):1024 KB
下面的示例将线程堆栈大小以不同的单位设置为1024 KB:
-Xss1m
-Xss1024k
-Xss1048576
此选项等效于-XX:ThreadStackSize。
设置方法区大小
-XX:MaxPermSize = 大小
设置最大永久生成空间大小(以字节为单位)。此选项在JDK 8中已弃用,并由该-XX:MaxMetaspaceSize选项取代。
-XX:PermSize = 大小
设置分配给永久生成的空间(以字节为单位),如果超出该空间,则会触发垃圾回收。此选项在JDK 8中已弃用,并由该-XX:MetaspaceSize选项取代。
-XX:MetaspaceSize = 大小
设置分配的类元数据空间(方法区)的大小,该类元数据空间将在首次超过垃圾收集时触发垃圾收集。垃圾收集的阈值取决于使用的元数据量而增加或减少。默认大小取决于平台
-XX:MaxMetaspaceSize = 大小
设置可以分配给类元数据(方法区)的最大本机内存。默认情况下,大小不受限制。应用程序的元数据量取决于应用程序本身,其他正在运行的应用程序以及系统上可用的内存量。
下面的示例显示如何将最大类元数据大小设置为256 MB:
-XX:MaxMetaspaceSize = 256m
其他参数
-XX:MinHeapFreeRatio=40:设置堆空间最小空闲比例(默认40)(当-Xmx与-Xms相等时,该配置无效)
-XX:MaxHeapFreeRatio=70:设置堆空间最大空闲比例(默认70)(当-Xmx与-Xms相等时,该配置无效)
-XX:NewRatio=2:设置年轻代与年老代的比例为2:1
-XX:SurvivorRatio=8:设置年轻代中eden区与survivor区的比例为8:1
-XX:MetaspaceSize=64M:设置元数据空间初始大小(取代-XX:PermSize)
-XX:MaxMetaspaceSize=128M:设置元数据空间最大值(取代之前-XX:MaxPermSize)
-XX:TargetSurvivorRatio=50:设置survivor区使用率。当survivor区达到50%时,将对象送入老年代
-XX:+UseTLAB:在年轻代空间中使用本地线程分配缓冲区(TLAB),默认开启
-XX:TLABSize=512k:设置TLAB大小为512k
-XX:+UseCompressedOops:使用压缩指针,默认开启
-XX:MaxTenuringThreshold=15:对象进入老年代的年龄(Parallel是15,CMS是6)
更多
还有其他参数暂未整理可参考官方文档