`

深入理解java虚拟机_笔记1

    博客分类:
  • java
 
阅读更多

运行时数据区域:

包括 方法区虚拟机栈本地方法栈 和程序计数器

 

程序计数器

是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器

每一个线程都有自己私有的程序计数器。

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

 

 

JAVA虚拟机栈

也是线程私有的,生命周期和线程相同。每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表操作栈动态链接方法出口等信息。每个方法被调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中从入栈道出栈的过程。

 

这个JAVA虚拟机栈就是我们常说的“栈”。局部变量表存放了元数据类型、引用类型

局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小

 

在JVM规范中,对这个区域规定了两种异常情况:

 

  1. 如果线程请求的栈深度大于JVM允许的深度,抛出StackOverflowError
  2. 如果虚拟机栈可以动态扩展(目前大部分JVM都可动态扩展),当扩展时无法申请到足够的内存时,会抛出OutOfMemoryError异常
本地方法栈
与虚拟机栈作用类似,只不过前者是执行JAVA方法服务,而本地方法栈是为Native方法服务
HotSpot将本地方法栈和虚拟机栈合二为一
和虚拟机栈一样会抛出来两种异常。
 
JAVA堆
JAVA堆是被所有线程共享的一块内存区域,在JVM启动时创建其作用就是存放对象实例
JVM规范规定:所有的对象实例及数组都要在堆上分配。
JAVA堆也是垃圾回收管理的主要区域。
由于现在收集器基本采用分代收集算法,所以JAVA堆还可以细分为:新生代和老年代,再细分还有Eden空间、From Survivor空间、To Survivor空间。
根据JVM规范,JAVA堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,可以是固定大小的,也可以是可扩展的(用-Xmx和-Xms控制),如果堆中没有内存,也无法扩展,抛出OutOfMemoryError
 
 
方法区
也是所有线程共享的内存区域。它用于存放虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据。
在JVM规范中被描述为堆的一部分,但却又有一个non-heap的别名。
 
对于HOTSPOT虚拟机,方法区又被称为永久代(Permanent Generation)
这个区域一样不需要连续的内存,可以选择固定大小或者扩展,还可以选择不实现垃圾回收。确实这个区域的数据一般不参与回收,但这些数据并不一定就是永久存在了,常量池和对类型的卸载也可以成为回收的目标。
抛出OutOfMemoryError
 
运行时常量池
是方法区的一部分。
编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
 
这个区域并不是只有编译时产生的常量,也有运行时产生的常量,比如String.intern()方法。
OutOfMemoryError异常
 
直接内存
Direct Memory
在JDK1.4中引入了NIO,是一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在JAVA堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
抛OutOfMemoryError异常
 
 
Object obj = new Object();
Object obj反映到JAVA栈的本地变量表中
new Object()反映到JAVA堆中,长度不确定,在JAVA堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存放在方法区中
 
引用访问对象的方法主流的有两种:
  1. 使用句柄访问方式,JAVA堆中将会划分出一块内存来作为句柄池引用对象存储的是对象的句柄地址,而句柄中包含着对象实例数据类型数据各自的具体地址信息
  2. 直接指针访问方式。引用对象中直接存储的就是对象地址。,在堆中的对象又有一个指针指向方法区的对象类型数据
句柄访问方式最大的好处是在对象被移动时(垃圾回收经常会移动对象),只用改变句柄中的实例数据指针,而引用对象不用修改。
直接指针方式最大的好处是速度更快HOTSPOT使用这种方式
 
设置堆不可扩展,将-Xms和-Xmx参数设置一样即可
 
栈溢出
-Xoss参数设置本地方法栈大小(在HOTSPOT虚拟机中,由于本地方法栈和虚拟机栈是一起的,所以这个参数是无效的),-Xss参数设定栈大小。
栈溢出会抛出两种异常stackoverflow和outofmemory,前者表示线程请求的栈深度大于虚拟机所允许的最大深度,后者表示无法申请到足够的内存空间。
 
实验表明,单线程只会有stackoverflow,不会有outofmemory.但多线程会产生Outofmemory。
 
这是因为,OS分配给每个进程的内存是有限制的,比如32位windows限制为2GB,栈内存的总量是由2GB减去堆内存(Xmx),再减去方法区容量(MaxPermSize),因为程序计数器消耗内存很小,可以忽略不计。而栈内存又被多个线程所瓜分,这样在线程很多的情况下,内存就可能会耗尽。
 
 
运行时常量池溢出
通过String.intern()方法可以向运行时常量池增加内容,该方法会检测常量池中是否有某个字符串,如果有就返回池中的字符串,否则将该字符串加入常量池,再返回该常量池字符串的引用
 
由于常量池分配在方法区中,可以通过-XX:PermSize-XX:MaxPermSize来限制方法区大小。方法区溢出的标志就是OutOfMemoryError: PermGen Space.
 
 
方法区溢出
主要是由于动态生成了太多的类造成的,这种溢出的实际场景比如大量JSP或动态生成JSP文件的应用(JSP第一次运行时会需要编译成JAVA类)CGLIB等动态代理生成框架产生的动态类等
 
本机直接内存溢出
DirectMemory容量可以由-XX:MaxDirectMemorySize指定,如果不指定,则默认与JAVA堆最大值(-Xmx)一样。实际上DirectByteBuffer并没有直接申请分配内存,而是先计算,如果计算得知无足够内存可分配,就抛出异常。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics