JVM内存模型
一,什么是JVM
JVM是Java Virtual Machine的简称,Java程序的运行环境(Java二进制字节码的运行环境)
好处:
- 一次编写到处运行。
- 自动内存管理,垃圾回收机制。
JVM的执行流程
从图中可以看出 JVM 的主要组成部分
- ClassLoader(类加载器)
- Runtime Data Area(运行时数据区,内存分区)
- Execution Engine(执行引擎)
- Native Method Library(本地库接口)
运行流程:
(1)类加载器(ClassLoader)把Java代码转换为字节码
(2)运行时数据区(Runtime Data Area)把字节码加载到内存中,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层系统去执行,而是有执行引擎运行
(3)执行引擎(Execution Engine)将字节码翻译为底层系统指令,再交由CPU执行去执行,此时需要调用其他语言的本地库接口(Native Method Library)来实现整个程序的
二,JVM的组成
1.程序计数器
程序计数器:他是线程私有
的,内部保存的是字节码的行号。用于记录正在执行的字节码指令的地址。
2.堆
堆:线程共享的区域,主要是用来保存对象实例,数组等。当堆中没有空间,也没有办法扩展的时候,就会抛出OutOfMemoryError(OOM)的异常。
在Java8中堆中分为了2部分,年轻代+老年代
1.Young
区被划分为三部分,Eden
区和两个大小严格相同的Survivor
区,其中,Survivor
区间 中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用。在Eden
区变满的 时候, GC
就会将存活的对象移到空闲的Survivor
区间中,根据JVM的策略,在经过几次垃圾收集 后,任然存活于Survivor
的对象将被移动到Tenured
区间。
2.Tenured
区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转 移一定的次数以后,对象就会被转移到Tenured区。
Java7和Java8堆的区别?
Java7堆中有一个永久代
:存储的是类信息
,静态变量
,常量
,编译后的代码
等。
Java8移除永久代
,将数据存储到本地内存的元空间
中,防止内存溢出。
3.方法区
方法区(线程共享的区域):
- 在jdk1.8之前方法区在永久代里,在jdk1.8中方法区在元空间中。
- 在虚拟机启动的时候创建在虚拟机关闭的时候释放。
- 用来存储虚拟机加载的类信息、常量、静态变量、及编译器编译后的代码等数据。
我们在判断某个变量再那个区域上时,我们需要把握以下原则:
1.局部变量在栈
2.普通成员变量在堆
3.静态成员变量再方法区/元数据区
4.直接内存
它又叫做堆外内存
,线程共享的区域
,在 Java 8 之前有个永久代
的概念,实际上指的是 HotSpot
虚拟机上的永久代
,它用永久代实现了 JVM 规范定义的方法区功能,主要存储类的信息,常量, 静态变量,即时编译器编译后代码等
,这部分由于是在堆中实现的,受 GC 的管理,不过由于永久 代有 -XX:MaxPermSize 的上限,所以如果大量动态生成类(将类信息放入永久代),很容易造成 OOM,有人说可以把永久代设置得足够大,但很难确定一个合适的大小,受类数量,常量数量的 多少影响很大。
所以在 Java 8 中就把方法区的实现移到了本地内存中的元空间中,这样方法区就不受 JVM 的控 制了,也就不会进行 GC,也因此提升了性能。
5.虚拟机栈
虚拟机栈:是描述的是方法执行时的内存模型,是线程私有
的,生命周期与线程相同,每个方法被执行 的同时会创建栈桢。保存执行方法时的局部变量
、动态连接信息
、方法返回地址信息
等等。方法开 始执行的时候会进栈,方法执行完会出栈【相当于清空了数据】,所以这块区域不需要进行 GC。
堆和栈的区别?
第一,栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的的。堆 会GC垃圾回收,而栈不会。
第二、栈内存是线程私有的,而堆内存是线程共有的。
第三、两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常。
栈空间不足:java.lang.StackOverFlowError。
堆空间不足:java.lang.OutOfMemoryError。
三,类加载器
**类加载器:**JVM只会执行二进制文件,类加载器的所用就是将二进制文件加载到JVM中,让Java文件可以运行。
类加载器的类型:
第一个是启动类加载器(BootStrap ClassLoader
):其是由C++编写实现。用于加载 JAVA_HOME/jre/lib目录下的类库。
第二个是扩展类加载器(ExtClassLoader
):该类是ClassLoader的子类,主要加载 JAVA_HOME/jre/lib/ext目录中的类库。
第三个是应用类加载器(AppClassLoader)
:该类是ClassLoader的子类,主要用于加载classPath下 的类,也就是加载开发者自己编写的Java类。
第四个是自定义类加载器:开发者自定义类继承ClassLoader
,实现自定义类加载规则。
类装载的执行过程
1.加载:查找和导入class文件
2.验证:保证加载类的准确性
3.准备:为类变量分配内存并设置类变量初始值
4.解析:把类中的符号引用转换为直接引用
5.初始化:对类的静态变量,静态代码块执行初始化操作
6.使用:JVM 开始从入口方法开始执行用户的程序代码
7.卸载:当用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也 退出内存
双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把这请求委派给父 类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传说到顶层的 启动类加载器中,只有当父类加载器返回自己无法完成这个加载请求(它的搜索返回中没有找到所 需的类)时,子类加载器才会尝试自己去加载(收到加载请求后自己不去加载,交由父类去加载,一直往上直至到最顶层的父类,当父类无法加载时,自己在区加载
)
使用双清委派模型有什么好处?
1.避免一个类被重复加载,当父类加载后子类无需再次加载,保证了唯一性。
2.为了安全保证类库APi不会被修改。
四,垃圾回收
1.什么样的对象可以被回收
如果一个或多个对象没有任何引用指向他了,那么这个对象就时垃圾,可以提被回收掉。
二中方法确认垃圾1.引用计数法
2.可达性分析法
**引用计数法:**一个对象被引用一次,子啊当前的对象头上增加一次应荣次数,如果这个对象的引用次数为0,就代表他是垃圾。
可达性分析算法:
2.垃圾回收算法
1.标记清除算法
此算法氛围2个阶段标记
和清除
(1)根据可达性分析算法找到垃圾在进行标记
(2)将标记的垃圾进行清除
优点:标记和清除的速度较快。
缺点:碎片化较为严重,内存不连续
2.标记整理算法
相对于标记清除算法,他多了个移动对象,使得内存连续,但是由于需要移动对象,效率低一点
3.复制算法
将原有的空间一份为2,每次只使用其中的一块,将正在使用(存活)的对象,复制到另一个内存空间中然后将该内存空间清空,交换2个雷村空间的角色,无碎片,空间使用率低。
优点:
- 在垃圾比较多的情况下,效率较高。
- 清理后碎片
缺点:
需要2块内存空间,在同一时刻是能使用一半,内存使用效率较低。
4.分代回收算法
在java8时,堆被分为了两份:新生代和老年代,它们默认空间占用比例是1:2
对于新生代,内部又被分为了三个区域。Eden
区,S0
区,S1
区默认空间占用比例是8:1:1
具体的工作机制是有些情况:
- 当创建一个对象的时候,那么这个对象会被分配在新生代的
Eden
区。当Eden
区要满了时候, 触发YoungGC
。 - 当进行
YoungGC
后,此时在Eden
区存活的对象被移动到S0
区,并且当前对象的年龄会加1,清 空Eden
区。 - 当再一次触发
YoungGC
的时候,会把Eden
区中存活下来的对象和S0
中的对象,移动到S1
区 中,这些对象的年龄会加1,清空Eden区和S0区。 - 当再一次触发
YoungGC
的时候,会把Eden区中存活下来的对象和S1
中的对象,移动到S0
区 中,这些对象的年龄会加1,清空Eden区和S1区。 - 对象的年龄达到了某一个限定的值(默认15岁 ),那么这个对象就会进入到老年代中。 当然也有特殊情况,如果进入
Eden
区的是一个大对象,在触发YoungGC
的时候,会直接存放到老 年代
当老年代满了之后,触发FullGC
。FullGC
同时回收新生代和老年代,当前只会存在一个FullGC
的 线程进行执行,其他的线程全部会被挂起。 我们在程序中要尽量避免FullGC的出现。
MinorGC【YoungGC】:发生在新生代的垃圾回收,暂停的时间短(STW)
**MixedGC:**新生代+老年代部分区域的垃圾回收,G1垃圾回收器持有
**FullGC:**新生代+老年代完成垃圾回收,暂停的时间长,应尽量避免。
**STW:**暂停所有应用程序的线程,等待垃圾回收的完成。
3.常见的垃圾回收器
1.串行垃圾回收器
指使用单线程进行垃圾回收,堆内存较小,适合个人电脑。
Serial:(瑟瑞要)用于新生代,采用复制算法。
**Serial-Old:**适用于老年代的垃圾回收器,采用标记整理算法。
垃圾回收时只有一个线程子工作,并且Java应用中的所有线程都要暂停(STW),等待垃圾回收的完成。
2.并行垃圾回收器
Parallel New和Parallel Old是一个并行的垃圾回收器。JDK8默认使用此垃圾回收器。
Parallel New:(破瑞瑶)用于新生代,采用复制算法。
**Parallel Old:**适用于老年代的垃圾回收器,采用标记整理算法。
垃圾回收时只有一个线程子工作,并且Java应用中的所有线程都要暂停(STW),等待垃圾回收的完成。
3.CMS并发的垃圾回收器
CMS(Concurrent Mark Sweep)是一款并发的垃圾回收期器,使用标记清除算法的垃圾回收器,该回收器是运用于老年代的垃圾回收的,是一款以获取最短回收停顿时间为目标的垃圾回收器,停顿时间短,用户体验就号。其最大的特点就是在进行垃圾回收时,应用任然可以运行。
它是一个以达到在垃圾回收期间用户线程低停顿为目标的垃圾收集器。CMS垃圾收集器垃圾回收分为四个阶段:
1,初始标记:只对与GCRoots有直接关键的对象进行可达性分析的标记。
2,并发标记:标记整个GCRoots引用链中的对象,与用户线程并发执行。
3,重新标记:用于更新在并发标记过程中被复活的对象。
4,并发清除:清除标记阶段判断的已死亡的对象。该流程与用户线程并发执行。
缺点:1,它使用标记清除算***导致内存碎片化,2,会产生在并发清除阶段的浮动垃圾,只有到下一次垃圾回收时才会被清除。
4.G1垃圾回收器
- 应用于新生代+老年代,
在Jdk9之后默认使用
。 - G1收集器将整个堆内存区域划分为多个大小相等的
region
,每个区域都可以充当eden,survival,old,humongous,
其中humongous时为大对象准备的。 - 采用复制算法,
- 响应时间和吞吐量
- 在垃圾会受到过程中分为了三个阶段:年轻代垃圾回收,并发标记,混合回收。
- 在垃圾回收失败的时候(回收的速度赶不上创建新对象的速度),会触发FullGc。
G1垃圾回收器的优点:
- **延迟可控性:**对于较大的堆空间,如超过6G的堆,G1垃圾回收器能够保持较低的延迟,确保应用程序的响应性和性能。
- **内存碎片最小化:**G1通过复制算法进行垃圾回收,避免了内存碎片的产生,从而提高了内存的利用率和应用程序的性能。
- **并发标记的SATB算法:**G1使用高效的并发标记的SATB算法,该算法在标记阶段对应用程序的影响较小,确保了高吞吐量。
- **多CPU并行处理:**G1充分利用多核CPU的计算能力,通过并行处理提高垃圾回收的效率,特别是在处理大规模数据和高负载应用程序时。
G1垃圾回收器的设计特点:
- **支持巨大的堆空间回收,并保证较高的吞吐量:**通过将堆划分为多个相等的区域(Region),G1能够更有效地管理内存,并在回收过程中优先处理含有大量垃圾的对象区域。
- **支持多CPU并行垃圾回收:**G1采用并发收集方式,充分利用多核CPU的计算能力,提高垃圾回收的效率。这使得它在处理大规模数据和高负载应用程序时表现出色。
- **允许用户设置最大暂停时间:**通过调整相关参数,用户可以控制垃圾回收过程中的最大停顿时间,以满足应用程序的性能需求。
年轻代回收(Young GC)
在刚开始时,所有的区域都是空闲的状态,创建了一些对象就会挑出一些空闲的区域当作Eden区,随着对象越来越多Eden就会放满就会触发一个新生代的垃圾回收(G1里新生代的区域占中区域的5%-6%
),采用复制算法进行垃圾回收,先使用可达性分算法确认哪些是垃圾,确认哪些对象是存活下来的,接下来就会用复制算法将存活的对象复制到Survivor区中(在标记和刚刚复制的过程中都需要暂停STW) ,随着时间的流逝,Eden区内存又不足了,这时就会将Eden区以及薛之前Survivor区的存活对象,采用复制算法,复制到新的幸存者(Survivor)区,一些老的对象复制到老年代(Old)中。
并发标记
当老年代占用内存超过阈值(默认是45%)后,触发并发标记,这时无需暂停用户线程。
在并发标记以后,会有重新标记阶段解决漏标问题,此时需要展厅线程。
这些都完成后就知道了老年代有哪些存活对象,随后进入混合收集阶段。此时不会对所有老年代区 域进行回收,而是根据暂停时间目标优先回收价值高(存活对象少)的区域(这也是 Gabage First 名称的由来)
混合回收
回将Eden区和幸存者(Survivor)区复制到一个新的Survivor区,还会将Survivor到了有定阈值的对象和Old区复制到新的old中,在复制完成内存得到释放
G1比CMS好在哪里?
- G1在空间压缩方面有优势
- G1通过Region的方式,很大程度上解决了内存碎片的问题
- Eden,S,Old区不在固定,内存使用上很灵活
- G1可以通过预设停顿时间来控制垃圾回收的时间,避免了应用雪崩的问题
- G1在垃圾回收后会马上同时做合并空闲内存的工作,而CMS则需要STW去干
5.JVM的调优工具
jps:输出JVM中运行的进程状态信息
jstack:查看java进程内线程的堆栈信息