java内存区域与内存溢出异常

运行是内存区域

java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。

1.线程独有的内存区域

(1)程序计数器(Program Counter Register)

这块内存区域很小,它是当前线程所执行的字节码的行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。Java方法这个计数器才有值,如果执行的是一个Native方法,那这个计数器是空的。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

(2)Java虚拟机栈(VM Stack)

生命周期同线程相同。每个方法执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在Java虚拟机栈中入栈到出栈的过程。这个区域规定了两种异常状况:

StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度抛出;

OutOfMemoryError:虚拟机栈如果可以动态扩展,当扩展时无法申请到足够的内存时抛出;

(3)本地方法栈(Native Method Stack)

和虚拟机栈的作用一样,只不过服务的对象是虚拟机调用的Native方法。没有强制规定,HotSpot虚拟机就干脆没有这块区域,和虚拟机栈是一起的。

2.线程间共享的内存区域

(1)堆(Heap)

大多数应用,Java堆是Java虚拟机所管理的内存中最大的一块,它在虚拟机启动时创建,此内存区域唯一的目的就是存放对象实例。从GC的分代算法上还可以划分为新生代,老年代,更细分可分为Eden空间、From Survivor空间、To Survivor空间。如果在堆中没有内存来完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

(2)方法区(Method Area)

这块区域用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。当方法区无法满足内存分配需求是,将抛出OutOfMemoryError异常。

(3)运行时常量池(Runtime Constant Pool)

方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。这个区域是一个重要特征就是具备动态性,Java语言并不要求常量一定只有编译期才能产生,运行期间也可能将新的常量放入池中,如String类的intern()方法。当常量池无法再申请到内存是抛出OutOfMemoryError异常。

(4)直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但这部分内存也被频繁的使用,而且也可能导致OutOfMemory异常出现。在JDK1.4  新增加NIO,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著的提供性能,因为避免了再Java堆和Native堆中来回复制数据。显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,肯定还是会受到本机总内存(包括RAM以及SWAP区或者分页文件)大小以及处理器寻址空间的限制。如果各个内存区域总和大于物理内存限制,会导致动态扩展时出现OutOfMemoryError异常。

对象创建

Java是一门面向对象的语言,Java程序运行过程中无时无刻都有对象被创建出来。在语音层面上,创建对象(例如克隆、反序列化)通常仅仅是一个new关键字而已,但是虚拟机层面上却不是如此。看一下在虚拟机层面上创建对象的步骤:

1、虚拟机遇到一条new指令,首先去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

2、类加载检查通过后,虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。两个问题:

(1)如果内存是规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”。

(2)如果内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上那些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”。

      <a> 选择哪种分配方式有Java堆是否规整决定,而Java堆是否规整又由锁采用的垃圾收集器是否带有压缩整理功能决定。因此,在使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。

      <b> 如何及时保证new对象时候的线程安全性?

一种方式是对分配内存空间的动作进行同步处理——实践上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性;

另一种是吧内存分配的动作按照线程划分在不同的空间中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB)。那个线程要分配内存就在那个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。

(3)内存分配结束时,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

(4)虚拟机对对象进行必要的设置。如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的GC分代年龄等信息。这些信息都存放在对象的对象头中。

(5)执行<init>方法。把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

对象访问

建立对象是为了使用对象,Java程序需要通过栈上的reference数据来操作堆上的具体对象。主流的对象访问方式有两种:

1.句柄访问。Java堆中将会划分出一开内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各种的具体地址信息。如图所示:

2.指针访问:reference中存储的直接就是对象地址。如图所示:

两种对象访问方式各有优势,使用句柄来访问最大的好处就是reference中存储的是文档的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。使用直接指针访问方式的最大好处就是速度更快,节省了一次指定定位的时间开销,由于对象的访问在java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。Sun HotSpot虚拟机使用的第二种方式进行对象访问。

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值