文章目录
一、JVM虚拟机组成
包括下图中三部分:类装载子系统、运行时数据区(内存区域)、字节码执行引擎。
二、JVM内存
-
堆
Java虚拟机所管理内存中的最大的一块,在虚拟机启动时创建,被所有线程共享。 -
栈
虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。虚拟机栈式线程所私有的,独有的,随着线程的创建而创建。每一个线程执行的方法,为该栈中的栈帧,即每一个方法对应一个栈帧。 -
方法区
方法区是所有线程所共享的内存区域,在虚拟机启动的时候创建。保存常量、类信息、静态变量。如果方法区中的变量对应的是对象,则该变量存储该对象在堆中的内存地址。 -
程序计数器
程序计数器占用的内存空间很小,在任意时刻,一个处理器只会执行一条线程中的指令,因此,为了线程切换后能够恢复到正确的执行位置,每条线程需要有一个独立的程序计数器(线程私有)。如果线程正在执行Java方法,则程序计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,则这个计数器为空。 -
本地方法栈
如果当前线程执行的方法是Native类型,这些方法就会在本地方法栈中执行。
三、程序执行
1、线程内存组成
当线程执行时,JVM会给该线程分配栈内存(符合FILO标准,即先进后出),同时会从程序计数器中划分一部分空间分配给该线程。不同方法有独立的内存空间,因此栈进一步划分为栈帧,每一个栈帧对应一个方法。
2、栈帧组成
-
局部变量表
每个方法对应的局部变量,存储在栈帧的局部变量表中。特别的,当局部变量代表一个对象时,该局部变量存放的是对象在堆中的内存地址。 -
程序计数器
而线程私有的程序计数器,是记录程序执行指令的地址,当有执行级别更高的线程抢走CPU时间片,程序计数器会记录当前执行指令的地址(程序计数器由字节码执行引擎进行修改),当前线程停止,等高级别线程执行完毕并还回CPU时间片时,当前线程从程序计数器中读取并接着进行执行。 -
操作数栈
存储程序执行过程中,存放执行语句的临时数据。 -
动态链接
存放方法在方法区的入口地址。(待补充) -
方法出口
存放调用该方法时的下一步代码的地址。
四、垃圾回收机制(GC)
1、意义
垃圾回收机制使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使用空闲的内存。
PS:内存泄露是指该内存空间使用完毕之后未回收,在不涉及复杂数据结构的一般情况下,Java的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度,我们有时也将其称为“对象游离”。
2、STW(stop the world)
gc的过程中会执行STW,即停止当前线程执行,等gc完成之后,再重新恢复。
为什么要执行STW?
因为如果gc的过程中不STW,线程和gc同时运行,这样的话会出现类似于线程不安全的问题,比如gc认为该变量不是垃圾,而线程执行完成之后,该对象成为了新的垃圾,这时候就会出现gc不彻底的问题。
3、JVM调优,如何不发生full gc
思路:尽可能增大年轻代空间,尽量不让对象移至老年代。
4、OOM的触发条件,降低GC的调优的策略
- gc与非gc时间耗时超过了GCTimeRatio的限制引发OOM;
- 调优诸如通过NewRatio控制新生代老年代比例,通过 MaxTenuringThreshold控制进入老年前生存次数等。
5、minor gc/full gc的触发条件
- Minor GC:
eden区满时,触发MinorGC(即申请一个对象时,发现eden区不够用,则触发一次MinorGC)
注:新生代分为三个区域,eden space, Survivor0 space, Survivor1 space。默认比例是8:1:1。在MinorGC时,会把存活的对象复制到Survivor区域,如果Survivor区域不够,则利用担保机制进入老年代区域
- Full GC:
1.老年代空间不够分配新的内存(或永久代空间不足,但只是JDK1.7有的,这也是用元空间来取代永久代的原因,可以减少Full GC的频率,减少GC负担,提升其效率)
2.GMS GC时出现promotion failed(在进行minorGC时,survivor space放不下,分配担保时担保失败,对象只能放入老年代,然而此时老年代也放不下), concurrent mode failure(在进行GMS GC过程中同时有对象要放入老年代中,而此时老年代空间不足)
3.Minor GC晋升到老年代的平均大小大于老年代的剩余空间
4.System.gc()【只是提醒一下虚拟机需要回收对象,但是是否回收,还是由虚拟机决定】
5.使用RMI来进行RPC或管理的JDK应用,每小时执行1次Full GC