jvm内存结构基础

JVM内存结构 

程序计数器、虚拟机栈、本地方法栈、堆、方法区

jvm内存结构基础_第1张图片

一、程序计数器(Program Counter Register)

      程序计数器(pc register)是一块较小的空间(物理上采用cpu的寄存器实现), 它可以看作当前线程所执行的字节码的行号指示器。在虚拟机的概览模型中,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

      由于java虚拟机的多线程是抢占式(获取cpu时间片)执行的,在任何一个确定的时刻,一个处理器(对于多核处理器就是指一个核心)都只会执行一条线程中的指令。因此,为了让线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各个线程之间的计数器互不影响。也就是说程序计数器是线程私有的。

     如果线程正在执行的是一个java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的是native方法,则计数器记录的值为空(Undefined)。此内存区是唯一一个在java虚拟机规范中没有任何OutOfMemoryError情况的区域。

二、虚拟机栈(JVM Stacks)

       与程序计数器一样,java虚拟机栈也是线程私有的,它的生命周期与线程相同。 虚拟机栈描述的是java方法执行的内存模型,每个方法在执行时都会创建一个栈桢(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成,都对应这一次栈桢的入栈和出栈过程。局部变量表中存放了编译期可知的各种基本数据类型、对象引用 和returnAddress类型。

       在java虚机规范中,对于这个区域规定了2种异常状况: 1 如果线程请求的栈深度大于虚拟机所允许的栈深度,将抛出StackOutflowError异常(如方法递归层数太多,而没有正确的return); 2 如果某栈桢申请的内存过大而得不到足够的内存,将会抛出OutOfMemoryError异常。

 

栈问题排查相关命令

 top  命令
 ps H -eo pid,tid,%cpu 命令,查看进程中都有哪些线程
 jstack 命令 ,列出java中进程的线程信息

问题辨析:
1、垃圾回收是否涉及栈内存?
解答: 线程每次方法调用结束后会自动弹出栈桢,因而栈内存是自动释放的,不涉及垃圾回收。

2、栈内存分配越大越好吗?
解答:java中可通过-Xss[size] 可以指定java程序中每个线程的栈内存大小,如果线程栈内存分配过多,则程序的总的线程数势必会减少,从而影响程序的并发线程数。因此栈内存并不是分配得越大越好。在linux、mac系统中默认的-Xss值为1024k,windows中的-Xss值与操作系统的设置的虚拟内存大小有关。

3、方法内的局部变量是否是线程安全的?
解答: 方法中的局部变量如果被方法返回了(逃离方法作用范围),并且返回值是一个引用数据类型,则可能导致线程安全问题。

三、本地方法栈(Native Method Stacks)

      本地方法栈与虚拟机栈所发挥的作用非常相似,它们的区别是虚拟机栈为执行java方法服务,而本地方法栈则为虚拟机使用到的native方法(操作系统相关方法)服务。在java虚拟机规范中对于本地方法中,方法使用的语言、使用方式和数据结构并没有强制规定,因此具体的虚拟机可以自由地实现它。

Object类的clone、hashcode、wait、notify、notifyAll等都是native方法,需要由操作系统来实现。
public native int hashCode();
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;

四、java堆 (heap)

        对于大多数应用来说,java堆(java Heap)是java虚拟机所管理的内存中的最大一块。java堆是被所有线程共享的一块内存区域。此内存区域的唯一目的就是存放对象的实例,几乎所有的java对象实例都在这里分配内存。这一点在java虚拟机规范中的描述是: 所有的对象实例以及数组都要在堆上分配。 但是随着JIT编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有对象都在堆上分配也逐渐变得不那么"绝对"了。

       java堆还可以细分为:新生代和老年代。其中新生代包括了 eden区和survivor区,而survivor区又分为from 和to区域。根据java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。java堆可以使用-Xms和-Xmx指定最小值和最大值。如果堆中没有完成实例分配,并且堆也无法扩展时,将会抛出OutOfMemoryError异常。

 

堆内存问题诊断工具

1、jps 查看当前系统中有哪些java进程,显示进程id 
2、jmap -heap 进程id 查看java进程的堆内存占用情况 
3、jconsole 图形工具 图形界面,多功能监测工具,可以连续监测
4、jvisualvm 图形工具

五、方法区(Method Area)

       方法区与堆一样,是各个线程共享的内存区域,它由于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。虽然java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-heap(非堆),目的应该是与java堆区分开。

      在JDK8之前,方法区是采用永久代PermGen实现的,而在JDK8之后方法区是采用元空间(本地操作系统内存)实现的。

JDK8之前的永久代内存溢出

java.lang.OutOfMemoryError: PermGen space
-XX:MaxPermSize=8m

JDK8及之后的元空间内存溢出

java.lang.OutOfMemoryError: Metaspace
-XX:MaxMetaspaceSize=8m

 

参考:《深入理解java虚拟机》 

 

你可能感兴趣的:(Java虚拟机,jvm,栈,堆,方法区)