jvm-内存模型

    java代码经过编译后形成class(字节码)文件,jvm将class文件加载到内存运行。那么jvm是如何为类的实例分配内存的呢?这时候就需要了解jvm的内存模型

  从图片可以看出,jvm内存大体分为四部分

  1. 堆内存  Heap
  2. 方法区  PermGen
  3. 栈内存  Stack
  4. 程序计数器 Program Counter   (图片没有反映出)
  • 堆内存

  java Heap是jvm管理的内存中最大的一块,是被所有线程共享的一块区域。它里面主要是存放类的实例对象。堆内存可以细分为新生代(Young)和老年代(Old),默认情况下Young:Old =  1:2,

该比例可以通过 –XX:NewRatio 来改变。新生代又可以细分为Eden,From Survior, To Survivor,默认情况下 Eden:From Survior:To Survivor =  8:1:1,该比例可以通过–XX:SurvivorRatio来改变

-Xms:堆的初始化大小

-Xmx:堆的最大值

-XX:newSize : 新生代的初始化内存大小,应该小于-Xms

-XX:MaxnewSize:新生代的可分配的最大内存,应该小于-Xmx

-Xmn:对于-XX:newSize 和-XX:MaxnewSize同时分配,即-XX:newSize=-XX:MaxnewSize=-Xmn

-XX:PermSize: 永久代初始化大小,永久代不属于堆区域;jdk1.8之后变成了元数据MetaSpace

-XX:MaxPermSize:永久代最大值

 

  • 逃逸分析与栈上分配

     java堆区不是给实例对象分配内存的唯一选择,目前有种堆外存储技术,利用逃逸分析刷选出未发生逃逸的对象,然后避开堆区,直接在栈帧中分配内存。

逃逸分析:确定对象的作用域,当一个对象被定义在方法体内部后,它的受访问权限仅限于方法体内部,一旦被外部对象引用,那么这个对象就发生了逃逸;

如果这个对象没有被外部对象引用,jvm就会为它在栈帧中分配内存空间。  

 在栈帧上分配的对象,gc无须执行垃圾回收。栈帧会随着方法的调用而创建,随着方法的结束而销毁。因此栈帧上分配的对象所占用的内存空间会随着栈帧的

出栈而释放。

 

  • 年轻代与老年代

对象优先分配在Eden区,如果Eden区没有足够的空间,则执行一次Minor GC;

大对象(需要大量的内存空间)直接分配在老年代;

长期存活的对象进入老年代,jvm为每个对象定义了一个年龄计数器,如果对象经过了一次Minor GC,那么对象会进入Survivor区,之后每经历一次Minor GC,那么对象的年龄+1

直到达到一定的阀值进入老年区;

 

  • Minor GC与Full GC

Minor GC:新生的对象一般都放在Eden区,因为它们很快变得不可到达,当对象从Eden区消失时,就是发生了一次Minor GC

Full GC:老年区长期存活的对象消失的时候,就是发生了一次Full GC

发生GC的时候,系统会产生一定的停顿,整个应用程序会被终止,直到GC完成

 

  • 引用计数法与根搜索算法 

引用计数法:对于一个对象A,只要任何一个对象引用了A,则A的引用计数器+1,当引用失效的时候,引用计数器-1。当A的引用计数器为0的时候,则标记为死亡对象,等待GC回收。

但是引用计数法有个很严重的问题,无法处理循环引用的问题。即当A对象引用了B对象,B对象也引用了A对象;除此之外,没有任何对象引用它们。A和B应该是被回收的垃圾对象,

但是引用计数法无法识别

根搜索算法 :按照根对象(GC Root)为起点,按照从上到下的方式,搜索被GC Root所连接的目标对象是否可到达。如果目标对象不可到达,那么该对象就是被标记为死亡对象,

等待GC回收

GC Root 组成:

1.java栈中的对象引用

2.本地方法栈中的对象引用

3.运行时常量池中的对象引用

4.方法区中的静态属性的对象引用

5.与某个类对应的Class对象

 

如图所示,user3,user5从GC Root不可达到,因而会被标记为垃圾对象。

 

  • 垃圾回收算法

标记清除算法:分为标记阶段和清除阶段。标记阶段,标记所有由GC Root触发的可到达的对象,此时所有未被标记的对象就是垃圾对象;清除阶段,清除未被标记的垃圾对象。标记清除算法会导致内存空间碎片过多,内存空间不连续

 复制算法:将内存空间分为两块,每次只使用其中一块,在垃圾回收的时候,将内存中存活的对象复制到另一块内存空间中,之后清除内存中的垃圾对象,最后互换两个内存空间的角色。该算法的缺点是将内存空间折半,浪费内存空间

标记压缩算法:分为标记阶段和压缩阶段。标记阶段,标记所有由GC Root触发的可达到的对象;压缩阶段,将所有存活的对象压缩到一块(减少空间碎片),之后清理其它空间的垃圾对象

标记清除算法会产生空间碎片,但是不会移动对象。比较适合存活对象较多的情况;复制算法需要将内存空间折半,并且需要移动存活对象,但是不会产生空间碎片。比较适合存活对象较少的情况;标记压缩算法在标记清除算法上做了优化,减少了空间碎片

 

  • 分代思想

根据jvm内存的不同区域采用不同的垃圾回收算法

新生代:存活对象较少,适合复制算法,只需复制少量对象就可以完成垃圾回收,并且不会产生空间碎片;新生代分为Eden区,From Survivor区,To Survivor区,默认大小比例是8:1:1.垃圾回收时,将Eden区和Survivor区的存活对象复制到另一块Survivor区域,之后清理Eden区和使用的Survivor区中的垃圾对象。这里为什么内存空间不是按照1:1的比例来划分的呢?根据IBM公司大量研究表明,新生代中98%都是朝生夕死的,移动的存活对象很少。因此设置Eden:S0:S1 = 8:1:1 ,提高内存的利用率  注意:S0和S1每次只有一个内存空间是可用的

实验:jvm参数设置 -Xms20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=2   堆初始化内存20m,新生代10m Eden:S0:S1 = 2:1:1  打印GC日志如下

Heap
PSYoungGen total 7680K, used 4309K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
eden space 5120K, 84% used [0x00000000ff600000,0x00000000ffa35660,0x00000000ffb00000)
from space 2560K, 0% used [0x00000000ffd80000,0x00000000ffd80000,0x0000000100000000)
to space 2560K, 0% used [0x00000000ffb00000,0x00000000ffb00000,0x00000000ffd80000)
ParOldGen total 10240K, used 0K [0x0000000088600000, 0x0000000089000000, 0x00000000ff600000)
object space 10240K, 0% used [0x0000000088600000,0x0000000088600000,0x0000000089000000)
Metaspace used 3348K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 363K, capacity 388K, committed 512K, reserved 1048576K

total(可用内存)=7680K  eden=5120K  from=2560K  to=2560K  OldGen=10240  

total=eden+from或者total=eden+to   因此同一时间from space和to space只有一个区域是可用的

 老年代:存活对象较多,适合标记清除算法或者标记压缩算法,因为不需要移动大量对象

  • 方法区

 方法区又称为永久代,主要存放加载的类的信息(class对象,从class能反映出类的结构,包括类的方法,属性,注解等)和类的静态变量,静态方法。方法区是被所有线程共享的一片区域。jdk1.8后,移除永久代PermGen,而是称为元数据MetaSpace。

  • 栈内存

 栈内存分为本地方法栈(Native Stack)和虚拟机栈(JVM Stack),栈内存是非线程共享的区域。

JVM Stack:每个方法被执行的时候都会创建一个栈帧,栈帧里面存储方法里面的局部变量,方法的参数,方法的出口等信息。每个方法从被调用到执行完,对应着一个栈帧从入栈到出栈的过程。

Native Stack:和JVM Stack类似,Native Stack是为java 中的native方法服务的。

总之如果调用方法就会涉及到本地方法栈,虚拟机栈等结构

  • 程序计数器

 程序计数器的作用是保存当前线程正在执行的方法。如果该方法不是Native,那么它保存的是jvm正在执行的字节码指令地址;如果该方法是Native,那么它保存的是空。

每个线程都具有各自独立的程序计数器,所以该区域是非线程共享的内存区域。

 

参考资料:

https://www.cnblogs.com/chanshuyi/p/jvm_serial_05_jvm_bytecode_analysis.html

https://www.cnblogs.com/rinack/p/9888692.html

 

posted @ 2019-07-18 14:27  兵哥无敌  阅读(305)  评论(0编辑  收藏  举报