jvm内存清理机制(深入理解JVM第二章-自动内存管理)
什么时候能带着理解的目标看JVM?
这个问题是我从学习Java开始 ,即大二上册开始 ,一直抱有的问题,我在网上搜索了很多次 ,都没有告知我明确的答案 ,我想现在我可以勉强给个答案 ,我觉得:
操作系统学习过一遍
计算机组成原理学习过一遍
有一定的汇编语言基础
Java SE有着扎实的基础
有一定的并发编程基础【因为虚拟机的设计都需要考虑高并发状态】
为什么要学习Java内存区域和内存溢出异常?
Java程序员把控制内存的权力交给了Java虚拟机 ,一旦出现内存泄露和溢出方面的问题 ,如果不了解虚拟机的内存管理机制 ,修正错误将会成为艰难的一项工作 。
运行时数据区域有哪几类?什么时候会发生OOM?
程序计数器定义:程序计数器是当前线程所执行的字节码的行号指示器(程序控制流指示器) ,程序的分支 、循环 、跳转、异常处理和线程恢复等继承功能都依赖它来实现 。特别的是 ,如果线程正在执行一个Java方法 ,那么它记录着虚拟机字节码指令的地址;如果执行的是本地(关键字native)方法,那么它记录着空值 。
生命周期:它是线程私有的内存 ,每条线程都有一个独立的程序计数器 ,各条线程之间互不影响,独立存储;随用户线程的启动和结束而建立和销毁 。
OOM:没有任何可能出现OOM的情况 。
虚拟机栈什么是栈帧?
每个方法被执行时 ,Java虚拟机都会同步创建一个栈帧 ,用于存储局部变量表 、操作数栈 、动态链接 、方法出口等方法有关的信息【参考汇编语言栈的作用】 。
什么是局部变量表?
局部变量表存储了方法执行过程中所有的局部变量,包括:基本数据类型 、对象引用 、方法返回地址(指向一条字节码指令的地址) 。
定义:每一个线程中 ,存储栈帧的栈就是虚拟机栈 。每一个方法从被调用到执行完毕的过程 ,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程 。
生命周期:它是线程私有的内存 ,生命周期与线程相同。
OOM:栈深度超出了虚拟机的最大深度 ,抛出StackOverflow 。而假如虚拟机栈支持动态扩展 ,则虚拟机栈无法申请到足够的内存时 ,会抛出OutOfMemoryError(OOM) 。
本地方法栈和虚拟机栈大致一样 ,只不过本地方法栈存储的是本地方法(关键字native)的栈帧 ,而虚拟机栈存储的是Java方法的栈帧。
堆定义:几乎所有的对象实例以及数组都在堆上分配 。(new关键字)
为什么说是几乎所有?
随着即时编译技术及逃逸分析技术的日渐强大 ,栈上分配 、标量替换(应该是两种编译优化技术)已经导致对象实例有可能不在堆中而在栈中了!
什么是经典分代?
新生代和老年代,在GC回收算法中它们会成为主角哦 。
在我看来 ,为什么堆的生命周期没有那么绝对?
大多对象实例是线程共享的(这个很明显吧 ,这是线程安全问题存在的根本原因),但也有少部分是线程私有的 ,比如:ThreadLocal<T>它就是线程私有的堆内存区域和TLAB(下面会讲)。
从这 ,对自己说一句,可以明显看出的是 ,大多事务都不绝对 ,一切都是为了更好的运行而设计的 ,而不是为了规则本身而设计的 ,别太死板 。生命周期由GC回收算法控制 。
OOM:堆内存不足以进行对象实例内存分配并且堆再也无法动态拓展时 ,会抛出OOM 。
方法区这是最难理解的内存分配区域 ,极其劝退 ,我前六次都被它劝退了 ,概念很绕 ,hh 。概括来说,就是这里存储着有关类的一切信息!
永久代 、元空间和方法区是什么关系?
永久代 、元空间都是方法区的一种实现 。在永久代被废除后 ,方法区的实现就采用元空间 。
永久代和元空间有什么不同?
存储位置不同 ,永久代是堆的一部分,和新生代 ,老年代地址是连续的 ,而元空间属于本地内存;
存储内容不同,元空间存储类的元信息 ,而静态变量和常量池等并入堆中 。相当于永久代的数据被分到了堆和元空间中 。
但是 ,同样的 ,对概念别太死板 ,虽然现在方法区的实现是元空间 ,但是 ,堆中的那部分数据仍然是属于方法区的 ,也就是堆和方法区(又称非堆)其实并没有那么清晰的界限 ,都只不过是为了对内存进行更好的分配罢了 。
定义:它存储着虚拟机加载的类型信息 、常量、静态变量和即时编译器编译后的代码缓存等长期存在的数据。
解释什么是常量池?
字符串常量池:存放字符串常量和字符串常量引用(intern存入的字符串常量引用 ,new方法会创建一个字符串对象同时存入一个字符串常量到池中)的内存区域,里面字符串常量不会重复 。原本被归类于方法区 ,因为长期不会被回收;后被归类于堆 ,因为方法区太小,容不下这尊大佛了 。
Class常量池:用于存放编译器生成的Class文件中的各种字面量(Literal)和符号引用(Symbolic References);
运行时常量池:运行时常量池可以在运行期间将符号引用解析为直接引用 ,由运行时常量池存储这些直接引用。也就是说 ,运行时常量池存储着Class常量池运行期间存入的符号引用,以及由符号引用解析出来的直接引用 ,还包括运行期间产生的新的常量(关于字符串String中的intern方法 ,如果字符串常量池中已经存在此字符串常量 ,不变;而如果不存在 ,字符串常量池存入此字符串常量的引用)
OOM:当方法区(运行时常量池)无法满足新的内存分配需求时 ,会抛出OOM 。
什么是直接内存?
非JVM管理的一块内存区域 。
什么是NIO?
NIO是一种基于通道于缓冲区的IO方式 ,它可以使用Native函数库直接分配堆外内存 ,然后通过堆中的DirectByteBuffer对象作为这块内存的应用进行操作 ,显著提高了Java应用程序的性能。(偷偷占了多的内存能不显著吗?)
产生OOM的原因?
显然 ,直接内存超出了JVM可以管理的内存区域,因为这是它偷的内存 。理想假设 ,本机只有2GB的运行内存 ,而我分配给了虚拟机2GB,但本机2GB因为NIO直接内存的存在 ,实际没有2GB了 ,那么当JVM动态拓展内存区域时,内存就不够了 ,就会出现OOM 。
对象是如何创建的?
对象内存的分配方式有哪些?
指针碰撞:把指针向空闲空间方向挪动一段与对象大小相等的距离 。
空闲列表:虚拟机维护一个列表 ,记录哪块内存是可用的 ,从列表中利用分配算法找到一块足够大的空间划分给对象实例 ,并更新列表 ,会产生外部碎片 。
具体选择哪种方式取决于虚拟机的垃圾回收策略 ,这种两种内存方式参考了操作系统的内存分配策略 。
内存分配的并发问题如何解决?
同步处理:利用CAS算法(Compare And Swap)配上失败重试机制 ,保证分配内存操作的原子性 。
TLAB:Thread Local Allocation Buffer ,每个线程先在线程的本地缓冲区中分配 ,本地缓存区用完了再利用同步处理分配线程的本地缓存区 。
可以使用-XX:+/-UseTLAB来配置TLAB 。
对象实例在堆中的内存分布布局?
对象头 、实例数据和对齐填充 。
对象头存储着什么信息?
一部分是用于存储对象自身的运行时数据,如哈希码 、GC分代年龄、锁状态标志 、线程持有的锁(客户端锁定) 、偏向线程ID、偏向时间戳等。
另一部分用于存储类型指针 ,即对象指向它的类型元数据的指针 ,通过这个来确定对象是哪个类的实例 。(反射会用到)如果是数组,还会存储数组的长度 ,因为一般的对象实例通过元数据信息已经可以确定大小 ,而数组不行 。
实例数据存储着什么信息?
程序代码里所定义的各种类型的字段内容。
为什么要对齐填充?
就和计网IP数据报以及计组里学的一样,数据最好封装成字节的整数倍 ,方便CPU读取 。
对象如何进行访问定位?
使用句柄如果使用句柄访问 ,在java堆中将划分出一块内存来作为句柄池 。reference中存储的就是对象的句柄地址 ,而句柄中包含对象实例数据与类型数据具体地址信息。
直接访问使用直接访问 ,在java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息 。reference中直接存储对象地址即可 。
句柄访问和直接访问的优缺点句柄访问在引用和具体数据之间增加了一层转换关系 ,这层转换关系使得对象在被移动的时候(如垃圾回收)只需要改变转换关系 ,即改变句柄池中的引用指向即可 。而引用本身不需要被修改 。使用直接访问最大的好处就是快 ,因为相对于句柄访问减少了一次指针定位的时间 。由于java是面向对象语言 ,对象访问非常频繁 ,因此这种访问开销积少成多也非常可可观 。hotspot虚拟机使用的就是直接访问方式 。
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!