前面章节
JAVA-大白话探索JVM-类加载器(一) JAVA-大白话探索JVM-类加载过程(二)html

JVM运行时内存
经过以前的章节,咱们知道.class类如何加载到内存中,如图红框

开始讲讲内存空间
先了解JVM的周期
- JVM在java程序执行时运行,结束时中止。
- 一个java程序对应开启一个JVM进程
- JVM的线程分为两种:守护线程和普通线程
- 守护线程属于JVM本身使用的线程,如GC
- 普通线程是java程序的线程

线程私有数据区
- Java栈(VM Stack)
- 本地方法栈(NM Stack)
- 程序计数器及隐含寄存器(Program Counter Register)
线程共享数据区
- 方法区(Method Area)
- Java堆(Heap)
- 执行引擎
- 本地方法接口
- 本地方法库
你会发现,这都是些什么?????。。。。。。呃java
不着急,一步一步来算法
首先,就是你了,方法区(Method Area,线程共享)
- 类的结构信息和类静态变量都保存在方法区(这样说会不会很抽象,举个例,例如运行时常量池,成员变量和方法数据,构造函数和普通函数的字节码内容,还包括一些在类、实例、接口初始化时用到的特殊方法。开发人员在程序中经过Class对象中的getName、isInstance等方法获取信息时,这些数据都来自方法区。)
- 程序中的全部线程共享一个方法区,简称全局共享
- 对于HotSpot虚拟机,方法区对应为永久代(Permanent Generation),但本质上,二者并不等价,仅仅是由于HotSpot虚拟机的设计团队是用永久代来实现方法区而已,对于其余的虚拟机(JRockit、J9)来讲,是不存在永久代这一律念的。
- 使用永久代来实现方法区并非一个好注意,因为方法区会存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等,在某些场景下很是容易出现永久代内存溢出。如Spring、Hibernate等框架在对类进行加强时,都会使用到CGLib这类字节码技术,加强的类越多,就须要越大的方法区来保证动态生成的Class能够加载入内存。在JSP页面较多的状况下,也会出现一样的问题。
- 在JDK1.8下并无出现咱们指望的永久代内存溢出错误,而是Metaspace内存溢出错误。这是由于Java团队从JDK1.7开始就逐渐移除了永久代,到JDK1.8时,永久代已经被Metaspace取代,所以在JDK1.8并无出现咱们指望的永久代内存溢出错误。在JDK1.8中,JVM参数-XX:PermSize和-XX:MaxPermSize已经失效,取而代之的是-XX:MetaspaceSize和XX:MaxMetaspaceSize。注意:Metaspace已经再也不使用堆空间,转而使用Native Memory
- 还有一点须要说明的是,在JDK1.6中,方法区虽然被称为永久代,但并不意味着这些对象真的可以永久存在了,JVM的内存回收机制,仍然会对这一块区域进行扫描,即便回收这部份内存的条件至关苛刻。
呃。。。。。。。有点多,慢慢吸取,这方法区也须要好好琢磨琢磨,一不当心溢出就麻烦了。缓存
其次,Java堆(Heap,线程共享)
- Java堆是JVM所管理的最大一块内存,全部线程共享这块内存区域,几乎全部的对象实例都在这里分配内存,所以,它也是垃圾收集器管理的主要区域。
- 从内存回收的角度来看,因为如今的收集器基本都采用分代收集算法,因此Java堆又能够细分红:新生代和老年代,新生代里面有分为:Eden空间、From Survivor空间、To Survivor空间。
- 有一点须要注意:Java堆空间只是在逻辑上是连续的,在物理上并不必定是连续的内存空间。
- 默认状况下,新生代中Eden空间与Survivor空间的比例是8:1,可使用参数-XX:SurvivorRatio对其进行配置。大多数状况下,新生对象在新生代Eden区中分配,当Eden区没有足够的空间进行分配时,则触发一次Minor GC,将对象Copy到Survivor区,若是Survivor区没有足够的空间来容纳,则会经过分配担保机制提早转移到老年代去。
- 何为分配担保机制?在发送Minor GC前,JVM会检查老年代最大可用的连续空间是否大于新生代全部对象的总空间,若是是,那么能够确保Minor GC是安全的,若是不是,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,若是小于,直接进行Full GC,若是大于,将尝试着进行一次Minor GC,Minor GC失败才会触发Full GC。注:不一样版本的JDK,流程略有不一样。
- Survivor区做为Eden区和老年代的缓冲区域,常规状况下,在Survivor区的对象通过若干次垃圾回收仍然存活的话,才会被转移到老年代。JVM经过这种方式,将大部分命短的对象放在一块儿,将少数命长的对象放在一块儿,分别采起不一样的回收策略。
Java栈(Stack,线程私有)、本地方法栈
Java栈
- java栈中只保存基础数据类型(四类八种)和自定义对象引用
- 存取类型:先进后出
- 栈内数据在超出其做用域将自动释放
- 每一个栈是线程私有,它们的生命周期与线程相同。
- 每一个线程创建一个操做栈,每一个栈又包含若干个栈帧,每一个栈帧对应每一个方法调用
- 栈帧:
- 局部变量区(方法内基本类型变量、变量对象指针)
- 操做数栈区(存放方法执行过程当中产生的结果)
- 运行环境区(动态连接、方法返回相关信息、异常捕捉)
本地方法栈
- 与JAVA栈相似
- 本地方法栈是在程序调用或JVM调用本地方法接口(Native)时候启用
- 本地方法非java语言编写,不受JVM管理
- HotSpot VM将本地方法栈和JVM栈合并了。
程序计数器(线程私有)
概念:在JVM概念模型里,字节码解释器工做时就说经过改变这个计算器的值来选取下一条须要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都须要依赖这个计数器来完成。安全
- Java虚拟机能够支持多条线程同时执行,多线程是经过线程轮流切换来得到CPU执行时间的,每条线程都会有独立的程序计数器
- 若是执行java方法,程序计数器记录JVM字节码指令的地址,若是执行 native,计数器为空(Underfined)
- 程序计数器这个内存区域在JVM规范中是惟一没有规定任何OutOfMemoryError的区域
运行时常量池(Runtime Constant Pool)
- 方法区的一部分,用于存放编译期间生成的各类字面量(int,short等等)和符号引用(对象符号引用Integer,String)
- 除了编译产生能存入,运行期间也能将新的常量放入池中(String.intern())
- 节省内存空间:常量池中若是有对应的字符串,那么则返回该对象的引用,从而没必要再次建立一个新对象。
- 节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,==判断引用是否相等,也就能够判断实际值是否相等
- Byte、Short、Integer、Long、Character这5种包装类都默认建立了数值[-128 , 127]的缓存数据。当对这5个类型的数据不在这个区间内的时候,将会去建立新的对象,而且不会将这些新的对象放入常量池中。
- Oracle对Java 7中的常量池作了一个很是重要的改变 — 常量池被从新定位到堆中。这意味着你再也不受限于单独的固定大小内存区域。全部字符串如今都位于堆中,与大多数其余普通对象同样,这使你能够在调整应用程序时仅管理堆大小。

完了······
先暂时这么多吧,以上是我我的针对JVM的总结,也方便你们快速理解跟巩固,有错误的地方望告知下,谢谢。