对象的创建

概述

下面简要介绍创建对象的几个重要步骤 :

  • 检查能否在常量池定位到一个类的符号引用,并检查这个符号代表的类是否已被加载,解析和初始化过。如果没有则执行类加载的操作。(即是说对象的引用放在方法区里的)
  • 堆中分配内存,分配有两种方式
    • 指针碰撞(Bump the Pointer)--中间分条线一边已分配,一边未分配
    • 空闲列表(free list)--已分配的空间在一个列表中进行记录

选择哪种分配方式java 堆是否规整决定,而java是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因此,在使用 Serial,ParNew 等带 Compact 过程的收集器时,系统采用的分配算法是指针碰撞,而使用 CMS 这种基于 Mark-Sweep 算法的收集器时,通常采用空闲列表

指针指向问题,分配过程中,为了避免并发情况发生使用了下面两种方式 :
       1.CAS并且失败重试

2.每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),哪个线程要分配内存就在哪个线程的TLAB上分配,只有TLAB用完并分配新的 TLAB 时,才需要同步锁定。

  • 设置对象信息

对象的布局

在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域: 对象头(Header),实例数据(Instance Data)和对齐填充(Padding)。

对象头

对象头包括两部分信息

  • Mark Word :
  • 自身运行时需要的信息,包括 HashCode ,GC分代年龄,锁状态标识,线程持有的锁,偏向线程ID,偏向时间戳等

  • 类型指针 :

对象直线它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例

对齐填充

没什么含义,只是起到占位符的作用,对齐填充。

对象的访问定位

目前主流的访问有使用句柄和直接指针两种。两种的优势对比 :

  • 句柄访问
  • reference 中储存的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。(下面的两张图可以看到句柄方式向实例数据是由另外一个指针指向的!!)

  • 直接指针

速度更快(由下图可以看到,直接指针只需要一次指针定位)

句柄访问

java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例与类型数据各自的具体地址信息。

直接指针

上图。

垃圾收集器与内存分配策略

概述

垃圾收集实际就三个问题 :

  1. 哪些内存需要回收
  2. 什么时候回收
  3. 怎么回收

引用计数算法

效率高,可是存在对象之间相互循环引用的问题。

  1 public class ReferenceGC {
2 public Object instance = null;
3 public void test(){
4 ReferenceGC ob1 = new ReferenceGC();
5 ReferenceGC ob2 = new ReferenceGC();
6 ob1.instance = ob2;
7 ob2.instance = ob1;
8
9 ob1 = null;
10 ob2 = null;
11 //假设在这行发生GC ,两个对象是否能被回收?
12 System.gc();
13
14 }
15 }
16
17
18

可达性分析算法(Reachability Analysis)

通过一系列的称为 “GC Roots” 的对象作为起点,从这些起点开始向下搜索,搜索所走过的路径称之为引用链(Reference Chain)当一个对象到 GC Roots 没有任何引用链相连(用图论的话,就是GC Roots到该对象不可达),则这个对象不可用。

可以成为 GC Roots 的有 :

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 本地方法栈中 JNI (即一般说的Native方法) 引用的对象

引用还可分为:

  • 强引用
  • 软引用
  • 虚引用
  • 弱引用

生存还是死亡

对象被被回收至少经过两次标记,过程如下 :

而判断有没必要执行finalize 方法有两方面 :

  • 对象是否覆盖finalize()方法
  • finalize()方法已经被虚拟机调用过

下面有个Demo 可以简述一下这个过程。

  1 public class FinalizeEscapeGC {
2
3 public static FinalizeEscapeGC SAVE_HOOK = null;
4
5 public void isAlive() {
6 System.out.println("yes,I am still alive :)");
7 }
8
9 @Override
10 protected void finalize() throws Throwable {
11 super.finalize();
12 System.out.println("finalize method executed!");
13 FinalizeEscapeGC.SAVE_HOOK = this;
14 }
15
16 public static void main(String[] args) throws Throwable {
17 SAVE_HOOK = new FinalizeEscapeGC();
18
19 //对象第一次成功拯救自己
20 SAVE_HOOK = null;
21 System.gc();
22 //因为finalize方法优先级很低,所以暂停0.5s等待
23 Thread.sleep(500);
24 if (SAVE_HOOK != null) {
25 SAVE_HOOK.isAlive();
26 } else {
27 System.out.println("no,I am dead :(");
28 }
29
30 //第二次拯救失败
31 SAVE_HOOK = null;
32 System.gc();
33 //因为finalize方法优先级很低,所以暂停0.5s等待
34 Thread.sleep(500);
35 if (SAVE_HOOK != null) {
36 SAVE_HOOK.isAlive();
37 } else {
38 System.out.println("no,I am dead :(");
39 }
40 }}

输出的结果 :

finalize method executed!

yes,I am still alive :)

no,I am dead :(

另外,在完全一样的两端代码片段里,第二次的执行结果确实逃脱失败了。这是因为任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法就不会被再次执行。

最后,在JVM中并不鼓励使用finalize()对象来拯救对象。因此它的运行代码非常高昂而且不确定性大。finalize()方法能做的工作,使用try-finally或者其他方式都可以做的更好更及时。

回收方法区

永久代的垃圾收集主要回收两部分内容 : 废弃常量和无用的类。例如一个字符串“abc”在常量池中,却没有被引用。无用类的回收必须满足以下三个条件 :

  • 该类所有的实例都已经被回收,也就是JAVA 堆中不存在该类的任何实例
  • 加载该类的ClassLoader 已经被回收
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

垃圾收集算法

标记-清除算法(Mark-Sweep)

下图就可以知道标记清除算法的过程,这个算法存在两个不足:

  • 效率不高
  • 产生大量不连续的内存碎片

复制算法(Copying)

分两部分内存,不需要清除的挑出来,复制放在没使用的内存上,然后清理掉需要清理的。现在的商业虚拟机都采用这种收集算法来手机新生代。HotSpot 内存中就有分 Eden 和 Survivor (存活者的意思)区域的比例 = 8  : 1 , 那么我们可以猜想到要是 survivor 的区域不够放了怎么办?不够就先新生代借,这个叫 “分配担保(Handle Promotion)”.

标记-整理算法(Mark-Compact)

结合了以上两个算法,为了避免缺陷而提出。标记阶段和Mark-Sweep算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象,即是说标记-整理使用到的只是一块内存空间,而复制算法是两块。如图:

分代收集算法(Generational Collection)

分代收集法是目前大部分JVM所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将GC堆划分为

  • 老生代(Tenured/Old Generation)
  • 新生代(Young Generation)。

老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。

目前大部分JVM的GC对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照1:1来划分新生代。一般将新生代划分为一块较大的Eden空间和两个较小的Survivor空间(From Space, To Space),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将该两块空间中还存活的对象复制到另一块Survivor空间中。

老生代因为每次只回收少量对象,因而采用Mark-Compact算法

对象的内存分配主要在新生代的Eden Space和Survivor Space的From Space(Survivor目前存放对象的那一块),少数情况会直接分配到老生代。当新生代的Eden Space和From Space空间不足时就会发生一次GC,进行GC后,Eden Space和From Space区的存活对象会被挪到To Space,然后将Eden Space和From Space进行清理。如果To Space无法足够存储某个对象,则将这个对象存储到老生代。在进行GC后,使用的便是Eden Space和To Space了,如此反复循环。当对象在Survivor区躲过一次GC后,其年龄就会+1。默认情况下年龄到达15的对象会被移到老生代中。

参考资料 :

JVM(二) 对象存活判断和垃圾回收算法的更多相关文章

  1. 03 JVM 从入门到实战 | 简述垃圾回收算法

    引言 之前我们学习了 JVM 基本介绍 以及 什么样的对象需要被 GC ,今天就来学习一下 JVM 在判断出一个对象需要被 GC 会采用何种方式进行 GC.在学习 JVM 如何进行垃圾回收方法时,发现 ...

  2. JVM调优-Jva中基本垃圾回收算法

    从不同的的角度去划分垃圾回收算法. 按照基本回收策略分 引用计数(Reference Counting) 比较古老的回收算法.原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回 ...

  3. java虚拟机学习-JVM调优总结-新一代的垃圾回收算法(11)

    垃圾回收的瓶颈 传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用带来的负担降到了最小,把应用的吞吐量推到了一个极限.但是他无法解决的一个问题,就是Full GC所带来的应用暂停.在一些对实时性要 ...

  4. JVM知识(五):垃圾回收算法

    在介绍垃圾回收算法之前,我们需要先了解一个词“stop the world”,stop the world会在执行某一个垃圾回收算法的时候产生,JVM为了执行垃圾回收,会暂时java应用程序的执行,等 ...

  5. jvm 调优(2)垃圾回收算法

    可以从不同的的角度去划分垃圾回收算法: 按照基本回收策略分 引用计数(Reference Counting): 比较古老的回收算法.原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数. ...

  6. JVM内存分配策略,及垃圾回收算法

    本人免费整理了Java高级资料,一共30G,需要自己领取;传送门:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q 说起垃圾收集(Garbage Co ...

  7. JVM学习总结二——垃圾回收算法

    昨天总结了JVM内存分区相关的知识,这次我们将来了解下JVM的另一个核心知识点——垃圾回收算法.这一部分其实并不太难,如果对操作系统的内存处理算法有所了解,那么这部分算法其实只看名字就能明白,两者在原 ...

  8. JVM垃圾回收算法及回收器详解

    引言 本文主要讲述JVM中几种常见的垃圾回收算法和相关的垃圾回收器,以及常见的和GC相关的性能调优参数. GC Roots 我们先来了解一下在Java中是如何判断一个对象的生死的,有些语言比如Pyth ...

  9. Java基础:JVM垃圾回收算法

    众所周知,Java的垃圾回收是不需要程序员去手动操控的,而是由JVM去完成.本文介绍JVM进行垃圾回收的各种算法. 1. 如何确定某个对象是垃圾 1.1. 引用计数法 1.2. 可达性分析 2. 典型 ...

随机推荐

  1. Android 开发怎样做代码加密或混淆?

    欢迎访问网易云社区,了解更多网易技术产品运营经验. 在大公司怎么做android代码混淆的?发现他们的软件用apktool反编译居然没看到classes.dex文件和当前安卓APP加固到底该如何做到防 ...

  2. Linux基础实验(一)

    一)基础实验:1. Unix中常见shell及其命令(shell的缩写)    Bourne shell (sh)      Korn shell (ksh)    C shell (csh)     ...

  3. iOS核心动画之anchorpoint

    anchorpoint是什么 All geometric manipulations to the view occur about the specified point 就是说所有的动画参考点都是 ...

  4. UIView中的tintColor和renderingMode

    tintColor 每一个view都有一个tintcolor,类似于魔法色,实现类似于换肤的效果. 每一个view的subview都集成view的tintcolor,当然subview可以指定自己的t ...

  5. 利用腾讯云为你的域名申请并配置免费SSL一年

    我想,点进来的朋友,应该都知道SSL的重要性吧.这里就简单提一下,大型网站域名只有配置了SSL后,才会更加安全. 现在,微信小程序也开始要求后台必须是SSL配置后的域名了.说了这么多,估计有些人还是有 ...

  6. string.Format("rspauth={0}",

    public string rspauth(string Username, string Realm, string Password, string Nonce, string Cnonce,   ...

  7. Jenkins 发布平台 MSB4064: The "Retries" parameter is not supported & error MSB4063: The "Copy" task could not be initialized

    ____________________________________________________________________________________________________ ...

  8. mysql语句插入前判断数据是否重复

    在mysql中插入数据有时需要判断数据插入是否重复 语句编写:insert into 表(相应字段) select 相应字段 from dual where not exists (select 相应 ...

  9. 透视效果shader(边缘光)

    思路:渲染两次. 1.第一次渲染:利用Greater进行深度测试,当目标被遮挡时,用一个边缘光的效果显示. 2.第二次渲染:正常渲染. 边缘光的思路:观察方向和顶点法向量夹角越大,边缘光越明显.边缘光 ...

  10. day1-Python擅长的领域+学习内容

    Python擅长的领域 WEB开发 Django   Pyramid     Tornado       Bottle    Flask    WebPy 网络编程 Twisted        Re ...