jvm 读书笔记(一) —— java 内存区域

前言

最近在学习jvm,根据同学推荐,买了《深入立即jvm虚拟机》这本书,以下是我对本书的总结和理解。

java 内存区域划分

一、程序计数器

  1. 程序计数器是一块较小的内存空间,它的作用是记录下一条指令所在单元的地址。
    可以看做是当前线程所执行的字节码的行号指示器。
  2. 由于java的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个时刻,一个处理器只会执行一条线程,java为了保证线程切换后能恢复到正确的执行时间,每条线程都会有自己的一个独立的程序计数器,各线程之间程序计数器互不影响,我们把这种内存区域称为**“线程私有”的内存**。
  3. 执行java方法时,程序计数器记录的是正在执行的虚拟机字节码指令的地址

二、java虚拟机栈

简介

虚拟机栈描述的是java方法执行的线程内存模型,每个方法执行的时候,java虚拟机都会同步创建一个栈帧用于储存局部变量、操作数栈、动态链接、方法出口等信息。每一个方法执行开始到结束的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的信息。

局部变量表

通常我们笼统的将java内存划分为堆内存和栈内存,其中栈通常就指的是java虚拟机栈,或者更多情况下指的是虚拟机栈中的变量表部分。

局部变量表存放了编译器可知的各种java虚拟机基本数据类型:

  1. 基本数据类型 (boolean、byte、char、short、int、float、long、double)
  2. 对象引用(reference类型、它可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他对象相关变量)
  3. returnAddress类型(指向了一条字节码指令的地址)

数据类型在局部变量表中的储存空间以局部变量槽(Slot)来表示,其中64位长度为long和double类型的数据会占用两个变量槽、其余的数据类型占用一个。

局部变量的内存空间是在编译期间完成分配,当进入一个方法时,方法需要在栈帧中分配的空间是完全确定的,方法运行期间不会改变方法局部变量表的大小。(这里的大小这的是变量槽的数量,虚拟机真正使用多大空间是由具体的虚拟机来决定的。例如有的虚拟机一个变量槽占了32个比特位,有的是64个)

这个内存区域会出现的异常

  1. 如果线程请求的深度大于虚拟机所允许的深度,将抛出StackOverflowError
  2. 如果java虚拟机栈容量可以动态扩展(现在的 jvm 是不允许扩展的),当栈扩展时无法申请到足够的内存将会抛出OutOfMemoryError异常(超出了jvm分配的内存上限)

三、本地方法栈

本地方法栈与虚拟机栈的区别是:虚拟机栈为虚拟机执行java方法服务,而本地方法栈为虚拟机使用本地方法(Native) 方法服务。

补充:关于java的native方法

Native 方法就是一个java调用非java代码的接口。:该方法的实现由非java语言实现,比如C。这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。在定义一个native method时,并不提供实现体(有些像定义一个java interface),因为其实现体是由非java语言在外面实现的。,下面给了一个示例:

public class IHaveNatives { 
    native public void Native1( int x ) ;
    native static public long Native2() ; 
    native synchronized private float Native3( Object o ) ;
    native void Native4( int[] ary ) throws Exception ;    
 }

特点:

  1. 和abstract方法很像
  2. 标识符native可以与所有其它的java标识符连用,但是abstract除外。这是合理的,因为native暗示这些方法是有实现体的,只不过这些实现体是非java的,但是abstract却显然的指明这些方法无实现体。native与其它java标识符连用时,其意义同非Native Method并无差别,比如native static表明这个方法可以在不产生类的实例时直接调用,这非常方便,比如当你想用一个native method去调用一个C的类库时。上面的第三个方法用到了native synchronized,JVM在进入这个方法的实现体之前会执行同步锁机制(就像java的多线程)
  3. 一个native method方法可以返回任何java类型,包括非基本类型,而且同样可以进行异常控制。这些方法的实现体可以制一个异常并且将其抛出,这一点与java的方法非常相似。当一个native method接收到一些非基本类型时如Object或一个整型数组时,这个方法可以访问这非些基本型的内部,但是这将使这个native方法依赖于你所访问的java类的实现。有一点要牢牢记住:我们可以在一个native method的本地实现中访问所有的java特性,但是这要依赖于你所访问的java特性的实现,而且这样做远远不如在java语言中使用那些特性方便和容易。
  4. 如果一个含有本地方法的类被继承,子类会继承这个本地方法并且可以用java语言重写这个方法(这个似乎看起来有些奇怪),同样的如果一个本地方法被fianl标识,它被继承后不能被重写。
  5. 本地方法非常有用,因为它有效地扩充了jvm事实上,我们所写的java代码已经用到了本地方法,在sun的java的并发(多线程)的机制实现中,许多与操作系统的接触点都用到了本地方法,这使得java程序能够超越java运行时的界限。有了本地方法,java程序可以做任何应用层次的任务。

java 堆

简介

java Heap 是虚拟机锁管理的内存的最大的一块,java堆是被所有线程共享的一块区域,在虚拟机启动时创建。此内存区域的唯一目的是存放对象实例,几乎所有的对象实例都在这里分配内存。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈从入栈到出栈的过程。

java堆也是垃圾收集器(GC)管理的一块区域,因此也被成为“GC堆”。

java堆可以处于物理上不连续的内存空间中,但在逻辑上他应该被视为连续的。java堆既可以被实现成固定大小的,也可以是可扩展,当前主流java虚拟机都是按可扩展实现的。如果java堆中没有内存完成实例分配,并且堆无法再扩展时,java将抛出 OutMemoryError异常。

方法区

方法区与java堆一样是各个线程的共享内存。它用于储存被虚拟机加载的类信息、常量、静态变量、即时编辑器编译后的代码缓存等数据

《java 虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它有个一别名叫做“非堆”,目的就是为了将java堆区分起来。因为以前方法区经常被称为“永久代”(java堆的分代)。本质上这两者是不等价的。(不能将方法区认为是java堆的永久代,方法区只不过是借鉴了这种分代设计)。

运行时常量池

运行时常量池是方法区的一部分。Class文件中的常量池表(用于存放编译期间生成的各种字面量与符号引用),将在类加载后存放到方法区的运行时常量池中。

运行时常量池具备动态性。并不要求常量只有编译器才能产生,运行期间也可以将新的常量放入池中,这种特性利用的比较多的场景是String的intern方法。

  • intern 方法 简介

当前的字符对象(通过new出来的对象)可以使用intern方法从常量池中获取,

如果常量池中不存在该字符串,那么就新建一个这样的字符串放到常量池中。

直接内存

直接内存并不是运行时数据区的一部分,在JDK1.4中新加入的NIO (NEW input/Output)类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个储存在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。

但本机直接内存分配是不受java堆大小的限制的,一般服务器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,忽略掉了直接内存,导致各个内存区域总和大于物理内存限制,导致动态扩展时出现 OutMemoryError。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值