9159金沙游艺场

图片 8
为什么 HTTP 有时候比 HTTPS 好?
图片 3
windows已激活,但提示:windows 7 内部版本7601 此windows副本不是正版

JVM自动内存管理-Java内存区域与内存溢出异常,jvm-java

对于 Java 程序员来说,在 JVM
自动内存管理机制的帮助下,不再需要为每一个 new 操作去写对应的
delete/free
代码,不容易出现内存泄露和内存溢出的问题。不过正因如此,如果不了解虚拟机是怎样使用内存的,一旦出现内存泄露和内存溢出的问题,那么排查错误将会非常艰难。

JVM自动内存管理-Java内存区域与内存溢出异常,jvm-java

摘要: JVM内存的划分,导致内存溢出异常的可能区域。

1. JVM运行时内存区域

  JVM在执行Java程序的过程中会把它所管理的内存划分为以下几个区域:

 图片 1

1.1 程序计数器

  程序计数器是一块较小的内存空间,在Java虚拟机规范中是唯一一个未规定OutOfMemoryError的内存区域。 

  程序计数器可看作当前线程所执行字节码的行号指示器。字节码解释器工作时通过改变计数器的值来选取下一条待执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器来完成。 

  每个线程均有一个独立的程序计数器。若线程执行的是一个Java方法,计数器记录的值是正在执行虚拟机字节码地址;若执行的是Native方法,计数器值为空(Undefined)。

 

1.2 Java虚拟机栈

  Java虚拟机栈也就是我们经常说的”栈”(其实这个说法不够严谨<^_^>),它是线程私有的,生命周期与线程相同。 

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

  局部变量表存放了编译期可知的8种基本数据类型、对象引用(reference类型,指向对象起始地址的引用指针或代表对象的句柄或其他与此对象相关的位置)、returnAddress类型(指向一条字节码指令的地址)。 

  Java虚拟机规范对这块区域规定了两种异常情况:

  • StackOverflowError 

      线程请求的栈深度大于虚拟机所允许的深度,将抛出此异常;

  • OutOfMemoryError 

      若虚拟机栈可以动态扩展,但在扩展时无法申请到足够的内存,将抛出此异常;

 

1.3 本地方法栈

  与虚拟机栈类似,但它执行的Native方法服务。

 

1.4 Java堆

  ”GC”堆,虚拟机启动时创建,为所有线程共享的一块区域。在Java虚拟机规范描述中,所有对象实例和数组都要在堆上分配(随着JIT编译器的发展和逃逸技术的逐渐成熟,所有对象在堆上分配也变得不是这么绝对)。 

  如果堆中剩余内存不足以完成实例的分配,且无法再扩展,将抛出OutOfMemoryError。

 

1.5 方法区

  ”Non-Heap”,存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据。

  根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError。

 

1.6 运行时常量池

  方法区的一部分,在类加载后进入方法区时,存放编译期生成的各种字面量和符号引用。 

  在运行期间,也可能将新的常量放入池中,如String常用的intern()方法。 

  当无法满足内存分配需求时,将抛出OutOfMemoryError。

 

1.7 直接内存

  这部分区域并不是虚拟机运行时数据区的一部分,也非Java虚拟机规范中定义的内存区域,但也被频繁使用,亦可能抛出OutOfMemoryError。 

  直接内存主要为NIO(JDK1.4
加入的类)使用,通过存储在Java堆中的DirectByteBuffer对象对这块内存的引用进行操作。在某些场合能显著提高性能,避免了Java堆和Native堆中来回复制数据。

 

2. HotSpot虚拟机对象

  讨论Java堆中普通对象分配、布局和访问的过程。

2.1 对象的创建

  • 虚拟机遇到一条new命令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过,若没有,则先执行相应的类加载过程;
  • 在类加载检查通过后,虚拟机将为新生对象分配内存:

  • 指针碰撞 

        若Java堆中的内存是绝对规整的,所有用过的和空闲的内存都各放一边,中间放着一个指针作为分界点的指示器,那分配内存仅仅是将指针向空闲空间方向移动一段与对象大小相等的距离;

  • 空闲列表 

      Java堆中的内存并不规整,此时虚拟机必须维护一个列表,记录哪些内存块是可用的,在分配的时候从列表上找到一块足够大的空间划分给对象实例,并更新列表上的记录。

      并发情况下,在堆上分配内存,可能出现内存分配的同步问题,解决方案有两个,一个就是同步内存(CAS)分配动作;另一个就是采用TLAB(本地线程分配缓冲),即在Java堆中针对每个线程先预先分配一小块内存。这样当线程需要分配内存时就在自己的TLAB上进行,从而避免同步的开销。但是当TLAB分配满重新分配TLAB时仍需要同步。在内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)。

  • 虚拟机将要对对象进行必要的设置,如这个对象是哪个类的实例,如何找到类的元数据信息、对象的hashCode和GC分代年龄等信息。这些信息存放在对象的对象头中。

最后执行init方法,从而产生真正可用的对象。

 

2.2 对象的内存布局

在HotSpot虚拟机中,对象在内存的存储的布局可分为3块区域:对象头、实例数据、对齐填充。

  • 对象头

  • Mark Word 

          存储对象自身的运行时数据,如hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等;

  • 类型指针 

          对象指向它的类元数据的指针,虚拟机通过这个指针确定这个对象是哪个类的实例。

  • 实例数据 

      程序代码中所定义的各种类型的字段内容,包括从父类继承下来的。

  • 对齐填充 

      并不是必然存在,仅仅起着占位符作用,以满足HotSpot VM
自动内存管理系统对对象起始地址必须是8字节的整数倍要求。

 

2.3 对象的访问定位

  Java程序通过栈上的reference数据来操作堆上的具体对象。主流的访问方式: 

    * 句柄 

      Java堆中将划分一块内存用作句柄池,reference中存储的就是对象的句柄地址,而句柄包含了对象实例数据与类型数据各自的具体地址信息。 

      优点:当对象移动时,只需要改变句柄的指针即可,而相应的reference则不需要做变动; 

 图片 2

  • 直接指针 

  reference存储的是对象地址,通过reference就能访问数据。 

  优点:访问速度快,相对句柄的方式而言少了一次指针定位的开销。HotSpot
VM使用的是此种方式。 

   图片 3

参考文献

深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)

 

摘要: JVM内存的划分,导致内存溢出异常的可能区域。 1. JVM 运行时内存区域
JVM在执行…

JVM(Java Virtual Machine)
即Java虚拟机,是一种用于计算设备的规范,用于运行Java程序编译后得到的字节码文件

虚拟机在执行 Java
程序的过程中会把它管理的内存划分为若干个不同的数据区域,这些区域各司其职

一。JVM的内存区域

下面这 3 个区域都是线程私有的区域,每个线程独占一份

  1.程序计数器(Programing Counter
Register)

程序计数器

  • 当前线程所执行的字节码的行号指示器
  • 通过改变计数器的值来选取下一条执行的字节码指令
  • 帮助完成分支,循环,跳转,异常处理,线程恢复等基础功能
  • 多线程环境中,为了正常完成线程的切换,使得各个线程能恢复到正确的执行位置,因此每条线程都需要一个程序计数器
  • 唯一一个不会出现 OutOfMemoryError 情况的区域

    用于选取需要JVM执行的字节码指令,最简单的一种方法就是通过修改程序计数器的值来达到选取下一条需要执行的字节码指令的目的。

虚拟机栈

  • 每个方法执行时都会创建一个栈帧,当方法被调用,栈帧入栈,方法执行完成,出栈
  • 每个栈帧存储局部变量表,操作栈,动态链接,方法出口等
  • 局部变量表存储了当前方法的局部变量,包括基本数据类型,对象引用以及
    returnAddress 类型(指向一条字节码指令的地址)
  • 可能出现的两种异常:StackOverflowError:栈溢出,线程请求的栈深度大于虚拟机允许的深度OutOfMemoryError:内存溢出,如果虚拟机栈可以动态扩展,那么如果扩展时无法申请到足够的内存,则抛出异常

    每个线程都会有一个独立的程序计数器,即这块内存是属于线程私有(或者说线程隔离)的。若是执行的是Native修饰的方法,则计数器值为空(Undefined)。

本地方法栈

  • 作用,运行机制,异常类型等与虚拟机栈相同
  • 为 native 方法服务
  • 很多虚拟机中,本地方法栈和虚拟机栈合二为一

下面两个为线程共享的区域

    此外,程序计数器所在的这块内存区域是唯一一个在JVM规范中没有任何OutOfMemoryError情况的区域

  • 虚拟机管理的内存中最大的一块
  • 存放对象实例
  • 垃圾回收器管理的主要区域,也被称为 GC
    堆,并因此可以细分为新生代和老年代
  • 可以处于不连续的空间中
  • 当没有内存完成实例分配,堆也无法扩展时,抛出 OutOfMemoryError

  2.Java虚拟机栈(也就是我们常说的栈内存)

方法区

  • 存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等
  • 垃圾回收行为在这个区域很少出现,因此也被称作为“永久代”

值得注意的是,从 Java 8
开始,方法区被移除,取而代之的是一个叫元空间(Metaspace)的区域

    是线程私有的。每个方法在执行时都会创建一个栈帧(方法运行时的基础数据结构),用来存储局部变量、动态链接等。

运行时常量池

  • Java 6 及之前属于方法区的一部分;Java 7 后被移入堆区域
  • 存放编译器生产的各种字面量和符号引用,在类加载时期存入运行时常量池

对象的创建过程中有以下几大步骤:

    其中,局部变量表中存放:基本数据类型(boolean、byte、char、short、int、long、float、double)和对象引用类型(reference类型,如存储引用的变量?)

类加载检查

当虚拟机遇到一个 new
指令,说明要创建对象了;但在创建对象之前,会先去检查这个类是否已经加载过,解析和初始化过,如果没有,则先执行类加载过程

    在栈帧中,64位长度的long、double都会占用两个局部变量空间,其余数据类型均只占用1个Slot

相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图