JVM01--从一个Java对象的角度看JVM

【摘要】:本篇试着从Java对象创建、使用、消亡三个过程来串联JVM的一些主要知识点,作为JVM系列的第一篇博文,意在对整个专栏文章的写作脉络进行一个铺陈。

1.Java对象的创建

当我们使用new关键字创建一个对象时,JVM大致会做如下几个工作:

1.1 类加载检查

        判断能否在常量池中定位到一个类的符号的引用。

      (1)如果能,则说明已经被加载,检查这个符号引用代表的类是否被加载、解析、初始化。

      (2)如果不能,则先进行类的加载过程。

        这里会引出类的《JVM04--类的加载》、《JVM05--常量池》等知识。

1.2 内存分配

        JVM 通过Java对象的类元数据信息确定对象大小,并为其在堆中分配相应大小的内存,分配方式有以下两种:

  • 指针碰撞:假设堆内存是绝对规整的,通过一个指针来分割使用和未使用内存区域,分配内存时,指针会向未使用区域移动待分配内存大小的距离。
  •  空闲列表:假设堆内存不是绝对规整的,虚拟机通过维护一个列表来区分哪些内存是使用过的和未使用过的,分配内存时,找到一块儿足够大小的内存区域完成内存分配。

        同时,分配内存的时候也需要考虑线程安全问题,有两种解决方案:

  • 第一种是采用同步的办法,使用CAS来保证操作的原子性。
  • 另一种是每个线程分配内存都在自己的空间内进行,即是每个线程都在堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB),分配内存的时候再TLAB上分配,互不干扰。

        这里会引出《JVM02--运行时数据区》等知识。

1.3 初始化零值:

        内存分配完成之后,会给对象初始化零值,这一步是为了保证对象的实例字段在java代码中不赋初始值也能使用。

1.4 设置对象头

        初始化零值之后,就要给对象设置对象头,对象头里的内容大致有这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的gc分代年龄等。这些信息存储在对象头中。另外,根据虚拟机的当前运行状态,如是否采用偏向锁等,对象头也会有不同的设置方式。

1.5 执行init()方法

        执行完上面的步骤之后,在虚拟机里这个对象就算创建成功了,但是对于Java程序来说还需要执行init()方法才算真正的创建完成,因为这个时候对象只是被初始化零值了,还没有真正的去根据程序中的代码分配初始值,调用了init方法之后,这个对象才真正能使用。

2 Java对象的访问

2.1 reference类型

        reference类型是引用类型的一种,JVM规范指出reference类型表示对某个对象的引用,对象的操作、传递和检查都通过引用它的reference类型的数据进行操作;

        在JDK1.2之后对引用的概念进行了细分扩充,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:

【强引用】:

关键词:必不可少

特点:具有强引用的对象,当内存空间不足时也不会被GC回收

【软引用】:

关键词:有用但非必需

特点:具有软引用的对象,在系统将要发生内存溢出之前,将会把这些对象列进可回收范围之中进行第二次回收。如果仍然没有足够的内存,则会抛出内存溢出异常。

【弱引用】:

 关键词:有用但非必需

特点:同软引用的区别在于,被弱引用关联的对象只能生存到下一次垃圾回收发生之前。当垃圾回收器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

【虚引用】

关键词:

特点:也被称为幽灵引用或者幻影引用,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。  

        在java.lang.ref包中提供了三个类:SoftReference类、WeakReference类和PhantomReference类,它们分别代表软引用、弱引用和虚引用。ReferenceQueue类表示引用队列,它可以和这三种引用类联合使用,以便跟踪Java虚拟机回收所引用的对 象的活动。         

2.2 对象访问方式

2.2.1 使用句柄

        在堆中会划分一块儿内存空间作为句柄池,句柄池中存放一个又一个的句柄地址,reference中存储就是对象的句柄地址,在这里句柄地址分为两类:在堆中分配的对象实例数据的地址和对象的类型数据地址。

2.2.2 使用直接指针

        HotSpot虚拟机使用了直接指针的方式访问对象,reference中存储就是在堆中分配的对象实例数据的地址,而对象实例数据中(对象头)存储着这个对象类型数据的相关信息。

3 Java对象的消亡

        前面我们已经简要的学习了Java对象的创建过程、Java对象的访问(使用),任何事物都无法永生,因此这里便引申出Java对象的消亡(GC垃圾回收)过程。

详见《JVM11--垃圾回收》,在该篇我们会对如下知识点进行一个笼统的学习:

3.1 如何判断对象已“死”

        垃圾回收器在对堆中的对象实例进行回收前,首先需要确定哪些对象已经“死去”(即不可能再被使用的对象),常见的算法包括:

3.1.1 引用计数算法(HotSpot未使用)

  • 实现方式:为所有的对象每个都添加一个计数器,每当这个对象被引用一次,计数器加一;当引用失效,计算器减一;计数器为零的对象表示该对象不可能再被使用。
  • 优点:实现简单、效率也比较高。
  • 缺点:运行期间需要维护计数器,带来额外开销,同时无法解决对象之间循环引用问题。

3.1.2 可达性分析算法(HotSpot使用)

  • 实现方式:通过一系列被称为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链时,则证明此对象是不可用的。
  • 优点:可达性分析可以解决引用计数器所不能解决的循环引用问题,即便对象a和b相互引用,只要从GC Roots出发无法到达a或者b,那么可达性分析便不会将它们加入存活对象合集之中。
  • 缺点:在多线程环境下,其他线程可能会更新已经访问过的对象中的引用,从而造成误报(将引用设置为 null)或者漏报(将引用设置为未被访问过的对象)。误报并没有什么伤害,Java 虚拟机至多损失了部分垃圾回收的机会。漏报则比较麻烦,因为垃圾回收器可能回收事实上仍被引用的对象内存。一旦从原引用访问已经被回收了的对象,则很有可能会直接导致 Java 虚拟机崩溃。

引申问题:可达性分析判定不可达的对象,一定是可以回收的吗?

答案:不一定

        

以下几类对象可以被作为GC Roots。
1、方法区静态属性引用的对象
全局对象的一种,Class对象本身很难被回收,回收的条件非常苛刻,只要Class对象不被回收,静态成员就不能被回收。

2、方法区常量池引用的对象
也属于全局对象,例如字符串常量池,常量本身初始化后不会再改变,因此作为GC Roots也是合理的。

3、方法栈中栈帧本地变量表引用的对象
属于执行上下文中的对象,线程在执行方法时,会将方法打包成一个栈帧入栈执行,方法里用到的局部变量会存放到栈帧的本地变量表中。只要方法还在运行,还没出栈,就意味这本地变量表的对象还会被访问,GC就不应该回收,所以这一类对象也可作为GC Roots。

4、JNI本地方法栈中引用的对象
和上一条本质相同,无非是一个是Java方法栈中的变量引用,一个是native方法(C、C++)方法栈中的变量引用。

5、被同步锁持有的对象
被synchronized锁住的对象也是绝对不能回收的,当前有线程持有对象锁呢,GC如果回收了对象,锁不就失效了嘛。

3.2 如何回收已“死”的对象

3.2.1 常见垃圾回收算法

  • 标记-清除算法
  • 复制算法
  • 标记整理算法
  • 分代收集算法

3.2.2 常见的垃圾回收器

  • Serial收集器
  • ParNew收集器
  • Parallel Scavenge收集器
  • Serial Old收集器
  • Parallel Old收集器
  • CMS 收集器
  • G1收集器

3.2.3 内存分配及回收策略

  • 对象优先在Eden分配
  • 大对象直接进入老年代
  • 长期存活的对象将进入老年代
  • 动态对象年龄判定

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值