JVM内存模型
对于大多数应用来说,Java堆是java虚拟机锁管理的内存中最大的一块。java堆是被所有线程共享的一块区域,在虚拟机启动时创建。
由上图可以清楚的看到JVM的内存部分分为三大部分分别是:堆内存,方法区,栈内存。其中栈内存可以在细分为java虚拟机和本地方法栈,堆内存可以划分为新生代和老年代,新生代中还可以再划分为Eden区,FromSurvivor区和ToSurvivor区,关于这个可以看我之前转载的一篇文章,戳我 ,其中一部分是线程共享的,包括Java堆和方法区;另一部分是线程私有的,包括虚拟机和本地线程栈,以及程序计数器这一小部分内存。
堆内存
对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。
此内存区域唯一的目的就是存放对象实例,几乎所有的对象实例都在这分配内存。
堆内存是所有线程共有的,可以分为两为两个部分;年轻代和老年代。
下图的Perm代表的是永久代。但是注意永久代并不属于堆内存中的一部分,同时java1.8之后永久代已经被移除。
新生代(young)和老年代(old)的比例是1:2(该数值可以通过参数 -XX:NewRatio来制定 )
默认的,Eden:from:to = 8:1:1 (可以通过参数-XXSurvivorRatio 来设定)
年轻代中为什么要有Survivor区
如果没有Survivor,Eden区每进行一个Minor GC,存活的对象就会送到老年区,那么可想而知,老年区很快就被填满,触发Major GC (因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC) 老年代的内存空间远比新生代,进行一次Full GC消耗的时间比Minor GC长的多。这一点在大型程序中是非常影响执行和响应速度的。
因此,我们可以得到结论:Survivor的存在意义就是减少被传送到老年化的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
为什么要设置两个Survivor区
设置两个Survivor区最大的好处就是解决了碎片化。碎片化带来的冯先生极大的,严重影响Java程序的性能。堆空间被散布的对象占据不连续的内存,最直接的结果是,对重没有足够大的内存空间,接下去如果程序需要给一个内存需求很大的对象分配内存,那…。
那么要怎么解决呢,开发者就想了个办法,创立了两块survivor区,这里分别为S0,S1。首先当我们的对象在Eden区占满了之后,就启动了Minor GC,然后把Eden中存活的对象放在S0,然后清空Eden,然后在等Eden区慢了之后,就利用复制算法把Eden和S0中存活的对象复制到S1,然后把Eden区和S0区清空,然后把S0和S1互换角色。这个过程很重要要特别说明一下,因为复制算法保证了Eden和S0中的两部分内存在S1中占用连续的空间,这就避免了碎片化发生。
方法区
方法区也称为“永久i代”,它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。再JDK8之前的HotSpot JVM,存放这些“永久的”区域叫做永久代。永久代是一片连续的堆空间,在jvm启动之前通过命令设置参数-XX:MaxPermSize来设定永久代最大可分配的内存空间,默认大小事64M(64为JVM默认是85M)。
随着JDK8的到来,JVM不再有永久代。但可的元数据信息还在,只不过不再是存储在连续的堆空间上,而是移动到叫“Metaspace”的本地内存。
方法区或永生代相关设置
虚拟机栈(JVM Stack)
描述的是java方法执行的内存模型:每个方法被执行的时候都会创建一个“栈帧”,用于存储局部变量表(包括参数),操作栈,方法出口等信息。每个方法被调用到执行完的过程,就对应一个栈帧在虚拟机中从入栈到出栈的过程。
本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务(native暗示这些方法是有实现体的,只不过这些实现体是非java的)。
程序计数器
程序计数器是用于标示当前线程执行的字节码文件的行号指示器。多线程情况下,每个线程都具有各自独立的程序计数器,所以该区域是非线程共享的内存区域。
当执行java方法时,计数器中保存的是字节码文件的行号,当执行Native方法时,计数器的值为空。
直接内存
直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小。