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

本文深入讲解Java虚拟机(JVM)的内存管理、垃圾回收机制、对象创建与访问、垃圾回收算法及收集器等内容,帮助读者掌握JVM的高级特性和最佳实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下是读完《深入理解Java虚拟机——JVM高级特性与最佳实践(第2版)》这本书的心得,我随便写,各位自己看,有不对的地方请指正,有些地方也没看懂(涉及了很多的编译原理内容),—— 多读书读看报,少吃零食多睡觉。

 第一章,啥也没说讲讲故事发生的背景(比如主人公出生前,都得天降异象),主要是讲述了一下发展史吧,不累述。

第二章,主要讲的是jvm的内存管理:开局一张图,(我从网上copy的)感觉图的记忆比较简单,看图说话,分别介绍下每个部分的作用。

1、先说最简单的程序计数器,线程私有,记录当前线程运行的到哪里,(就像书签,夹好了书放起来,隔两天回来再继续看),程序计数器也一样,当完成线程切换或者循环等其他操作时,需要回到上一次运行指令的地方,如果是native方法则值为空

2、栈,分为java栈 和本地方法栈,区别是啥,本地方法栈执行的是虚拟机需要的native方法。

栈也是线程私有的,与线程的生命周期相同,每个方法执行的时候都会创建一个栈帧,存放,局部变量表,操作数栈,方法出口等,方法的执行对应着栈帧出栈到入栈的过程。

3、堆,被所有线程共享,存放对象实例及数组,垃圾收集器的主要管理区域,可分新生代:eden、form survivor、to survivor;老年代。

4、方法区(方法区相当于接口,jdk7永久代和jdk8元空间是具体实现),被虚拟加载的类的信息,存放常量(存放在常量池,方法区的一部分),静态变量。垃圾回收主要是类的卸载和常量回收。(方法区回收:回收常量池中的常量,常量池中对象不再被引用。类的卸载需要三个条件:①类的所有实例都被回收,②类的ClassLoader也被回收,③ 对应的class对象没有被引用,无法通过反射获取)

5、对象的创建,内存分配,以及访问。对于面向对象语言最常听到的一个名词就是对象,那么对象是如何被创建,分配,以及访问的呢?

当遇到关键字new时,虚拟机会先查找改对象是否存在于常量池中,并且是否被类加载 器 加载过、解析初始化过,如果没有则需要进行类加载,内存分配两种方式:

① 当垃圾回收器带有压缩功能(serial,parnew),即用过的内存在一边,空闲的在另一边,中间用指针隔开,分配内存只需要移动指针即可,这种方式叫做指针碰撞。

② 垃圾收集器不带有压缩功能如:CMS(我们系统使用的就是这种),内存空间零散,大小不一,这时候就需要维护一张表,记录剩余空间的大小,以便存放对象这种方式叫做空闲列表。

除了这两个内存分配地址分配方式 之外,当内存分配的时候其实也存在并发问题,A、B同时请求一块地址,那么改如何分配呢?

这里也有两中选择,① CAS,② 本地线程分配缓冲( TLAB )

CAS是什么,简单点说就是 更新A先要记录A的旧值, A更新为B时判断A的值 是否改变过,如果没有则更新,(ABA问题,忽略先)

②TLAB 内存的分配按线程划分,每块线程有一个独立的内存,内存用尽才会再申请新的,只有这个才会同步锁定。

内存分配完成,需要把分配到的内存空间初始化零值(除了对象头——存放对象的信息,比如:对象哈希码,那个类的实例) 。

之后执行对象的<init>方法,根据程序员的心意进行初始化。

6、对象内存布局;分为三块:对象头,实例数据,补齐填充(如果位不够填充值,薯片包装盒子里的气)

①对象头:作者讲了很多很细,有些也听不动,总结起来如下:一、存放运行时数据(hash码,GC分代年龄——决定对象是否要被回收,线程持有的锁等等)二、对象类型指针(看看下面对象访问的那张图,确定对象是哪个类的实例,如果是一个数组还要存放长度),关于对象头可以参考我的这个博客  Synchronized关键字------------ 对象头_synchronized 对象头线程id_君莫笑_0808的博客-优快云博客

②实例数据,程序中被定义的字段安装定义的顺序和虚拟机的分配策略:long/double, int, shorts/chars,bytes/boolean,oops,相同宽度的会被分配到一起。

7、对象访问:两种方式,句柄访问,直接指针。

① 句柄访问,需要划分出来一块单独的空间,栈中的reference ,直接访问句柄池,句柄池中存储这对象实例数据指针和类型指针,分别对应堆中的实例池,和方法区中的对象类型,如下图:

② 直接指针,直接存储对象地址,由对象存储对象类型数据,如下图:

对比:句柄访问,垃圾回收对象地址移动,句柄池修改,reference不需要修改;直接指针(hotspot采用此方式进行对象访问),减少了一次二次寻址。

第三章:垃圾回收,说说垃圾回收算法,以及几种垃圾回收器。

一、确认什么样的对象需要清除:两种算法

① 引用计数,对象被引用一次则计数器加1,引用失效则减1,当引用计数为0的时候表示对象需要被清理。(循环引用清除不了了)

② 可达法,从根节点GCRoot起向下找,走过的 路径成为引用链,如果对象到GCRoot没有引用连相连则不可达,表示可能被清理。

在Java语言中,可作为GC Roots 的对象包括下面几种

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中静态变量引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈(即一般说的 Native 方法)中JNI引用的对象

③ 被判断为不可达的对象,不会立马被回收掉,需要看对象是否重写了finalize(),如果没有重写,或者已经调用一次,会被放入一个慢队列中,稍后有虚拟机重新标记,如果第二次被标记时依然不可达,则会被回收。

二、垃圾收集算法,一般新生代采用的是复制算法,老年代采用的标记——整理法。

1、标记清除

    标记所有需要被回收的对象,然后回收,缺点:产生大量的空间碎片。

2、复制(主要回收新生代)

    将内存划分为2块,将存活的对象复制到另外一块内存中,将剩余的空间全部清理,缺点:内存利用率低

    在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。

    新生代 90%的对象会“朝生夕死”,所以对于空间的划分 eden:8,form survivor:1,to survivor:1,但是我们不能保证剩余的空间一定够用,这个时候就需要分配担保,如果survivor的空间不足以存放存活的对象,则直接将对象放到老年代,即需要老年代的空间做担保(如果老年代空间也不够则发生fullGC)。新生代老年代的空间占比如下图:

3、标记——整理

    标记--------整理 就是带有整理功能的标记清除,先标记存活的对象然后向一边移动,再清理边界以外的内存空间。

4、分代收集

    就是把堆分成,新生代,老年代,新生代朝生夕死,适合使用复制算法,老年代没有人再为他担保,一般采用标记清除或者标记整理,为什么采用分代收集? 为了减少gc次数,减少stop the world时间。

hotspot算法实现,设置很多个安全点,到达安全点的线程会被挂起,直至gc完成。

三、垃圾收集器

1、serial收集器,单行的收集器,垃圾收集过程会挂起所有正在执行的线程,对应老年代收集器serialOld。

2、parnew收集器,并行收集器,相对于serial,增加了多个线程执行垃圾回收,配合CMS使用。对应老年代收集器parnew。

3、parallel Scavenge收集器(吞吐量优先收集器),关注的是吞吐量,吞吐量=运行代码时间/(运行代码+垃圾收集时间)。

4、CMS收集器(我们系统使用的垃圾收集器,标记——清除),第一款真正并行的收集器,用户和垃圾收集器同时工作,(依然会出现短暂的stop the world),分为四个步骤:

① 初始标记:标记GC Root直接关联到的对象。

② 并发标记:GC Root tracing 查找GC Roots 

③ 重新标记:修正并发标记过程中导致的一些变化的标记。

④ 并发清除:清除标记 的内存空间。

缺点:CMS回收线程 =(CPU数量+3)/ 4,cpu核数较少时占用资源明显。无法清除浮动垃圾,GC的同时在运行客户程序,这部分新产生的垃圾无法被标记回收,称为浮动垃圾,CMS的垃圾回收并不是满了之后才会回收在JDK1.6中设置阈值为82%,如果预留空间不够,则会切换至servial Old收集器,效率大大降低。采用标记——清除产生大量的空间碎片,默认情况下会在每次fullGC前进行一次空间整理,此过程中出现长时间停顿。

5、G1收集器(年老代:标记——整理)

img

6、内存分配与回收

    对象创建完成后,首先会在新生代的Eden分配,如果空间不足则会发生一次minor GC,大对象需要连续的内存空间存放,会造成频繁的minorGC,可以设置参数-XX:PretenureSizeThreshold,直接将他们放入年老代,长期存活的对象进入年老代,每熬过一次minorGC对象年龄会被+1,默认加到15 的时候,会进入老年代,或者内存中相同年龄的对象总和大于survivor空间的一半,发生minor GC,发生minor GC之前由于分配担保,年老代会检测下剩余空间是否大于新生代对象的内存总和,小于则发生一次full GC,或者设置HandlePromotionFailure=true,会更具历次晋升年老代的空间计算一个平均值,如果够用则不发生minorGC,-XX:CMSInitiatingOccupancyFraction=58 是指设定CMS在对内存占用率达到58%的时候开始GC (因为CMS会有浮动垃圾,所以一般都较早启动GC)。

7、常用命令

1、查看堆使用情况,gc次数:jstat -gc 6512(pid) 1000(间隔多少时间打印) 1000(打印多少次)

2、查看GC种类: java -XX:+PrintCommandLineFlags -version

UseParallelGC 即 Parallel Scavenge + Parallel Old,再查看详细信息

查看详情: java -XX:+PrintGCDetails -version

3、jmap -dump:format=b,file=/tmp/jmap_heapdump.hprof (生成文件保存目录)  <pid>

好文章:GC垃圾收集器ParNew&CMS与底层三色标记详解_怎么起个名就那么难的博客-优快云博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值