JVM内存模型

JVM内存结构

根据java虚拟机规范,JVM内存结构包括:程序计数器,虚拟机栈,本地方法栈,Java堆,方法区(永久代)
特别说明下,JVM内存模型和Java内存模型是不一样的

  • JMM规定了所有的变量都存储在主内存(Main Memory)中。每个线程还有自己的工作内存(在CPU层面就是CPU的缓存),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成。
  • Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节,jmm 只是一直模型定义 一种规则。jvm内存结构需要遵循jmm的规则

程序计数器

程序计数器是JVM中较小的内存区域,可以看作是当前线程执行的字节码行号指示器。即每个指令都有一个行号。因为Java支持多线程,每个线程都有独立的程序计数器,是线程私有的。程序计数器的作用就在于,如果进行线程切换,那么当前线程在下次被cpu调度时,可以在中断的位置继续执行。各条线程之间的计数器互不影响,独立存储。
如果线程正在执行的是Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址。
如果正在执行的是Native方法,这个计数器值则为空。
程序计数器是唯一一个在JVM规范中没有规定任何OutofMemoryError情况的区域.。

Java虚拟机栈

此区域是线程私有的,与线程的生命周期相同。每个方法在被执行的时候都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。方法在开始执行的时候栈帧就入栈,执行完后就出栈,满足“先进后出”的规律。

  • 局部变量表:用于存储编译时期可预知的8种基本数据(byte,char,short,int,long,float,dubble,boolean)和对象的引用。
    此区域会抛出两种异常:
    1.如果线程请求的栈深度大于虚拟机允许的深度将抛出StackOverflowError异常;
    2.如果虚拟机栈可以动态的扩展,扩展到无法动态的申请到足够的内存时会抛出OutofMemoryError异常。

本地方法栈

java的某些操作可能会涉及到其他语言,例如C/C++。那么可以在这区域提供一个native方法,来调用其他语言实现的方法。
本地方法栈与虚拟机栈发挥的作用非常相似,区别就是虚拟机栈为虚拟机执行Java方法,本地方法栈则是为虚拟机使用到的Native方法服务。
Sun HotSpot虚拟机直接将Java虚拟机栈和本地方法栈合二为一。

堆内存是线程共享区域,JVM垃圾回收和内存分配操作的主要区域即GC管理的主要区域。
堆内存可以细分为新生代和老年代。注意:永久代不属于对内存中

  • 新生代:新生代可以细分为一个Eden和一个from survivor,一个to survivor。可以通过-XX:SurvivorRatio=n 来设置Eden和两个Survivor的比值,如:n=3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
  • 老年代:

方法区

方法区也是所有线程共享区,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。GC在这个区域很少出现,这个区域内存回收的目标主要是对常量池的回收和类型的卸载,回收的内存比较少,所以也有称这个区域为永久代(Permanent Generation)的。当方法区无法满足内存分配时抛出OutofMemoryError异常。
此外方法区并不等同于永久代,由于HotSpot设计团队将GC分代收集拓展到方法区,也可以说永久代就是方法区的实现。
通过XX:MaxPremSize来设置永久代的上限

运行时常量区

运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。
垃圾收集(Garbage Collection)并不是Java独有的,最早是出现在Lisp语言中,它做的事就是自动管理内存,也就是下面三个问题:
1、什么时候回收
2、哪些内存需要回收
3、如何回收
1、什么时候回收?
上面说到GC经常发生的区域是堆区,堆区还可以细分为新生代、老年代,新生代还分为一个Eden区和两个Survivor区。
1.1 对象优先在Eden中分配,当Eden中没有足够空间时,虚拟机将发生一次Minor GC,因为Java大多数对象都是朝生夕灭,所以Minor GC非常频繁,而且速度也很快;
1.2 Full GC,发生在老年代的GC,当老年代没有足够的空间时即发生Full GC,发生Full GC一般都会有一次Minor GC。大对象直接进入老年代,如很长的字符串数组,虚拟机提供一个-XX:PretenureSizeThreadhold参数,令大于这个参数值的对象直接在老年代中分配,避免在Eden区和两个Survivor区发生大量的内存拷贝;
1.3 发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则进行一次Full GC,如果小于,则查看HandlePromotionFailure设置是否允许担保失败,如果允许,那只会进行一次Minor GC,如果不允许,则改为进行一次Full GC。
2、哪些内存需要回收
jvm对不可用的对象进行回收,哪些对象是可用的,哪些是不可用的?Java并不是采用引用计数算法来判定对象是否可用,而是采用根搜索算法(GC Root Tracing),当一个对象到GC Roots没有任何引用相连接,用图论的来说就是从GC Roots到这个对象不可达,则证明此对象是不可用的,说明此对象可以被GC。对于这些不可达对象,也不是一下子就被GC,而是至少要经历两次标记过程:如果对象在进行根搜索算法后发现没有与GC Roots相连接的引用链,那它将会第一次标记并且进行一次筛选,筛选条件是此对象有没有必要执行finalize()方法,当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用执行过一次,这两种情况都被视为没有必要执行finalize()方法,对于没有必要执行finalize()方法的将会被GC,对于有必要有必要执行的,对象在finalize()方法中可能会自救,也就是重新与引用链上的任何一个对象建立关联即可。
3、如何回收
选择不同的垃圾收集器,所使用的收集算法也不同。
在新生代中,每次垃圾收集都发现有大批对象死去,只有少量存活,则使用复制算法,新生代内存被分为一个较大的Eden区和两个较小的Survivor区,每次只使用Eden区和一个Survivor区,当回收时将Eden区和Survivor还存活着的对象一次性的拷贝到另一个Survivor区上,最后清理掉Eden区和刚才使用过的Survivor区,Eden和Survivor的默认比例是8:1,可以使用-XX:SurvivorRatio来设置该比例。
而老年代中对象存活率高,没有额外的空间对它进行分配担保,必须使用“标记-清理”或“标记-整理”算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值