《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》阅读

本文详细介绍了《深入理解Java虚拟机》一书的内容,涵盖运行时数据区域,如程序计数器、虚拟机栈、本地方法栈、Java堆、方法区和直接内存。讨论了对象创建、内存布局、对象访问以及垃圾收集器的工作原理,包括引用计数、可达性分析等算法。此外,还详细阐述了各种垃圾收集器,如Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS和G1的特性和应用场景。
摘要由CSDN通过智能技术生成

来源:《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》

作者:周志明

 

 

运行时数据区域:

程序计数器:

      较小的内存空间,当前线程所执行的字节码的行号指示器。作用:程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖计数器;

Java虚拟机栈:

     线程私有,生命周期与线程相同。作用:存储局部变量表、操作数栈、动态连接、方法出口等信息。 ( HotSpot虚拟机的栈容量是不可以动态扩展的,以前的Classic虚拟机倒是可以。所以在HotSpot虚拟 机上是不会由于虚拟机栈无法扩展而导致OutOfMemoryError异常——只要线程申请栈空间成功了就不 会有OOM,但是如果申请时就失败,仍然是会出现OOM异常的???)

本地方法栈:

       为虚拟机使用到的本地方法服务。(作用:类似于Java虚拟机栈,具体虚拟机可以根据需要自由实现它)

Java堆:

       被所有线程共享的一块内存区域,虚拟机启动时创建。作用:存放对象实例。所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区 (Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。不过无论从什么角度,无论如 何划分,都不会改变Java堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java 堆细分的目的只是为了更好地回收内存,或者更快地分配内存。

方法区:

       各个线程共享的内存区域,作用:存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。JDK8以前被称为  永久代,JDK8后被称为  元空间。运行时常量池:存放编译期生成的各种字面量与符号引用。(运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量 一定只有编译期才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常 量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的 intern()方法。)

直接内存:

        非虚拟机运行时数据区的一部分,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到 本机总内存大小以及处理器寻址空间的限制。

对象:(HotSpot虚拟机为例)

对象创建:先去检查这个指令的参数是否能在常量池中定位到 一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那 必须先执行相应的类加载过程。在类加载检查通过后,接下来虚拟机将为新生对象分配内存。分配内存方法:“指针碰撞”、”空闲列表“;

对象内存布局:

     划分为三部分:对象头(Header)实例 数据(Instance Data)和对齐填充(Padding)

       对象头部分:包括两类信息。第一类是用于存储对象自身的运行时数据,如哈 希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部 分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32个比特和64个比特,官方称它 为“Mark Word”。

 

第二部分:是类型指针,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针 来确定该对象是哪个类的实例(并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话 说,查找对象的元数据信息并不一定要经过对象本身,)。

实例数据:对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字 段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。这部分的存储顺序会 受到虚拟机分配策略参数(-XX:FieldsAllocationStyle参数)和字段在Java源码中定义顺序的影响。 HotSpot虚拟机默认的分配顺序为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers,OOPs),从以上默认的分配策略中可以看到,相同宽度的字段总是被分配到一起存 放,在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。

对齐填充,这并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作 用。(对象头部分已经被精心设计成正好是8字节的倍数(1倍或者 2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。)

对象访问:

       Java程序会通过栈上的reference数据来操作堆上的具 体对象。reference:一个指向对象的引用,未定义通过什么方式定位和访问堆中对象具体位置。主流访问方式:使用句柄和直接指针。

 

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

        垃圾收集器:主要处理的就是  Java堆和方法区的内存区域。

垃圾回收算法:

       垃圾收集器在对堆进行回收前,第一件事情就 是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(“死去”即不可能再被任何途径使用的对 象)了。

引用计数算法:

        在对象中添加一个引用计数器,每当有一个地方 引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可 能再被使用的。

        缺陷:当对象objA和objB都有字段instance,赋值令 objA.instance=objB及objB.instance=objA,除此之外,这两个对象再无任何引用时。实际上这两个对象已 经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为零,引用计数算法也 就无法回收它们。

 

可达性分析算法:

       基本思路:通过 一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过 程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连, 或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。(为目前主流的垃圾回收算法)

   

        在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:

                ·在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的 参数、局部变量、临时变量等。

·                ·在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。

                ··在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。 ·

                ·在本地方法栈中JNI(即通常所说的Native方法)引用的对象。 ·

                ·Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。 ·

                ·所有被同步锁(synchronized关键字)持有的对象。 ·反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

 

引用:

强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回 收掉被引用的对象。

·软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内 存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常。在JDK 1.2版之后提供了SoftReference类来实现软引用。

·弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只 能生存到下一次垃圾收集发生为止。当垃圾收集器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值