JVM memory structure

foreword

 

The memory structure of JVM is formulated by the " Java Virtual Machine Specification", which is only responsible for formulating standards, and the specific implementations are various, such as: Sun 's HotSpot , BEA 's JRockit , IBM 's J9 (the first two All of them have been acquired by Oracle ), in addition, Apache , Google , Microsoft and other organizations or companies have their own java virtual machine implementation. It's just that HotSpot is the most commonly used in our current development .

 

The relationship between the Java Virtual Machine Specification and the specific Java virtual machine is somewhat similar to interfaces and implementation classes. The " Java Virtual Machine Specification" is responsible for formulating interfaces, and specific Java virtual machines implement these interfaces. The java code we write will eventually be compiled into a class file, which is parsed, loaded and executed by the java virtual machine. The "cross-platform" of Java statements is actually achieved through the Java virtual machine. Different platforms need to implement different jvm versions ( windows version, linux version, etc.). Our business code only needs to be developed and compiled into the same class file, but it can be executed in different operating systems such as windows and linux .

 

The format and specification of the class file mentioned here are also formulated by the " Java Virtual Machine Specification". In other words, no matter what language is used, the program can be executed in the Java virtual machine as long as it is compiled into a class file according to the " Java Virtual Machine Specification" . For example, the common languages ​​that can be run on jvm (equivalent to java virtual machine) are: Scala , Groovy , Jython , etc. dozens of languages ​​(I have only used three listed here). This is another powerful feature of jvm virtual machine in addition to "cross-platform" - " cross-language " .

 

Now more and more languages ​​have JVM -based implementation versions, such as Rhino for JavaScript , Luaj for Lua , Jyhon for Python , and so on. Using the " cross-language " feature of JVM , no matter what language you use, the code can eventually run in the same platform JVM to achieve cross-language calls. JVM is no longer exclusive to the java language, it belongs to every programming language in the world.

 

Why are so many languages ​​vying to implement a jvm -based implementation version? Two points have been mentioned earlier: " cross-platform " can be achieved with the help of jvm ; " cross-language " can be achieved with the help of jvm . Another point is actually related to today's topic: automatic "memory management" with the help of jvm .

 

As we all know, different from c , c++ (need to control the memory by oneself), java can automatically realize garbage memory collection. In fact, this work is not implemented by the java language itself, but by the jvm , that is to say, any other language can be compiled into executable class files as long as it follows the " java virtual machine specification" , and its memory management can be delivered with confidence. to the jvm .

 

Then again, the mainstream jvm implementation is essentially written in c and c++ (of course, it can be written in any language, as long as it conforms to the " java virtual machine specification"), and its memory management is still controlled by c and c++ The creation and destruction of space. Just leave this part of the work to the jvm , and java programmers only need to care about their own business logic. This is a bit similar to architectural design. The architect puts common functions into the framework for unified processing, and ordinary programmers only need to write business code. Or it can be said that JVM is a higher-level architecture design, but the architects become the guys who implement the JVM source code.

 

另外《java虚拟机规范》有多个版本,笔者只阅读过周老师翻译的《java虚拟机规范 java SE7》,感兴趣的可以直接研究oracle官网最新的版本(英文原版)。

 

Jvm的内存结构

 

根据java虚拟机规范可以完成java虚拟机的开发,Java虚拟机可以看作是一台抽象的计算机。如同真实的计算机那样,它有自己的指令集以及各种运行时内存区域。本次主题是讨论jvm的内存结构,对应的就是jvm“运行时内存区

 

该区域大致分为5部分:方法区、java堆、PC寄存器(或者程序计数器)java虚拟机栈、本地方法栈。大致流程为程序启动时:先把class文件加载到方法区;初始化bean对象放到java堆(比如spring ioc容器中的bean);每个线程执行时会对应一个自己私有的PC寄存器;同时还会创建一个私有的 java虚拟机栈”或者本地方法栈

 

由此可以看出:方法区和java堆是所有线程公有的,PC寄存器、java虚拟机栈和本地方法栈是线程私有的。结构图如下(来至《深入理解Java虚拟机):

 


 

 

1、方法区

 

在程序启动时jvm会读取class文件,把每个类的结构信息放到方法区,这里的class文件包括jar包、war包中的所有class文件。所以程序中应该尽量避免引入无用的、重复的(不同版本)jar包。类的结构信息包括:运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法(<init><cinit>方法)。该区域所有线程共享。

 

jdk1.8(对应不同的jvm实现)以前,我们常用的hotspot java虚拟机内存分代中有永久代的概念,这个永久代等价于“方法区”。可以通过PermSize MaxPermSize来设置永久代大小。如果超过MaxPermSize,系统会抛出OutOfMemoryError: PermGen异常。

 

jdk1.8之后,hotspot jvm在内存空间中完全移除了永久代。“方法区”中“类的元数据信息”被放到“元空间”(Metaspace),运行时常量池被放到“java堆”(这部分是从jdk1.7开始)。

 

jdk1.8之后,PermSize MaxPermSize参数设置失效,启动时会有警告信息。改用新的参数MaxMetaspaceSize来限制“元空间”的大小,如果不设置默认最大为本机内存容量(动态调整)。建议通过MaxMetaspaceSize设置最大“元空间”,如果类元数据的空间占用达到参数“MaxMetaspaceSize”设置的值,将会触发对死亡对象和类加载器的垃圾回收。

 

2java

 

通过-Xms -Xmx指定堆内存大小,Java堆在jvm启动时创建,所有对象的创建和销毁都在这个区域进行,是jvm管理的最大的一块内存(可以不是连续的)。对象的销毁指的就是垃圾回收,为了更合理的回收对象(对象存活时间的长短),通常的jvm实现把java堆分为年轻代和年老代,并采用不同的垃圾回收算法、以及垃圾回收器进行垃圾回收(对象销毁)。这里不对分代算法、垃圾回收器详细讲解。

 

该区域中的对象是所有线程共享的,典型的运用场景就是springioc容器,在程序启动时创建一系列对象放到一个全局的map数据结构里,防止被垃圾回收。线程中可以直接使用这些对象,而不需要重复创建和销毁。

 

在创建新对象时,如果该区域已没有足够的空间 会抛出OutOfMemoryError异常

 

3程序计数器

 

由于jvm是支持多线程执行的,但本质上是cpu在多个线程之间切换,为了记录每个线程执行的位置,每个线程都有自己独有的程序计数器,多个线程之间互不干扰。对于非native方法程序计数器记录的是正在执行的虚拟机字节码指令地址,可以看做所执行字节码的指示器,通过字节解释器改变其值来保证程序按照特定的顺序执行。

 

PC寄存器的容量至少应当能保存一个returnAddress类型的数据或者一个与平台相关的本地指针的值,其大小是能确定的。

 

该区域需要注意以下三点:

1.如果线程正在执行的是非native方法,那么计数器记录的是正在执行的虚拟机字节码指令地址

2.如果执行的native方法,计数器当中的内容应当是空。执行顺序交给jvmnative方法控制。

3.该区域是java的虚拟机规范当中,唯一一个没有规定OutOfMemoryError的区域。

 

4java虚拟机栈

 

每一条java虚拟机线程在创建时会创建自己的java虚拟机栈,因此它是线程私有的。其主要作用是:用于存储局部变量与一些过程计算结果的地方。其工作方式是结合程序计数器读入变量到栈,根据不同的指令读取值出栈进行运算(先入后出),运算结果再入栈。

 

java虚拟机栈的总容量可以动态扩展,但每个线程的栈大小是固定的,可以通过-Xss参数指定。总内存固定 其值越小就可以支持创建更多的线程,同时每个栈的容量就越小,当该线程需要的容量超过这个值时,就会抛出StackOverflowError异常。同理 如果-Xss参数指定的值越大,每个线程可以用的栈内存空间就越大可以存放更多的局部变量等信息,但支持的线程就越小,如果总内存耗尽 没有足够的空间开辟新的线程,会抛出OutOfMemoryError异常。简单的讲如果抛出抛出StackOverflowError异常,增大-Xss的值;如果抛出OutOfMemoryError异常,减小-Xss的值。

 

Java虚拟机栈里数据结构叫栈帧

栈帧随着方法调用而创建,随着方法结束而销毁,也就是说一个线程里执行多个方法,对应会产生和销毁多个栈帧,这里的销毁时机是指方法调用结束,也就是说栈的内存回收不像java 没有复杂的垃圾回收机制。虽然同一个线程里会有多个栈帧,但同一时间只有一个栈帧,上面提到的-Xms指定的空间,其实是每个栈帧的空间。当线程中一个方法返回时,当前栈帧会传回此方法的执行结果给前一个栈帧,在方法返回之后,当前栈帧就随之被丢弃,前一个栈帧就重新成为当前栈帧了。

 

每个都有自己的:局部变量表、操作数栈、动态链接、返回地址。其中局部变量表和操作数栈的容量是在编译期确定,因此栈帧实际消耗容量的大小仅仅取决于Java虚拟机的实现和方法调用时可被分配的内存。

 

局部变量表:存放局部变量的列表,一个局部变量类型为booleanbytecharshortfloatreferencereturnAddress的数据,两个局部变量可以保存一个类型为longdouble的数据。局部变量使用索引来进行定位访问,第一个局部变量的索引值为零。

 

操作数栈:后进先出(Last-In-First-OutLIFO)栈,长度由编译期决定,在任意时刻,即任意一个栈帧中的操作数栈都会有一个确定的栈深度,一个long或者double类型的数据会占用两个单位的栈深度,其他数据类型则会占用一个单位深度。

 

动态链接:简单的理解为指向运行时常量池的引用。Class文件里面,描述一个方法调用了其他方法,或者访问其成员变量是通过符号引用(Symbolic Reference)来表示的,动态链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用。

 

返回地址:方法调用的返回,包括正常返回(有返回值)和异常返回(没有返回值),不同的返回类型有不同的指令。

 

5、本地方法栈

 

用于支持native方法,和java虚拟机栈相似,是线程私有,只是这个栈是采用其他语言实现。同样会有可能抛出StackOverflowErrorOutOfMemoryError异常。-Xss设置栈内存的大小同样适用于本地方法栈。

 

关于内存溢出和内存泄漏

 

内存泄漏一定会导致内存溢出,但内存溢出不一定是内存泄漏导致,也有可能是服务器内存本来就不足,可以通过增加服务器内存 同时增大-Xms Xmx配置。

 

内存泄漏一般比较隐蔽,难于发现。典型的发生场景就是,多线程的的线程中中使用ThreadLocal,在线程执行结束时没有remove,导致对象无法被回收,日积月累内存耗尽,抛出OutOfMemoryError异常。

 

关于jvm的内存结构就总结到这里,转载请注明出处:

http://moon-walker.iteye.com/blog/2394056

 

部分内容参考自周志明翻译的《java虚拟机规范SE7

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326801340&siteId=291194637