JVM虚拟机学习(1)---Jvm内存区域

目录页:https://mp.csdn.net/postedit/95937156

1.小声哔哔

    本文主要基于JDK1.8,网上已有许多大神对JVM内存区域划分做了详解,本文目的主要是对自己学习心得的记录,如有错误请指正。

2.运行时数据区

    JVM在运行过程中会把它所管理的内存划分为若干不同数据区域

  1. 线程私有:程序计数器、本地方法栈、虚拟机栈
  2. 线程共享:堆、方法区

如下图所示:

 

2.1. 程序计数器

    程序计数器指向正在执行的字节码指令的地址(行号),为什么需要程序计数器呢,因为Java是多线程的,程序计数器就是保证多线程情况下线程切换后程序仍能正常执行。程序计数器是唯一不会OOM的区域

2.2. 虚拟机栈

    虚拟机栈是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行同时会创建一个栈帧,栈帧中存储局部变量表、操作数栈、动态链接,方法出口等信息。每一个方法从调用到执行完成的过程都对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

    局部变量表中存放了基本数据类型(boolean,byte,char,short,int,float,long,double)和对象引用,局部变量所需内存空间在编译期完成分配,long和double会占用两个局部变量空间,其余数据类型占用一个。

    若单个线程请求的栈深度大于虚拟机允许的深度,则会抛出StackOverflowError(栈溢出错误)。当整个虚拟机栈内存耗尽会抛出OutOfMemoryError异常。

    局部变量表:记录局部变量

    操作数栈:用于计算的临时数据存储区,大多数指令都在操作数栈弹栈运算,然后结果压栈。

    动态连接:多态机制时运行期根据实际类型确定执行哪个方法。

    返回地址:方法返回

2.3. 本地方法栈

    本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出StackOverflowError和OutOfMemoryError异常。不同的是,本地方法栈服务的对象是JVM执行的native方法(例如hashCode方法),当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机中创建栈帧,只是简单的动态链接并直接调用native方法。而虚拟机栈服务的是JVM执行的java方法。我们常用的HotSpot虚拟机选择合并了虚拟机栈和本地方法栈。

2.4. Java堆

    Java被所有线程共享,虚拟机启动时创建,需要关注的三个启动参数如下:-Xms:初始大小,-Xmx:最大大小,-Xmn:新生代大小。Java堆用于存放对象实例,是垃圾收集器的主要区域。

2.5. Java方法区(永久代、元空间)

    与Java堆一样,是各个线程共享的内存区域,用于存储虚拟机加载的类信息、常量(被final关键字修饰)、静态变量(被static关键字修饰)、即时编译期编译后的代码等数据。更加详细一点的说法是方法区里存放着类的版本,字段,方法,接口和常量池。常量池里存储着字面量和符号引用。

    JDK1.8版本之前有人会误以为永久代与方法区是等价的,实际情况是使用永久代实现方法区这个概念,可以将方法区理解为接口,永久代为具体的实现方法。因为JDK1.8以后做了去永久代,就是使用元空间来实现方法区,同时隔离了堆内存的垃圾回收与元空间的垃圾回收。元空间大小只受制于机器内存大小。

    永久代启动参数:-XX:PermSize(初始值)   -XX:MaxPermSize(最大值)

    元空间启动参数:-XX:MetapaceSize(初始值) -XXMaxMetaspaceSize(最大值)

2.5.1. 常量池:全局字符串池

    全局字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的)。 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是我们常说的用双引号括起来的)的引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。

2.5.2. 常量池:class文件常量池

    当java文件被编译成class文件之后会生成class文件常量池,我们都知道,class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。 字面量就是我们所说的常量概念,如文本字符串、被声明为final的常量值等。 符号引用是一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。一般包括下面三类常量:

  1. 类和接口的全限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符

2.5.3. 常量池:运行时常量池

    jvm在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在上面我也说了,class常量池中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。而经过解析(resolve)之后,也就是把符号引用替换为直接引用,直接引用一般是指向方法区的本地指针,解析的过程会去查询全局字符串池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。

    JDK1.6运行时常量池放在方法区中,JDK1.7运行时常量池放在堆中。

    以上2.5.1,2.5.2,2.5.3参照博客:http://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/

2.6. 直接内存

直接内存不是虚拟机运行时的数据区一部分,在使用NIO的时候这个区域会被频繁调用,在Java堆内可以使用directByteBuffer对象直接引用并操作,这块内存受本机内存大小限制,可以通过MaxDirectMemorySize来设置(默认与堆内存大小一致),也会出现OOM内存异常。使用NIO可以避免在JAVA堆和Native堆中来回复制数据,能够提高效率。

2.7. 思维导图

 

参考资料:周志明大神-《深入理解Java虚拟机 JVM高级特性与最佳实践》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章