java虚拟机(java virtual machine stacks)在执行java程序的过程中会把它管理的内存分为若干个不同的数据区域,主要包括方法区,堆,栈,直接内存4大块,和一个小块--程序计数器。
程序计数器:每个线程都有一个独立的计数器,如果线程正在执行的是java方法,则这个计数器记录的是正在执行的虚拟机字节码指令地址,如果正在执行的是native方法,这个计数器的值为空,此内存区域是唯一一个在java虚拟机中没有规定任何outofMemoryError情况的区域。
栈:栈也是线程私有的,他的生命周期与线程相同。虚拟机栈是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用到执行完成,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表中存放了编译期可知的基本数据类型,对象引用,和returnaddress类型,局部变量表所需的内存在编译器就完成分配,当进入一个方法时,需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小,如果线程请求的栈深度大于虚拟机所允许的,责抛出StackOverflowError。如果扩展时无法申请到足够的内存就会抛出outOfMemoryError异常。
栈中还分本地方法栈,用来存放虚拟机用到的native方法。还有一个reference表用来存放堆对象的引用。
堆:堆是被所有线程共享的一块内存区域,他的唯一目的就是存放实例变量,几乎所有的对象实例以及*****数组*****都在堆上分配。java堆是GC管理的主要区域,java堆可以处于不连续的内存空间中,如果不能申请更多空间完成扩展的时候会抛出outOfMemoryError。
方法区:和堆一样,他也是被所有线程共享的。存储已被加载的类信息(类的版本,字段,方法,接口等信息),常量,静态变量,******即时编译器编译后的代码******等数据。方法区是堆的一个逻辑部分。
运行时常量池是方法区的一个部分,用于存放编译期生成的各种字面量和符号引用,常量池中的常量在类加载后进入方法区的运行时常量池中存放。运行期也可以将新的常量放入池中,比如String.intern()方法。
方法区被叫做永久代,(PermGen)也叫做非堆
java1.8以后方法区被移除,取而代之的是MetaSpace http://ifeve.com/java-permgen-removed/
他把windows的虚拟内存,linux的交换空间用来存储以前方法区的内容,所以以后不存在out of menory现象。
直接内存:它不是虚拟机运行时数据区的一个部分,现在用于NIO。如果有很多NIO的操作,需要适当调小堆内存大小,否则会出现内存溢出out of direct memory。
java中,类型的加载,连接和初始化过程都是在程序运行期间完成的。
栈是线程私有的,但是堆和方法区是线程共有的。 所以栈资源随着线程的结束而结束,不需要GC,而堆需要GC。
堆一般被称为jvm可用内存。
堆中分为新生代和老年代,新生代中包括eden和survivor,默认比例为8:1; survivor用于确保在GC时,内存区域的连续性(标记-整理,复制算法),以提高效率。
新生代GC : minor GC
老年代GC:Major GC/Full GC 出现了major GC往往会伴随一次Minor GC 但非绝对,Major GC的速度比Minor GC慢10倍以上。
老年代的判定:
1.如果对象在eden出生并且经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到survivor空间中,并且对象年龄设为1,
对象在Survivor区中每熬过一次Minor GC ,年龄就会增加1,当他的年龄增加到一定程度(默认是15岁),就将会被晋升到老年代中,对象晋升
老年代的阀值可以通过参数 -XX:MaxTenuringThreshold 设置。
2.如果在survivor空间中的相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代,无需
等到XX:MaxTenuringThreshold中要求的年龄。
空间分配担保机制(Full GC/ Minor GC判定:):
jdk1.6 update24之后handlePromotinoFailure设置值失效。
规则变为只要老年代的连续空间大于新生代对象总大小或者大于历次晋升的平均大小就会进行Minor GC 否则进行 Full GC
对象的创建:
虚拟机遇到一条new指令时,
1.首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个类是否被夹在,解析和初始化过,如果没有,那就先去类加载,
2.加载完成后,接下来虚拟机将为新生对象分配内存,对象所需的内存大小在类加载完之后就可以完全确定,注意是类加载完成之后!(tips:类加载不是实例对象的加载,紧仅仅只是从class文件中把类读入方法区中,1.8版本为MetaSpace中。)
3. 内存分配完之后,虚拟机将对分配到的内存空间初始化为0值。(不包括对象头)
4. 接下来虚拟机要对对象头进行必要的设置,例如这个对象头是哪个类的实例,这些信息放在对象的对象头中。
5.接下来将会执行init方法,程序按照程序员的意愿进行初始化,即调用构造方法。