Java内存区域

java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人想出来—《深入理解java虚拟机》

jvm内存模型

(也叫java虚拟机运行时数据区)
在这里插入图片描述
1.程序计数器(Program Counter Register):线程私有,可以看作是当前线程所执行的字节码的行号指示器。此内存区域是唯一一个在《Java虚拟机规范》中没有任何OutOfMemoryError情况的区域。

2.Java虚拟机栈(Java Virtual Machine Stack):线程私有,它的生命周期与线程相同,虚拟机描述的是Java方法执行的线程内存模型,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧,用于存放局部变量表、操作数栈、动态连接、方法出口等信息。另外,这类内存区域规定了两类异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError,如果java虚拟机栈容量可以动态扩展,当栈扩展到无法申请到足够的内存会抛出OutOfMemoryError异常。

3.本地方法栈(Native Method Stack):线程私有,本地方法栈和虚拟机栈的区别是虚拟机栈为虚拟机执行java方法服务,而本地方法栈则是为虚拟机使用到的本地方法服务。

4.Java堆(Heap):线程共享,在虚拟机启动时创建,所有的对象实例和数组都应当在堆上分配,垃圾收集的主要区域,如果在java堆中没有内存完成实例分配,并且堆也无法再扩展时,java虚拟机将会抛出OutOfMemoryError异常。

5.方法区(Method Area):线程共享,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据,方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。

补充:局部变量表存放了编译器可知的各种Java虚拟机基本数据类型和对象引用;对于基本数据类型,引用和值都存在栈中;对于引用类型,只有引用存在栈中。这些数据类型在局部变量表中的存储空间以局部变量槽(slot)来表示,局部变量表所需的内存空间在编译器完成分配,在方法运行期间不会改变局部变量表的大小。

永久代(PermGen)

永久代并不等价于方法区,方法区是jvm内存模型规范,而永久代是HotSpot虚拟机对于方法区的一个实现,对于其它虚拟机实现,很多都是没有永久代这个概念的。HotSpot使用永久代是使得垃圾收集器能够像管理java堆一样去管理这部分内存,省去专门为方法区编写内存管理代码的工作,垃圾收集器对于方法区的垃圾回收是比较少的,但不代表这部分空间不会进行垃圾回收,方法区的内存回收目标主要是针对常量池的回收和类型的卸载。

永久代的垃圾收集是和老年代(old generation)捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。

JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量转移到Java堆中了,但永久代仍然存在于JDK 7,并没有完全的移除,直到JDK 8,才完全废弃了永久代的概念,所以永久代的参数-XX:PermSize和-XX:MaxPermSize也被移除了,改用与JRockit、J9一样在本地内存中实现的元空间来代替,把JDK 7中还剩余的内容(主要是类型信息)全部移到元空间中。

元空间(MetaSpace)

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。但可以通过以下参数来指定元空间的大小:-XX:MetaspaceSize(初始空间大小)、-XX:MaxMetaspaceSize(最大空间)

为什么要将永久代转换为元空间?

1、字符串常量池存在永久代中,容易出现性能问题和内存溢出(OOM)。

2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出(因为永久代是与老年代捆绑在一起的,永久代大了说明老年代就空间就小了)。

3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

4、Oracle 可能会将HotSpot 与 JRockit 合二为一(而JRockit 是没有永久代这个概念的)。

HotSpot虚拟机对象探秘

1.对象的创建
为对象分配内存:根据Java堆中是否规整有两种内存的分配方式:(Java堆是否规整由所采用的垃圾收集器是否带有压缩整理功能决定)

①指针碰撞
Java堆中的内存是规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,分配内存也就是把指针向空闲空间那边移动一段与内存大小相等的距离。例如:Serial、ParNew等收集器。

②空闲列表
Java堆中的内存不是规整的,已使用的内存和空闲的内存相互交错,就没有办法简单的进行指针碰撞了。虚拟机必须维护一张列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。例如:CMS这种基于Mark-Sweep算法的收集器。

2.对象的内存布局
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

3.对象的访问定位
Java程序需要通过栈上的引用数据来操作堆上的具体对象。对象的访问方式取决于虚拟机实现,目前主流的访问方式有使用句柄和直接指针两种。

①使用句柄
Java堆中划分出一块内存来作为句柄池,引用中存储对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而引用本身不需要修改。
在这里插入图片描述
②直接指针
如果使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而引用中存储的直接就是对象地址。优势:速度更快,节省了一次指针定位的时间开销。由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是非常可观的执行成本。(例如HotSpot)
在这里插入图片描述

JVM的栈上分配与逃逸分析

栈上分配与逃逸分析是在JVM层面进行java性能优化的一个技巧

①栈上分配

栈上分配主要是指在Java程序的执行过程中,在方法体中声明的变量以及创建的对象,将直接从该线程所使用的栈中分配空间。 一般而言,创建对象都是从堆中来分配的,这里是指在栈上来分配空间给新创建的对象。好处是对象跟着方法调用自行销毁,不需要进行垃圾回收,可以提高性能。

有了栈上分配的存在,数组和对象就不一定都是在堆中分配了

②逃逸

逃逸是指在某个方法之内创建的对象,除了在方法体之内被引用之外,还在方法体之外被其它变量引用到;这样带来的后果是在该方法执行完毕之后,该方法中创建的对象将无法被GC回收,由于其被其它变量引用。正常的方法调用中,方法体中创建的对象将在执行完毕之后,将回收其中创建的对象;故由于无法回收,即成为逃逸。

对象作为当前方法的返回值或者作为参数传递都属于发生了逃逸

③逃逸分析

逃逸分析是栈上分配需要的技术基础,逃逸分析的目的是判断对象的作用域是否会逃逸出方法体。注意,任何可以在多个线程之间共享的对象,一定都属于逃逸对象。

当通过逃逸分析发现对象没有发生逃逸,就可以使用栈上分配来进行优化了

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值