JVM(一)——Java内存区域管理

       在Netty中为了提高了性能,运营了很多多线程相关的知识,而在多线程中我们又看到了各种线程交互、共享资源安全、线程复用等等。而归根结缔所有编写的Java程序都会在JVM中运行,JVM也就是我们程序的容器,它是如何保证多线程程序正常运行,如何存储数据,如何存储逻辑,如何执行程序……好,我们接下来,来学习总结一下JVM相关的知识。

       学习JVM,首先就需要这个容器是怎么存储东西的?先看一下思维导图:

        一,首先看下JVM的运行时数据的内存区域吧,一个容器怎么划分了容积,每个地方存什么?看个图及表格说明吧。

运行时数据区
1-程序计数器(Program Counter Register)

线程独享,为当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器来选择下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都要依赖这个计数器来完成。

理解:就是任务每一步执行的指示灯

2-Java虚拟机栈(Java Virtual Machine Stacks)

线程独享,为Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完毕,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。

局部变量表:存放编译期可知的基本数据类型(boolean、byte、char、short、int、long、double)、对象引用、returnAdress类型。

理解:就是任务执行流程

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

线程独享,类似Java虚拟机栈,不过此是为虚拟机使用到的Native本地方法服务。

理解:Native方法执行任务的流程   

4-Java堆(Java Heap)

线程共享,为存放对象实例,也就是我们在程序中New的对象。Java堆还可以细分为:新生代、老年代;再细致点Eden空间、From Survivor空间、To Survivor空间(为了更好GC)。

线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)避免线程共享安全。

理解:任务执行需要的材料

5-方法区(Method Area)

线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。也被称为非堆(Non-Heap)。而大家经常说的永久代(Permannent Generation)也指的是此。

理解:制造执行任务需要材料的工具(或常量、静态变量这些不变的材料-运行时常量池)

6-运行时常量池(Runtime Constant Pool)

方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,即存放在运行时常量池中。

理解:执行任务需要的常量、静态变量这些不变的材料

7-直接内存(Direct Memory)

并不是虚拟机运行时的一部分,在Netty中里边的Buffer使用的内存有堆内存,还有直接内存两种。直接内存也就是这里指的。

理解:虚拟机可以直接使用的机器内存。

       二,JVM中的对象:

       1,对象的创建

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

       b,加载通过后分配内存:指针碰撞(Bump the Pointer),堆内存规整,用过的放在一边,空闲的一边,中间指针作为分界指示器; 空闲列表(Free List),堆内存不规整,通过列表标记内存地址是否被使用。

       分配解决并发问题:1,对分配内存空间的动作进行同步处理,采用CAS配上失败重试的方式保证更新操作的原子性;2,把内存分配的动作按照线程划分在不同的空间中进行,即每个线程在Java堆中预先分配一小块内存,即前边说的线程分配缓冲。

       通过加装过程,内存分配,然后进行对象头等必要信息的设置, 就可以设置我们业务设置的一些数据了,init出对象。

      2,对象的内存布局

对象的内存布局
1,对象头(Heard)

一部分:存储自身的运行时数据:哈希码、GC分代年龄、锁状态标识、线程持有锁、偏向线程ID、偏向时间戳;

另一部分:类型指针,即对象指向它的类元数据的指针,来原定为那个类的实例。

2,实例数据(Instance Data) 对象上真正存储的业务有效信息,也就是我们代码中定义的各个字段内容,无论是父类继承下来的,子类定义的,都会进行记录。
3,对齐填充(Padding) 非必然存在,仅仅起占位符的作用。

      3,对象的访问定位

      创建对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。reference类型为一个指向对象的引用。访问方式有两种:

      句柄访问:Java堆中划出一块内存作为句柄池,reference中存储就是对象的句柄地址 

      直接指针访问:reference存储的就直接是对象地址

       两者各有优势:句柄来访问,最大的好处就是reference中存储的是稳定的句柄地址,对象被移动(GC)时只改变句柄中指向地址即可;直接指针访问的最大好处就是速度快。

       这样对象的创建、内存布局、访问方式都有了,JVM就可以很好的管理它了。

       三,Jvm中的OutOfMemoryError异常:

       我们经常见到OOM异常,而且如何在实际工作中,遇到这种异常,寻找起来都相对比较麻烦,内存泄漏、内存溢出。一般步骤是先确定为OOM异常,然后查看堆内存文件,利用memory analyzer工具查看是哪些对象过多,然后查看代码尤其是循环的,集合类的对象的处理进行排查问题。(程序计算器不会发生OOM)

Jvm中的OutOfMemoryError异常
1,Java堆溢出 不断的创建对象,并且保证GC Roots到对象之间有可达路劲来避免GC清除这些对象,那么对象数量达到最大堆的容量限制后就会产生内存溢出。
2,虚拟机栈、本地方法栈溢出

1,如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError;

2,如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OOM;

例如无限调用方法本身。

3,方法区、运行时常量池溢出 通过动态代理生成的大量类,将空间占满;不断的创建基本数据类型等。
4,本机直接内存溢出 通过Unsafe直接分配直接内存,大于DirectMemory即可。一般可检查NIO相关的代码即可。

       好,这篇主要总结了JVM的内存区域划分、已经如何存储对象的,并将集中区域可能出现的OOM进行简单总结。JVM——运行Java程序的容器,我们一点点了解……

猜你喜欢

转载自blog.csdn.net/liujiahan629629/article/details/85108860