JVM是什么?
JVM属于JDK下的JRE体系中的一个底层虚拟机。Java的跨平台也是因为JVM虚拟机的存在而存在的特性。
听说华为有一个去除了JVM虚拟器的工程,不知道是如何实现的,等有时间了,研究一下。
JVM模型图(记忆流程)
类装载子系统
类的加载,通过类加载器进行加载类。注意new 的对象都在堆中,而加载的类信息会存在方法区中,并且创建一个Class类到堆中。(实际执行时使用的是方法区中类信息,而堆中用于Java获取类的信息)
字节码执行引擎(待验证)
读取方法区中类信息进行执行,实时修改程序计数器,进行程序执行。注意new 的对象都在堆中。
内存模型
单个线程独有的
在JVM中单个线程独有线程栈、本地方法栈、程序计数器(PC寄存器)
栈(线程栈)
栈中又包括栈帧,而栈帧又包括局部变量表、操作树栈、动态链接、方法出口。
- 局部变量表
储存着方法中局部使用的变量,存储基本数据类型时,直接存储。存储引用数据类型时,存储堆中的内存地址。
- 操作数栈
在操作数据时,存放数据进行处理。通过压栈出栈将数据进行计算与赋值(底层存在操作数据计算与赋值方法)
- 动态链接
将其中未替换的非静态方法(字符)替换为实际的方法内存地址,我的理解是为了在多态的情况下正确获取实际实体对象的方法。
- 方法出口
存储调用该方法的位置信息,与返回数据信息等。
进栈流程
调用方法逐行读取
- 读取到变量时,分配局部变量表内存空间,将数据压入操作数栈中进行操作,当存在其他变量时依次入栈,再进行计算。
- 读取到方法时,在线程栈中压入新的栈帧,从新的方法中开始逐行读取。
- 读取到return,读取方法出口。栈帧出栈,根据方法出口信息,到指定位置开始执行。
本地方法栈
由C++进行编写,在JVM掉用本地方法或C++语言时进行压栈。早期调用C++程序方法,或调用底层方法时使用。
程序计数器(PC寄存器)
用于线程标记当前执行的位置,在CPU切换时,保证正常的执行位置。
线程共有的
堆
堆中分为新生代与老年代,其中新生代还分为Eden(伊甸园区), Survivor1(幸存者一区),Survivor2(幸存者二区)
- 新生代
大小比例:Eden:Survivor1:Survivor2=8:1:1
- Eden(伊甸园区)
新new的对象存储在该区域
- Survivor1(幸存者一区)
默认经历过低于15次minor gc的存储在该区域
- Survivor2(幸存者二区)
默认经历过低于15次minor gc的存储在该区域
- Eden(伊甸园区)
- 老年代
默认经历了15次minor gc后还存活的对象存储区域,还包括大对象(新生代无法装载),以及对象动态年龄判断的对象。
入堆流程
-
new 一个对象时进入新生代的Eden(伊甸园区)
-
当Eden(伊甸园区)满时,进行minor gc,还生存的对象进入Survivor1(幸存者一区)或Survivor2(幸存者二区),只进入其中一个。
minor gc会将一个Survivor区与Eden(伊甸园区)都进行gc,而Survivor区中会一直存在一个空的Survivor1或2区。
-
当对象头中记录经历了15次gc时,进入老年代。
当存在Survivor区中存储超过50%,从1加到n对象(加上就超过50%的对象)以外即超过n对象年龄的对象进入老年代。(对象动态年龄判断)
-
当老年代满时,触发Full gc(该gc包括新生代也会进行gc),诺gc后还是满的,抛出OOM错误。
注意:GC可能触发STW机制,即Stop the workd(停止世界),此机制有以下原因:
- 在收集非垃圾对象(可达性对象)时,root对象被销毁造成已收集的对象成为垃圾对象,从而造成收集大量无用对象。而由于未暂停,可能收集无数垃圾对象,无法达到释放内存目的。
- 降低GC方法实现难度,如不进行暂停,GC方法实现需要检查已收集的对象是否成为垃圾对象。
元空间(方法区)
存储常量、静态变量、类信息,静态变量为引用数据类型时,存储堆中的内存地址。
注意:该方法区中的数据直接存储在内存中,64位默认为21MB,如war包很大或引用非常多的类时,注意需要扩大元空间大小,因为当元空间大小到达触发Full gc的阈值时会进行Full gc,未调整会造成反复Full gc。