JVM理论:(一)JVM内存模型

一、JVM内存模型

  

    

1、程序计数器

  线程私有,当前线程所执行的字节码的行号指示器,通过计数器来选取下条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都依赖此功能,唯一没有规定OutOfMemoryError的区域,若执行的是Native方法,计数器值为空。

2、Java虚拟机栈

 (1)线程私有,生命周期和线程相同。

 (2)栈与栈帧定义的区别:

    每创建一个线程,虚拟机就会为这个线程创建一个虚拟机栈,每调用一个方法就会为每个方法生成一个栈帧,一个方法从调用到完成对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

 (3)栈帧结构:具体参考JVM理论:(三/3)运行时栈帧结构、基于栈的字节码解释执行过程

  栈帧是方法运行时的基础数据结构,存储了局部变量表、操作数栈、动态链接、方法出口等信息。局部变量表所需的内存空间在编译期便可确定,存放了基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)、returnAddress类型(指向了一条字节码指令地址),其中long和double占2个Slot。 

 (4)线程请求的栈深度大于java虚拟机允许的最大深度将会抛出StackOverflowError异常。如果java虚拟机栈可动态扩展但动态扩展时无法申请到足够的内存将会抛出OutOfMemoryError异常。

3、本地方法栈

  虚拟机栈为虚拟机执行java方法服务,本地方法栈为虚拟机使用native方法服务,也会抛StackOverflowError、OutOfMemoryError异常。

4、Java堆

 (1)被所有线程共享,在虚拟机启动时创建,存放几乎所有的对象实例及数组,java堆可以处于物理上不连续的内存空间中,只要逻辑上连续即可。

 (2)Java堆可分为新生代和老年代,新生代又可以分为Eden、From Survivor、To Survivor。

 (3)逃逸技术、栈上分配、标量替换等优化技术让对象在堆上分配变得不那么绝对了。

 (4)每个线程预先在Java堆中分配的一小块内存,称为TLAB(本地线程分配缓冲),通过-XX:+/-UseTLAB参数来设定是否使用TLAB。

 (5)堆中没有内存完成实例分配,并且堆也无法再扩展时,会抛OutOfMemoryError异常。

5、方法区

 (1)线程共享,存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

 (2)HotSpot虚拟机设计团队为了省去专门为方法区编写内存管理代码,把GC分代收集扩展至方法区,让HotSpot的垃圾收集器可以像管理Java堆一样管理方法区,所以有“永久代”的说法。

 (3)通过永久代来实现方法区会更容易遇到内存溢出问题(通过-XX:MaxPermSize设置永久代上限),因此有弃用永久代改用Native Memory的趋势,JDK1.7中的hotSpot已把原本放在永久代的字符串常量池移出。

 (4)方法区可以选择不实现垃圾收集,回收目标主要针对常量池的回收和对类型的卸载。

   方法区的回收 —— 永久代的垃圾收集主要回收两部分内容,废弃常量和无用的类。

 回收废弃常量

  与回收Java堆的对象类似,以回收常量池中的字面量为例,若一个字符串“abc”已经进入常量池中,当前系统没有任何String对象引用常量池中的“abc”常量,也没有其他地方引用该字面量,如果这时发生内存回收,且必要的话,该“abc”就会被系统清理出常量池。常量池中的类(接口)、方法、字段的符号引用也类似。

 回收无用类

  <1> 该类所有的实例都已经被回收,即Java堆中不存在该类的任何实例;

  <2> 加载该类的ClassLoader已经被回收;

  <3> 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

  需要同时满足上述三个条件,才能算是“无用类”,但是否要对无用类进行回收,还要通过-Xnoclassgc参数进行控制。还可以通过-verbose:class和-XX:+TraceClassLoading、-XX:TraceClassUnLoading查看类加载和卸载的信息。

 

 (5)Java虚拟机规范把方法区描述为堆的一个逻辑部分,但要注意它还有一个别名叫Non-heap(非堆),要注意和Java堆区分开,GC分代收集参考JVM理论(1.2):GC分代算法

 (6)方法区无法满足内存分配需求时,抛OutOfMemoryError异常。

6、运行时常量池

 (1)先看看常量池的概念,常量池数据在编译期被确定,是Class文件中的一部分,用于存放编译期生成的各种字面量和符号引用,关于Class类文件结构可参考class类文件结构

 (2)运行时常量池是方法区的一部分,所有线程共享,常量池的数据会在类加载后进入方法区的运行时常量池中存放,除了保存class文件中描述的符号引用还会把翻译出来的直接引用也存储在运行时常量池里。

 (3)运行时常量池还具备动态性,不要求常量一定只有编译期才能产生,即并非预置入Class文件中常量池的内容才能进入运行时常量池,运行期间也可能将新的常量放入池中(String类的intern())。

 (4)运行时常量池是方法区的一部分,当常量池无法再申请到内存时会抛OutOfMemoryError异常。

7、直接内存

  不是虚拟机运行时数据的一部分,不受Java堆大小的限制,但受本机总内存限制。

  NIO:基于通道与缓冲区的I/O方式,可以使用native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

 

posted @ 2018-07-29 14:05  湮天霸神666  阅读(276)  评论(0编辑  收藏  举报