1.栈内存
生命周期与线程相同,是线程私有的。
当方法被执行时,方法体内的局部变量都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。
栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。
每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
通常所说的虚拟机分为栈和堆,这里的栈指的就是虚拟机栈或者说虚拟机栈中的局部变量表部分。
局部变量表存放了编译器可知的各种基本数据类型、对象引用和returnAddress类型(指向一条字节码指令的地址)。
会抛出 StackOverflowError 和 OutOfMemory 异常。
2.堆内存
堆是Java虚拟机所管理的内存中最大的一块,又称动态内存分配.堆是所有线程共享的一块区域,在虚拟机启动时创建。堆的唯一目的是存放对象实例,几乎所有的对象实例都在这里分配。通常就是指在程序运行时直接 new 出来的内存(包括该对象其中的所有成员变量)和数组
堆是垃圾收集器管理的主要区域,所以也称为“GC堆”。现在的垃圾收集器基本上都是采用分代收集算法,所以Java堆还可细分为:新生代和老生代。在细致一点可分为Eden空间,From Survivor空间,To Survivor空间。
如果从内存分配的角度看,线程共享的Java堆可划分出多个线程私有的分配缓冲区。不过无论如何划分,都与存放内容无关,无论哪个区域,都是用来存放对象实例。细分的目的是为了更好的回收内存或者更快的分配内存。
堆内存是按照可扩展的方式来实现的。如果当前堆中没内存来完成对象实例的创建,并且不能再进行内存扩展,则会抛出OutOfMemory异常。
为了分代垃圾回收,Java堆内存分为3代:新生代,老年代和永久代。
新的对象实例会优先分配在新生代,在经历几次Minor GC后(默认15次),还存活的会被移至老年代(某些大对象会直接在老年代分配)。
永久代是否执行GC,取决于采用的JVM。
Minor GC发生在新生代,当Eden区没有足够空间时,会发起一次Minor GC,将Eden区中的存活对象移至Survivor区。Major GC发生在老年代,当升到老年代的对象大于老年代剩余空间时会发生Major GC。
发生Major GC时用户线程会暂停,会降低系统性能和吞吐量。
JVM的参数-Xmx和-Xms用来设置Java堆内存的初始大小和最大值。依据个人经验这个值的比例最好是1:1或者1:1.5。比如,你可以将-Xmx和-Xms都设为1GB,或者-Xmx和-Xms设为1.2GB和1.8GB。
3.方法区(静态存储区)
方法区也是线程共享的区域,用于存储类信息,常量,static变量和即时编译器(JIT)编译后的代码等数据。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在.
方法区描述为堆的一个逻辑分区,不过方法区有一个别名Non-Heap(非堆),用于区别于Java堆区。
运行时常量池
运行时常量池是方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等信息以外,还有一项信息是常量池用于存储编译器生成的各种字面量和符号引用,这部分信息将在类加载后存放到方法区的运行时常量池中。
会抛出OutOfMemoryError异常。
4.本地方法区
本地方法栈和虚拟机栈基本类似,只不过Java虚拟机栈执行的是Java代码(字节码),本地方法栈中执行的是本地方法的服务。本地方法栈中也会抛出StackOverflowError和OutOfMemory异常。
5.寄存器(程序计数器)
由于java虚拟机的多线程是通过轮流切换并分配处理器执行时间来完成,一个处理器同一时间只会执行一条线程中的指令。为了线程恢复后能够恢复正确的执行位置,每条线程都需要一个独立的程序计数器,以确保线程之间互不影响。所以程序计数器是“线程私有”的内存。
程序计数器是一块较小的区域,它的作用可以看做是当前线程所执行的字节码的行号指示器。
程序计数器区域是Java虚拟机中唯一没有定义OutOfMemory异常的区域。
附:在Java编程语言和环境中,即时编译器(JIT compiler,just-in-time compiler)是一个把Java的字节码(包括需要被解释的指令的程序)转换成可以直接发送给处理器的指令的程序。当你写好一个Java程序后,源语言的语句将由Java编译器编译成字节码,而不是编译成与某个特定的处理器硬件平台对应的指令代码(比如,Intel的Pentium微处理器或IBM的System/390处理器)。字节码是可以发送给任何平台并且能在那个平台上运行的独立于平台的代码。