点击蓝字关注我们
本章节知识点:
对象的创建和使用
四种引用类型
如何判断是否回收对象
内存分配的两种方式
内存泄露和内存溢出
对象创建和使用
对象创建的过程
之前讲过类加载的过程,这是将 class文件加载到内存的过程,会将常量和静态变量加载到方法区(元空间),而对象的创建是在执行到new 关键字JVM执行的操作。
对象创建的过程:
类加载检查
分配内存
初始化零值
设置对象头
执行init方法,进行初始化
1.类加载检查
虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
2.分配内存
对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。
3.初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这里的零值是指JAVA中字段默认的值。
4.设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。
5.执行init方法
执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化(构造方法),这样一个真正可用的对象才算完全产生出来。
6.补充
类加载的初始化是静态变量和静态代码块,对象的初始化是代码块和构造器
HotSpot虚拟机是java虚拟机中主要使用的一种,我们平时所说的java虚拟机是泛指,而Hotspot虚拟机就是其中使用最广泛的一种。
对象的内存布局
在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:对象头、实例数据和对齐填充。
1.Hotspot 虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的自身运行时数据(哈希码、GC 分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
2. 实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。
3. 对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍。
对象的访问方式
Java 程序通过栈上的 reference 数据来操作堆上的具体对象。目前主流的访问方式有①使用句柄和②直接指针两种:
句柄:如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
直接指针:如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。
使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。
四种引用类型
在Java中一共有四种引用类型,分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱。
1.强引用是指在代码中普遍存在的,类似 Object obj = new Object();这类的引用,只要强引用还存在,垃圾回收器永远不会回收掉引用的对象。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为 null,这样一来,JVM 就可以适时的回收对象了
2.软引用 是用来描述一些还有用但并非是必要的对象。对于软引用着的对象,在系统将要发生内存溢出异常之前,将会把这类对象列进回收范围进行第二次的回收。如果这次回收仍然没有足够的内存,就会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
3.弱引用 也是用来描述非必须对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次的垃圾回收之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
4.虚引用,它是最弱的一种引用关系。无法通过虚引用来取得一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集时收到一个系统通知。虚引用必须和引用队列(ReferenceQueue)联合使用。
在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
对象回收
对对象进行标记,判断是否回收。
对象标记算法
1.引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。
这个方法实现简单,效率高,但是它很难解决对象之间相互循环引用的问题。
2.可达性分析算法
2.1概念:
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。
2.2可以作为GC Roots 的对象
虚拟机栈中的引用的对象
全局的静态的对象,也就是使用了 static 关键字
常量引用,就是使用了 static final 关键字
本地方法栈中引用的对象
如何判断一个对象是否可以回收
宣告一个对象死亡,至少要经历两次标记
1.第一次标记
如果对象进行可达性分析算法之后没发现与 GC Roots 相连的引用链,那它将会第一次标记并且进行一次筛选。当对象没有覆盖 finalize () 方法、或者 finalize () 方法已经被 JVM 执行过,则判定为可回收对象。如果对象有必要执行 finalize () 方法,则被放入 F-Queue 队列中。稍后在 JVM 自动建立、低优先级的 Finalizer 线程(可能多个线程)中触发这个方法.
2.第二次标记
GC 对 F-Queue 队列中的对象进行二次标记。如果对象在 finalize () 方法中重新与引用链上的任何一个对象建立了关联,那么二次标记时则会将它移出 “即将回收” 集合。如果此时对象还没成功逃脱,那么只能被回收了。
内存分配
对象创建的第二步是给对象分配内存。
内存分配的两种方式:
指针碰撞
空闲列表
选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是 “标记 - 清除”,还是 “标记 - 整理”(也称作 “标记 - 压缩”),值得注意的是,复制算法内存也是规整的,关于垃圾回收器和垃圾回收算法,我会在下一章节展开。
1.指针碰撞
场景:堆内存规整,没有内存碎片。
原理:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界指针,只需要向着没有用过的内存区域移动对象内存大小位置即可
2.空闲列表
场景:堆内存不规整,有内存碎片
原理:虚拟机会维护一个列表,该列表会记录那些内存是可用的,在分配的时候找一个足够大的内存区域划分给对象实例,并更新列表
内存并发分配,采用下面两种方式保证线程安全:
CAS + 失败重试:CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
TLAB(Thread Local Allocation Buffer):为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配
内存泄露和内存溢出
内存泄露
1.定义
当某些对象不再被应用程序所使用,但是由于仍然被引用而导致垃圾收集器不能释放 (Remove, 移除) 他们.这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。
2.场景
长生命周期的对象持有短生命周期对象的引用
修改对象的地址(常见于hashset的哈希值)从而无法找到该对象。
机器的连接数和关闭时间设置,长时间开启非常耗费资源的连接
内存溢出
1.定义
指程序运行过程中无法申请到足够的内存而导致的一种错误。
2.场景
堆内存溢出(对象过多)
方法区内存溢出(加载的类过多)
线程栈溢出(递归太深或者方法层级过多)
总结:
内存泄露是内存溢出的主要诱因,内存泄漏的堆积最终会导致内存溢出,不是唯一因素。
如何避免:
尽早释放无用对象的引用
使用字符串处理,避免使用 String,应大量使用 StringBuffer,每一个 String 对象都得独立占用内存一块区域
尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收避免在循环中创建对象
开启大型文件或数据库。不要一次拿了太多的数据
1.说一下对象创建的过程?对象有哪几部分构成?虚拟机如何访问对象?
2.java 中都有哪些引用类型?
3.怎么判断对象是否可以被回收?
4.内存泄露和内存溢出分别是什么?什么原因造成?如何避免?
5.给对象分配内存如何保证线程安全?
答案在上面的内容都可以找到,如果还有疑问,可以在公众号回复:基础 200109
获取详细答案。
▇ 扫码关注我们 ▇
Java面试那点事
微信号 : javajob666