java 内存区域划分

堆(Heap)和非堆(Non-heap)内存
按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。 
堆内存分配
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。 
非堆内存分配
JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。 
JVM内存限制(最大值)
首先JVM内存限制于实际的最大物理内存(废话!呵呵),假设物理内存无限大的话,JVM内存的最大值跟操作系统有很大的关系。简单的说就32位处理器虽然可控内存空间有4GB,但是具体的操作系统会给一个限制,这个限制一般是2GB-3GB(一般来说Windows系统下为1.5G-2G,Linux系统下为2G-3G),而64bit以上的处理器就不会有限制了。

 

我们常常做的是将Java内存区域简单的划分为两种:堆内存和栈内存。这种划分比较粗粒度,这种划分是着眼于我们最关注的、与对象内存分配密切相关的两类内存域。其中栈内存指的是虚拟机栈,堆内存指的是java堆。

1.栈内存,即虚拟机栈。每个方法被执行的时候都会同时创建一个栈帧,用来存储局部变量,操作栈,动态链接,方法出口等信息。 局部变量包括各种基本类型的变量和对象的引用变量都是在方法的栈内存中分配。其中,64位长度的long和double类型的数据占用2个局部变量的空间,其他数据类型只占用1个。局部变量所需要的内存空间是在编译期间完成的,当进入一个方法时候,这个方法所需的局部变量空间已经确定,在方法运行期间不会改变。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。当线程请求的栈深度大于虚拟机所允许的深度,则抛出 StackOverflowError异常。当虚拟机栈无法扩展时候则抛出 OutOfMemoryError异常。出现这种情况的解决办法具体参见java调优。
2.堆内存,在虚拟机启动时创建。 堆内存的唯一目的就是创建对象实例,所有的对象实例和数组都要在堆上分配。 堆是由垃圾回收来负责的,因此也叫做“GC堆”,垃圾回收采用分代算法,堆由此分为新生代和老年代。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。当堆内存因为满了无法扩展时就会抛出 java.lang.OutOfMemoryError:Java heap space 异常。 出现这种情况的解决办法具体参见java调优。
   其实,Java内存不只局限于以上两种,还有:

1
   1.程序计数器,(上图中的线程pc寄存器)记录当前线程所执行的字节码的行号。
   2.本地方法栈,与上面的虚拟机栈类似,只不过 虚拟机栈是为java方法服务,而本地方法栈 是用来为native方法服务。
   3.方法区,用于存放已被虚拟机加载的类信息、常量、静态变量。垃圾回收在这个区域出现的几率少。在这个区域的回收就是类型的卸载。只有当加载该类型的类加载器实例(非类加载器类型)为unreachable状态时,当前被加载的类型才被卸载。所以一般不会被卸载。
   4.运行时常量池,存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放在方法区的运行时常量池中。该区域不会被回收,例如
Strings="abc";
s="xyz";
s=null;
"abc"和"xyz"将一直存在于在字符串池中,这就是java内存泄露的原因之一。

由于Reference类型在Java虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到Java堆中的对象的具体位置,因此不同虚拟机实现的对象访问方式会有所不同,主流的访问方式有两种:使用句柄和直接指针:

如果使用句柄访问方式,Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息如下图所示:

通过句柄方式访问对象

 

如果使用直接指针访问方式,java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象地址。如下图所示:

通过直接指针方式访问对象

 

 

这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要被修改。

使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的的时间开销。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值