Java内存管理-JVM内存模型以及JDK7和JDK8内存模型对比总结(三)

作一个积极的人java

编码、改bug、提高本身程序员

我有一个乐园,面向编程,春暖花开!编程

推荐阅读设计模式

第一季

0、Java的线程安全、单例模式、JVM内存结构等知识梳理数组

一、Java内存管理-程序运行过程(一)安全

二、Java内存管理-初始JVM和JVM启动流程(二)数据结构

三、Java内存管理-JVM内存模型以及JDK7和JDK8内存模型对比总结(三)架构

四、Java内存管理-掌握虚拟机类加载机制(四)jvm

五、Java内存管理-掌握虚拟机类加载器(五)函数

六、Java内存管理-类加载器的核心源码和设计模式(六)

七、Java内存管理-掌握自定义类加载器的实现(七)
第一季总结:由浅入深JAVA内存管理 Core Story

第二季

八、Java内存管理-愚人节new一个对象送给你(八)

【福利】JVM系列学习资源无套路赠送

九、Java内存管理-”一文掌握虚拟机建立对象的秘密”(九)

十、Java内存管理-你真的理解Java中的数据类型吗(十)

十一、Java内存管理-Stackoverflow问答-Java是传值仍是传引用?(十一)

十二、Java内存管理-探索Java中字符串String(十二)

实战

一文学会Java死锁和CPU 100% 问题的排查技巧

分享一位老师的人工智能教程。零基础!通俗易懂!风趣幽默!你们能够看看是否对本身有帮助,点击这里查看【人工智能教程】。接下来进入正文。

勿在流沙筑高台,出来混早晚要还的。

@[toc]上一篇分享了JVM及其启动流程,今天介绍一下JVM内部的一些区域,以及具体的区域在运行过程当中会发生哪些异内存常! 其实也就对应了内存管理的第一篇中 JVM的第三个阶段,程序运行内存溢出。

知识地图

本文地图

1、概述

Java的内存管理采用[自动内存管理]机制,由于这个自动管理机制,Java程序员就不须要去写释放内存的代码,并且不容易出现内存泄漏问题(比C/C++程序员少一些烦恼)。可是因为内存的申请和释放都交给了Java虚拟机,一旦出现内存泄漏和溢出问题时,在不了解Java虚拟机内存结构和自动管理机制的状况下,就很难排查问题的所在。因此若是想要成为一个优秀的程序员或者进阶为一个牛逼的架构师,掌握Java虚拟机的自动内存管理机制那是必须的。

2、JVM运行时数据区域

根据《Java虚拟机规范(Java SE 7版)》的规定,Java虚拟机所管理的内存将会包括如下几个运行时的数据区域:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。

如图所示:

运行时数据区

注:上图的虚拟机运行时数据区是Java虚拟机规范所规定的区域,不一样的虚拟机有不一样的实现。

上面图片有线程共享和线程隔离的区域,下面在经过一张图片来进行简单说明,让你更加清晰的理解什么是线程共享和什么是线程隔离。

线程共享和线程隔离区域

经过上面的两个图,大概对JVM的内存模型有个初步的认识,下面咱们在看一下具体的每个区域究竟是什么东东。

一、程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它能够是看做当前线程所执行的字节码的行号指示器。说简单一点就是一个计数器,当字节码解释器工做是可以经过改变这个计数器的值来选取下一条须要执行的字节码指令。在说明一点,各条线程之间计数器互不影响,独立存储,程序计数器器内存区域为 线程私有 的。

类比汇编语言中的程序计数器:在汇编语言中,程序计数器是指CPU中的寄存器,它保存的是程序当前执行的指令的地址(也能够说保存下一条指令的所在存储单元的地址),当CPU须要执行指令时,须要从程序计数器中获得当前须要执行的指令所在存储单元的地址,而后根据获得的地址获取到指令,在获得指令以后,程序计数器便自动加1或者根据转移指针获得下一条指令的地址,如此循环,直至执行完全部的指令。

在JVM规范中规定,若是线程执行的是非native方法,则程序计数器中保存的是当前须要执行的指令的地址;若是线程执行的是native方法,则程序计数器中的值是undefined。因为程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,所以,此内存区域是惟一一个在JVM规范中没有规定任何OutOfMemoryError状况的区域

2.本地方法栈

本地方法栈和虚拟机栈所发挥的做用是很类似的,它们之间的区别不过是 虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。Sun HotSpot 直接就把本地方法栈和虚拟机栈合二为一。本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。

三、Java虚拟机栈

 Java栈也称做虚拟机栈(Java Vitual Machine Stack),也是常说的栈。Java栈是Java方法执行的内存模型。Java栈中存放的是一个个的栈帧,每一个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操做数栈(Operand Stack)、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。栈也是线程私有的。

下图表示了一个Java栈的模型

栈示意图1

栈示意图2

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

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

六、运行时常量池

运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池( Constant pool table),用于存放编译期生成的各类字面量和符号引用,这部份内容将在类加载后进入运行时常量池中存放。运行时常量池相对于class文件常量池的另一个特性是具有动态性,java语言并不要求常量必定只有编译器才产生,也就是并不是预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中。

七、直接内存

直接内存(Direct Memory)并非虚拟机运行时数据区的一部分,也不是JVM规范中定义的内存区域。但这部份内存也被频繁的使用,并且也可能致使OutOfMemoryError异常出现

JDK1.4中新引入了NIO机制,它是一种基于通道与缓冲区的新I/O方式,能够直接从操做系统中分配直接内存,即直接堆外分配内存,这样能在一些场景中提升性能,由于避免了在Java堆和Native堆中来回复制数据。

3、JDK7和JDK8的JVM内存模型的总结

一、方法区变化

这里介绍的是JDK1.8 JVM内存模型。1.8同1.7比,最大的差异就是:元数据区取代了永久代,就是JDK8没有了PermSize相关的参数配置了。元空间的本质和永久代相似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存

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

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

2)为何废除永久代?

1.官方文档:移除永久代是为融合HotSpot JVM与 JRockit VM而作出的努力,由于JRockit没有永久代,不须要配置永久代

2..PermGen很难调整,PermGen中类的元数据信息在每次FullGC的时候可能被收集,但成绩很难使人满意。

并且应该为PermGen分配多大的空间很难肯定,由于PermSize的大小依赖于不少因素,好比JVM加载的class总数,常量池的大小,方法的大小等。

而且永久代内存常常不够用发生内存泄露。

二、运行时常量池变化

在近三个JDK版本(1.六、1.七、1.8)中, 运行时常量池(Runtime Constant Pool)的所处区域一直在不断的变化,在JDK1.6时它是方法区的一部分;1.7又把他放到了堆内存中;1.8以后出现了元空间,它又回到了方法区。其实,这也说明了官方对“永久代”的优化从1.7就已经开始了。

各个版本的变化

贴一张 Java 8 的内存模型图:

JVM8内存图

4、总结

运行时区域大概了解后,咱们在来总结一下:

运行时区域
异常
主要缘由
虚拟机栈和本地方法栈 StackOverflowError、OutOfMemoryError StackOverflowError:线程请求的栈深度大于虚拟机所容许的最大深度;OutOfMemoryError:虚拟机在扩展栈时没法申请足够的内存空间
程序计数器



OutOfMemoryError
对象数量到达最大堆的容量,内存泄漏、内存溢出
方法区和运行时常量池 OutOfMemoryError
反射,动态代理:CGLib、JSP、OSGI等

最后在说两个概念:

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

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

本文内容较多,也比较重要,由于本人也是在学习JVM中,若是文中有不对的地方,也请指出!谢谢!

当咱们了解了JVM和JVM里面有什么区域,对后面学习和理解JVM的一些规范或者排查JVM相关的问题也更加容易一点,当咱们知道了JVM哪些地方报什么样的错误的时候,在出现问题的时候,可以快速的定位和解决,这样对于咱们的成长来讲帮助也是很是大的,继续学习JVM,深刻对JVM的认识,也了解更加有趣的Java世界。

备注: 因为本人能力有限,文中如有错误之处,欢迎指正。

5、参考内容

《深刻理解Java虚拟机》

JDK1.8 JVM内存模型

Java内存区域与内存溢出异常(jdk 6,7,8)

对于JVM内存模型的理解(对比jdk1.7与1.8)

谢谢你的阅读,若是您以为这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到!祝你天天开心愉快!

Java编程技术乐园:一个分享编程知识的公众号。跟着老司机一块儿学习干货技术知识,天天进步一点点,让小的积累,带来大的改变!

扫描关注,后台回复【资源】,获取珍藏干货! 99.9%的伙伴都很喜欢

image.png | center| 747x519

© 天天都在变得更好的阿飞云
相关文章
相关标签/搜索