Java内存模型及内存溢出相关问题

    java内存模型是jvm的基础,而jvm是java的基础,所以弄明白java的内存模型是至关重要的!本篇设计到了Java内存模型(堆、栈等)、常见的内存溢出(OOM、StackOutflowError等)。

目录

一、Java内存模型相关问题

JDK1.6、1.7、1.8的jvm(逻辑上的)内存结构

栈和堆的区别是什么?

JDK1.8为什么要去除方法区(永久区)?

二、常见的内存溢出异常问题

出现堆溢出的原因及解决办法

出现栈溢出的原因及解决办法

出现方法区/元数据区内存溢出的原因及解决办法

出现本机直接内存溢出的原因解决办法

 


一、Java内存模型相关问题

    jvm对于操作系统来讲,本质上还是一个进程,下图大致的将内存分成了三块儿:堆(存数据)、方法区(线程执行)、本地内存(不是jvm规范的一部分,由操作系统来直接管理)。

本地内存:不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。如果使用了NIO,这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作;这块内存不受java堆大小限制,但受本机总内存的限制,可以通过MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常。

在 JDK 1. 4 中 新 加入 了 NIO( New IO) 类, 引入了一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在Java堆的DirectByteBuffer对象作为这块内存的引用进行操作,避免了在 Java堆和 Native堆中来回复制数据。NIO 是一种同步非阻塞的 IO 模型。

 

JDK1.6、1.7、1.8的jvm(逻辑上的)内存结构

JDK1.6的JVM内存结构(逻辑上):

关于内存溢出:

程序计数器:较小的内存空间(不会出现OOM即内存溢出),当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响。

Java栈:线程私有,生命周期和线程,每个方法在执行的同时都会创建一个栈帧由于存储局部变量表,操作数栈,动态链表,方法出口等信息。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程;栈里面存放着各种基本数据类型和对象的引用。

本地方法栈:与java栈不同的是,本地方法栈保存的是native(本地)方法的信息,当一个JVM创建的线程调用native方法后,JVM不在为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法。

:涉及到内存的分配(new关键字、反射等)与回收(回收算法、收集器等)。

方法区:也叫永久区,用于存储已被虚拟机加载的类信息、常亮(“abc”,“1234”等),静态变量(static)等数据。

运行时常量池:是方法区的一部分,用于存放编译期生成的各种字面量(“abc”,“1234”等)和符号引用。

 

JDK1.7JDK1.8的JVM内存结构(逻辑上有哪些):

 

 

栈和堆的区别是什么?

▶功能:

  ▷栈:以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(short、byte、int、long、float、double、boolean、char)以及对象的引用变量,其内存分配在栈上,变量除了作用域就会自动释放;

  ▷堆:堆内存用来存储Java中的对象。无论是成员变量、局部变量,还是类变量,他们指向的对象都存储在堆内存中。

▶线程独享还是共享: 

  ▷栈:栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。

  ▷堆:堆内存中的对象对所有线程可见,堆内存中的对象可以被所有线程访问。

▶空间大小:

  栈的内存要远远小于堆内存,栈的深度是有限制的,如果递归没有及时跳出,很可能发生StackOutflowError问题。可以通过-Xss选项设置栈内存大小;-Xms设置堆开始时的大小;-Xmx选项设置堆的最大值。

jvm常用内存参数设置:

:java8去掉了-XX:PermSize和-XX:MaxPermSize,新增了-XX:MetaspaceSize和-XX:MaxMetaSpaceSize。

 

JDK1.8为什么要去除方法区(永久区)?

★ 永久区来存储类信息、常量、静态变量等数据不是个好主意,很容易遇到内存溢出的问题,JDK 8的实现中将类的元数据放入native memory,将字符串池和类的静态变量放入java堆中,可以使用MaxMetaspaceSize对元数据区大小进行调整;

★ 对永久区进行调优是很困难的,同时将元空间与堆得垃圾回收进行了隔离,避免永久区引发的Full GC和OOM等问题。

 


二、常见的内存溢出异常问题

java内存溢出异常主要有两个: 
▶ OutOfMemeoryError:当堆、栈(多线程情况)、方法区、元数据区、直接内存中数据达到最大容量时产生; 
StackOverFlowError:如果线程请求的栈(单线程)深度大于虚拟机栈允许的最大深度,将抛出StackOverFlowError,其本质还是数据达到最大容量。

出现堆溢出的原因及解决办法

堆用于存储实例对象,只要不断创建对象,并且保证GC Roots到对象之间有引用的可达,避免垃圾收集器回收实例对象,就会在对象数量达到堆最大容量时产生OutOfMemoryError异常( java.lang.OutOfMemoryError: Java heap space) 。

▶ 使用-XX:+HeapDumpOnOutOfMemoryError可以让java虚拟机在出现内存溢出时产生当前堆内存快照以便进行异常分析,主要分析那些对象占用了内存;也可使用jmap(工具)将内存快照导出;一般检查哪些对象占用空间比较大,由此判断代码问题,没有问题的考虑调整堆参数。

 

出现栈溢出的原因及解决办法

→ 如果线程请求的栈深度大于虚拟机栈允许的最大深度,将抛出StackOverFlowError
→ 如果虚拟机在扩展栈时无法申请到足够的内存空间(栈中一般不会发生OOM,多线程/高并发时才会出现),抛出OutOfMemeoryError

▶ StackOverFlowError 一般是函数调用层级过多导致,比如死递归死循环; 
▶ OutOfMemeoryError一般是在多线程环境才会产生,一般用“减少内存的方法”,即减少最大堆和减少栈容量来换取更多的线程支持; 

 

出现方法区/元数据区内存溢出的原因及解决办法

→ jdk 1.6以前,运行时常量池还是方法区一部分,当常量池满了以后(主要是字符串变量),会抛出OOM异常; 
→ 方法区和元数据区还会用于存放class的相关信息,如:类名、访问修饰符、常量池、方法、静态变量等;当工程中类比较多,而方法区或者元数据区太小,在启动的时候,也容易抛出OOM异常; 

▶ jdk 1.7之前,通过-XX:PermSize,-XX:MaxPerSize,调整方法区的大小; 
▶ jdk 1.8以后,通过-XX:MetaspaceSize ,-XX:MaxMetaspaceSize,调整元数据区的大小; 

出现本机直接内存溢出的原因解决办法

→ jdk本身很少操作直接内存,而直接内存(DirectMemory)导致溢出最大的特征是,Heap Dump文件不会看到明显异常,而程序中直接或者间接的用到了NIO; 

▶ 直接内存不受java堆大小限制,但受本机总内存的限制,可通过MaxDirectMemorySize来设置(默认与堆内存最大值一样)。 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值