JVM初认识
什么是JVM
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。
JVM的位置
从这张图中我们可以看出,JVM是运行在操作系统之上的,它与硬件没有直接的交互
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z01GW3hr-1617693870832)(C:\Users\43895\AppData\Roaming\Typora\typora-user-images\1617675682768.png)]
JVM 的组成部分
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aZ6r1D88-1617693870834)(C:\Users\43895\AppData\Roaming\Typora\typora-user-images\1617677674079.png)]
可以看出大致分为四个系统构成,分别是类加载器系统、运行时数据区,以及执行引擎、Native Interface 本地接口
类加载器
类加载器的作用是加载类文件到内存,比如编写一个HelloWord.java 程序,然后通过javac 编译成class 文件,那怎么才能加载到内存中被执行呢?Class Loader 承担的就是这个责任,那不可能随便建立一个.class 文件就能被加载的,Class Loader 加载的class 文件是有格式要求。
Class Loader 只管加载,只要符合文件结构就加载,至于说能不能运行,则不是它负责的,那是由Execution Engine 负责的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SPeINktx-1617693870837)(C:\Users\43895\AppData\Roaming\Typora\typora-user-images\1617677991796.png)]
JAVA中默认自带了三个类加载器,分别为
启动类加载器(根加载器):只负责加载JAVA本身自带的核心库 rt.jar
扩展类加载器:只加载Jar格式的文件,不管Class文件
应用类加载器:负责加载我们编写的编码编译而成的Class文件
双亲委机制
1.类加载器收到类加载的请求
2.将这个清求向上委托给父类加载器去完成,一 直向上委托,直到启动类加载器(根加载)
3.因为各个类加载器的职责不同,所以当父类加载器无法加载该类时,才会转交给子级加载器去加载
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gnSxMv1J-1617693870839)(C:\Users\43895\AppData\Roaming\Typora\typora-user-images\1617679063589.png)]
这样设计的好处
保证了JAVA的稳定性,避免让公共的类重复加载,防止了核心类库被用户编写的同名类所纂改
运行数据区
运行数据区是整个JVM 的重点。我们所有写的程序都被加载到这里,之后才开始运行,Java 生态系统如此的繁荣,得益于该区域的优良自治。
整个JVM 框架由加载器加载文件,然后执行器在内存中处理数据,需要与异构系统交互是可以通过本地接口进行,瞧,一个完整的系统诞生了!
Stack 栈
栈也叫栈内存,是Java程序的运行区,是在线程创建时创建
生命周期
它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就Over
栈中的数据格式
栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生栈帧F2也被压入栈,执行完毕后,先弹出F2栈帧,再弹出F1栈帧,遵循“先进后出”原则。
栈中储存的数据
栈帧中主要保存3类数据:本地变量(Local Variables),包括输入参数和输出参数以及方法内的变量;栈操作(Operand Stack),记录出栈、入栈的操作;栈帧数据(Frame Data),包括类文件、方法等等。光说比较枯燥,我们画个图来理解一下Java栈,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hcK5CIoG-1617693870840)(C:\Users\43895\AppData\Roaming\Typora\typora-user-images\1617681351951.png)]
Method Area 方法区
方法区是被所有线程共享,该区域保存所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。
简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关
static final, Class, 常量池
PC Register 程序计数器
每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码,由执行引擎读取下一条指令。
Native Method Stack 本地方法栈
native :凡是带了native关键字的。说明java 无法直接操作
本地接口的作用是融合不同的编程语言为Java 所用,它的初衷是融合C/C++ 程序,Java 诞生的时候是C/C++ 横行的时候,要想立足,必须有一个聪明的、睿智的调用C/C++ 程序,于是就在内存中专门开辟了一块区域处理标记为native 的代码,它的具体做法是Native Method Stack 中登记native 方法,在Execution Engine 执行时加载native libraies 。目前该方法使用的是越来越少了,除非是与硬件有关的应用,比如通过Java 程序驱动打印机,或者Java 系统管理生产设备,在企业级应用中已经比较少见,因为现在的异构领域间的通信很发达,比如可以使用Socket 通信,也可以使用Web Service 等等,
Heap 堆内存
jvm中分为堆和方法区,堆又进一步分为新生代和老年代, 方法区为永久代
堆中区分的新生代和老年代是为了垃圾回收,新生代中的对象存活期一般不长,而老年代中的对象存活期较长,所以当垃圾回收器回收内存时,新生代中垃圾回收效果较好,会回收大量的内存,而老年代中回收效果较差,内存回收不会太多。
基于以上特性,新生代中一般采用复制算法,因为存活下来的对象是少数,所需要复制的对象少,而老年代对象存活多,不适合采用复制算法,一般是标记整理和标记清除算法。
因为复制算法需要留出一块单独的内存空间来以备垃圾回收时复制对象使用,所以将新生代分为eden区和两个survivor区,每次使用eden和一个survivor区,另一个survivor作为备用的对象复制内存区。
一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常量放到堆内存中,以方便执行器执行,堆内存分为三部分:
堆内存中还要细分为三个区域:
新生区(伊甸园区) Young/New
养老区old
永久区Perm
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PvI4qRC6-1617693870841)(C:\Users\43895\AppData\Roaming\Typora\typora-user-images\1617689579877.png)]
GC垃圾回收,主要是在伊甸园区和养老区~假设内存满了,OOM,堆内存不够!
在JDK8以后,永久存储区改了个名字(元空间)
新生区
●类:诞生和成长的地方,甚至死亡;
●伊甸园,所有的对象都是在伊甸园区new出来的!
●幸存者区(0,1)
老年区
真理:经过研究, 99%的对象都是临时对象!
永久区
这个区域常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据, 存储的是ava运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭虚拟机就会释放这个区域的内存
启动类,加载了大量的第三方jar包。Tomcat部署了太多的应用,大量动态生成的反射类。不断的被加载。直到内存满,就会出现OOM;
●jdk1.6之前:永久代,常量池是在方法区;
●jdk1.7 :永久代,但是慢慢的退化了,去永久代,常量池在堆中
●jdk1.8之后:无永久代,常量池在元空间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uCpetWoR-1617693870842)(C:\Users\43895\AppData\Roaming\Typora\typora-user-images\1617689791141.png)]
OOM;
●jdk1.6之前:永久代,常量池是在方法区;
●jdk1.7 :永久代,但是慢慢的退化了,去永久代,常量池在堆中
●jdk1.8之后:无永久代,常量池在元空间
[外链图片转存中…(img-uCpetWoR-1617693870842)]