JVM内存模型

JVM1.7内存模型

JVM1.8内存模型

JVM数据区异常

运行区域

异常

主要原因

虚拟机栈和本地方法栈

StackOverflowErrorOutOfMemoryError

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

OutOfMemoryError:虚拟机在扩展栈时无法申请足够的内存空间

程序计数器

OutOfMemoryError

对象数量到达最大堆的容量,内存泄漏、内存溢出

方法区和运行时常量池

OutOfMemoryError

反射,动态代理:CGLib、JSP、OSGI

最后在说两个概念:

内存泄露(Memory Leak):程序在申请内存后,对象没有被GC所回收,它始终占用内存,内存泄漏的堆积最终会造成内存溢出。

内存溢出(Memory Overflow):程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。通常都是由于内存泄露导致堆栈内存不断增大,从而引发内存溢出。

JVM1.7与1.8的区别

一、方法区变化

1.8同1.7比,最大的差别就是:元数据区取代了永久代

  • 就是JDK8没有了PermSize相关的参数配置了。
  • 元空间的本质和永久代类似,都是对JVM规范中方法区的实现
  • 元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存

1)方法区与永久代的区别?

方法区只是JVM规范定义,而永久代为具体的实现,元空间也是方法区在jdk1.8中的一种实现。

2)为什么废除永久代?

  • 官方文档:移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。
  • PermGen很难调整,PermGen中类的元数据信息在每次FullGC的时候可能被收集,但成绩很难令人满意。
  • 而且应该为PermGen分配多大的空间很难确定,因为PermSize的大小依赖于很多因素,比如JVM加载的class总数,常量池的大小,方法的大小等。
  • 并且永久代内存经常不够用发生内存泄露。

JVM内存模型解说

程序计数器

当前线程所执行的字节码地址指令存放与指示;

本地方法栈

为虚拟机使用到native(操作系统)方法服务;

java虚拟机栈

又称java栈,Java栈是Java方法执行的内存模型。每当启动一个新线程时,Java虚拟机都会为它分配一个Java栈。Java栈以帧为单位保存线程的运行状态。虚拟机只会直接对Java栈执行两种操作:以帧为单位的压栈和出栈。Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。栈也是线程私有的

1)、局部变量表

就是用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。

2)、操作数栈

想必学过数据结构中的栈的朋友想必对表达式求值问题不会陌生,栈最典型的一个应用就是用来对表达式求值。想想一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。

3)、指向运行时常量池的引用

因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。

4)、方法返回地址

当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。由于每个线程正在执行的方法可能不同,因此每个线程都会有一个自己的Java栈,互不干扰。也就解释了栈是线程私有的。

当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。在这个区域规定了两种异常状况:

如果线程请求的栈深入大于虚拟机所允许的深度,将抛出StackOverFlowError异常!

如果虚拟机栈可以动态扩展,当扩展到无法申请内存到足够的内存,就会抛出OutOfMemoryError异常!

Java堆

堆是jvm内存管理的最大的一块区域,此内存区域的唯一目的就是存放对象的实例,所有对象实例与数组都要在堆上分配内存。它也是垃圾收集器的主要管理区域。java对可以处于物理上不连续的空间,只要逻辑上是连续的即可。线程共享的区域。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将抛出OutOfMemoryError异常。

为了支持垃圾收集,堆被分为三个部分:

  • 年轻代 : 常常又被划分为Eden区和Survivor(From Survivor To Survivor)区(Eden空间、From Survivor空间、To Survivor空间(空间分配比例是8:1:1
  • 老年代
  • 永久代 (jdk 8已移除永久代,下面会讲解)

(1) 堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的

(2) Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配

(3) TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。

(4) 所有新创建的Object 都将会存储在新生代Yong Generation中。如果Young Generation的数据在一次或多次GC后存活下来,那么将被转移到OldGeneration。新的Object总是创建在Eden Space。

这些知识在后面学习GC和内存调优方面非常重要。

方法区

方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的.class文件等。方法区是堆的一个逻辑部分,为了区分Java堆,它还有一个别名Non-Heap(非堆)。相对而言,GC对于这个区域的收集是很少出现的。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

在Java 7及之前版本,我们也习惯称方法区它为“永久代”(Permanent Generation),更确切来说,应该是“HotSpot使用永久代实现了方法区”!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值