对象的创建

概述

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

  • 检查能否在常量池定位到一个类的符号引用,并检查这个符号代表的类是否已被加载,解析和初始化过。如果没有则执行类加载的操作。(即是说对象的引用放在方法区里的)
  • 堆中分配内存,分配有两种方式
    • 指针碰撞(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. 洛谷P5205 【模板】多项式开根(FFT)

    题面 传送门 题解 考虑分治 假设我们已经求出\(A'^2\equiv B\pmod{x^n}\),考虑如何计算出\(A^2\equiv B\pmod{x^{2n}}\) 首先肯定存在\(A^2\eq ...

  2. 百度地图API —— 制作多途经点的线路导航

    [百度地图API]如何制作多途经点的线路导航——驾车篇   摘要: 休假结束,酸奶小妹要从重庆驾车去北京.可是途中要去西安奶奶家拿牛奶饼干呢!用百度地图API,能不能帮我实现这个愿望呢? ------ ...

  3. TCP的坚持定时器

    一.简介 TCP不对ACK报文段进行确认,TCP只确认那些包含有数据的ACK字段. 如果一个确认丢失了,双方就有可能因为等待对方而使得链连接终止: 接收方等待接受数据,因为已经向发送方通告了一个非0的 ...

  4. git恢复到上次提交

    4个区 5种状态 未修改(Origin) 已修改(Modified) 已暂存(Staged) 已提交(Committed) 已推送(Pushed) 检查修改 已修改,未暂存(检查工作区与暂存区间的差异 ...

  5. Linux系统查找清理磁盘大文件方法

    本文主要介绍Linux系统磁盘使用空间不足时,如何查找大文件并进行清理的方法. 下午使用df-h检查一台服务器磁盘使用空间,发现磁盘已经使用了100%,其中/dev/mapper/vg_iavp-lv ...

  6. docker-compose部署mysql配置

    docker-compose部署mysql配置文件如下 version: ' services: mysql: image: mysql environment: - MYSQL_ROOT_PASSW ...

  7. python学习,day3:函数式编程,带return

    return的主要作用就是,在调用的时候,能知道函数的运行情况,相当于打个标签 # coding=utf-8 # Author: RyAn Bi def test1(): print('in the ...

  8. 洛谷 P4307 [JSOI2009]球队收益 / 球队预算(最小费用最大流)

    题面 luogu 题解 最小费用最大流 先假设剩下\(m\)场比赛,双方全输. 考虑\(i\)赢一局的贡献 \(C_i*(a_i+1)^2+D_i*(b_i-1)^2-C_i*a_i^2-D_i*b_ ...

  9. [八分之三的男人] POJ - 1741 点分治 && 点分治笔记

    题意:给出一棵带边权树,询问有多少点对的距离小于等于\(k\) 本题解参考lyd的算法竞赛进阶指南,讲解的十分清晰,比网上那些讲的乱七八糟的好多了 不过写起来还是困难重重(史诗巨作 打完多校更详细做法 ...

  10. redis安全(加入密码)

    一.前言 在使用云服务器时,安装的redis3.0+版本都关闭了protected-mode,因而都遭遇了挖矿病毒的攻击,使得服务器99%的占用率!! 因此我们在使用redis时候,最好更改默认端口, ...