jvm全面入门

本文详细介绍了Java虚拟机(JVM)的运行区结构,包括GC(垃圾回收)的演化过程,如Serial、ParallelScavenge、CMS和G1等垃圾回收器的特点。此外,还涵盖了类加载器的工作原理,以及类加载器的分类和双亲委派机制。
摘要由CSDN通过智能技术生成

jvm

运行区结构

image-20231012132612858

GC 的演化

image-20231012143719178

基础

java从编码到执行

image-20231012143930515

jvm 架构模型

基于栈的指令集架构

image-20231015170534767

寄存器的指令集架构

image-20231015170543201

字节码文件组成—常量池

image-20231015181858155

i=i++

image-20231015184141905

类的生命周期

image-20231015220522055

image-20231015221008563

image-20231015221201485

image-20231015221243590

image-20231015221538690

查看内存中的对象

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

import java.io.IOException;

public class main {
    private static final String a = "dd";
    public static void main(String[] args) throws InterruptedException, IOException {
        main m = new main();
        int i = 2;
        System.in.read();
    }
}

// 可以看见static存放在堆里面

image-20231015222923438

image-20231015223056316

验证内容

image-20231015225516357

连接

image-20231015225635725

image-20231015225700227

image-20231015225822529

解析

image-20231015230202062

image-20231015230210952

初始化

image-20231015230636650

image-20231015231120466

image-20231015231537469

面试题

image-20231015233150964

image-20231015233305439

image-20231015233525488

类加载器

image-20231015234436344

类加载器的分类

image-20231015234529656

打印启动类加载器

image-20231015235336219

import java.io.IOException;

public class zz {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IOException {
        ClassLoader classLoader = String.class.getClassLoader();
        System.out.println(classLoader);
        System.in.read();
    }
}

image-20231015235318195

jar包的扩展

image-20231015235522899

-Xbootclasspath/a:D:/SpringProjectClassPath/student/target/student-1.0-SNAPSHOT.jar

image-20231016000440039

默认类加载器

image-20231016000828520

扩展类加载器加载javascript代码

image-20231016001349268

import jdk.nashorn.internal.runtime.ScriptEnvironment;

public class cc {
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader classLoader = ScriptEnvironment.class.getClassLoader();
        System.out.println(classLoader);
    }
}

image-20231016001308840

image-20231016001440985

image-20231016001814854

应用程序类加载器

这个加载本身和第三方

使用arthas查看类加载器

image-20231016001859103

双亲委派机制

image-20231016003044486

使用arthas展示继承关系

image-20231016003234858

image-20231016003543761

image-20231016003637950

image-20231016003703314

image-20231016003742326

image-20231016003754889

三个问题

image-20231016004018826

image-20231016004039825

打破双亲委派机制

image-20231016124819470

image-20231016125546436

命令

新创建的对象,是保存在伊甸园空间的(Eden)。那些经历多次GC依然存活的对象会经由幸存者空间(Survivor)转存到老年代空间(Old generation)

也有例外出现,对于一些大的对象(指需要占用大量连续内存空间的对象)则直接进入到老年代。
Java提供了 -XX:PretenureSizeThreshold 来指定对象大于这个值,直接分配到老年代

 -XX:PretenureSizeThreshold

常用的垃圾回收器

每一个回收器都存在Stop The World 的问题,只不过各个回收器在Stop The World 时间优化程度、算法的不同,可根据自身需求选择适合的回收器。
目前应用范围最广的,应该还是JDK8,它默认使用的是 Parallel Scavenge + Parallelo Old 收集器组合。

Serial(-XX:+UseSerialGC)

Serial 是Java虚拟机初代收集器,在JDK1.3之前是Java虚拟机新生代收集器的唯一选择,这是一个单线程工作的收集器。在进行垃圾回收的时候,需要暂停所有的用户线程,直到回收结束。

虽然历史久远,但它依然是HotSpot虚拟机运行在客户端模式下,或者4核4GB以下服务端的默认新生代收集器,这种核心数和内存空间较小的场景下,它单线程的优势就体现出来了,没有线程交互的开销,加上内存空间不大,单次回收耗时几十毫秒,这点停顿时间,完全是可以接受的。

Serial 负责收集新生代区域,它采用标记-复制算法。

img

Serial Old(-XX:+UseSerialOldGC)

SerialOld 是 Serial 收集器的老年代版本,和 Serial 一样,它也是单线程的收集器。目前主要应用在客户端模式(Client VM)下的HotSpot虚拟机使用。

如果在服务端模式(Server VM)下,它也有两种用途:一个是在JDK5以及之前,和Parallel Scavenge收集器搭配使用,另外一个就是作为CMS收集器在出现并发模式故障(Concurrent Mode Failure) 时作为后备收集器。

SerialOld 负责收集老年代区域,它采用标记-整理算法。

img

ParNew(-XX:+UseParNewGC)

随着计算机的核心数和内存容量都在飞速发展,多核心和大内存容量的场景下,Serial 收集器单线程的性能明显比较落后了,ParNew 就是 在Serial 收集器的基础之上,实现了它的多线程版本。它可以多条线程同时进行垃圾收集,这也是它和 Serial 收集器的最大的区别,其他的功能性、配置、策略等等的和 Serial 基本一致。

ParNew有一个比较重要的知识点,在JDK9之后,Java官方取消了ParNew和除了CMS收集器之外的所有老年代收集器的搭配,而且还取消了 - XX:+UseParNewGC 这个参数。所以JDK9之后,ParNew只能和CMS搭配使用了。

ParNew 负责收集新生代区域,它采用标记-复制算法。

img

ParNew 是JDK7之前 Server VM 模式下的首选的新生代收集器。但是在单CPU的情况下,它的效率不会比 Serial收集器高的,所以要注意使用场景。

Parallel Scavenge(-XX:+UseParallelGC)

Parallel Scavenge 从外观上看,和 ParNew 很相似,都是新生代的收集器,支持多线程并行回收,也同样是使用标记-复制来作为回收算法。但 Parallel Scavenge 的关注点不一样,它的目标是实现一个可控制吞吐量的垃圾收集器。

吞吐量的计算公式:运行用户代码时间 / (运行用户代码时间 + 运行垃圾收集时间)

假设运行用户代码时间是 99 分钟,运行垃圾收集时间是 1 分钟,结合计算公式 :吞吐量 = 99 / (99 + 1) = 0.99,也就是 99% 的吞吐量。

Parallel Scavenge 收集器提供了一些参数,给用户按自身需求控制吞吐量:

-XX:MaxGCPauseMillis
控制垃圾收集停顿的最大时间,单位是毫秒,可以设置一个大于0的数值。
不要想着把这个数值设置得很小来提升垃圾收集的速度,这里缩短的停顿时间是以牺牲新生代空间大小换来的,空间小,回收自然就快,停顿时间自然也短,但是空间小,吞吐量自然也会小。所以得综合考虑。

-XX:GCTimeRatio
设置垃圾收集时间占比的计算因子,参数范围是0 - 100的整数。它的公式是 1 / (1+GCTimeRatio)
举个栗子:当设置成15,那就是 1 / (1+15) = 0.0625,就是允许最大垃圾收集时间占总时间的6.25%,当设置成99的时候,就是 1 / (1+99) = 0.01,也就是允许最大垃圾收集时间占总时间的1%,依次类推。

-XX:+UseAdaptiveSizePolicy
动态调整开关,这个参数和 Parallel Scavenge 收集器无关,但是搭配起来使用是一个很好的选择。
当这个参数被激活,就不需要人工指定新生代的大小、Eden和Survivor区的比例、对象直接进入老年代的大小等等细节参数了,JVM会根据当前运行的情况动态调整,给出最合适的停顿时间和吞吐量。搭配以上两个参数,和把基本的内存数据设置好即可,例如堆的最大占用空间等等。

Parallel Old(-XX:+UseParallelOldGC)

就像 Serial Old 是 Serial 的老年代版本一样,Parallel Old 是 Parallel Scavenge 的老年代版本。

Parallel Old 也支持多线程并行回收的能力,使用标记-整理来作为回收算法。这个收集器是JDK6的时候推出的,和 Parallel Scavenge 搭配,在多CPU核心和大内存的场景下,吞吐性能优秀。

img

在注重吞吐量和多CPU核心的情况下,都可以优先考虑 Parallel Scavenge + Parallelo Old 收集器,这也是JDK8默认的垃圾收集器组合

CMS (-XX:+UseConcMarkSweepGC)

CMS(Concurrent Mark Sweep) 是JDK1.4后期推出的GC收集器,它是一款并发低停顿的收集器,对于响应速度有较高要求,对停顿时间忍受度低的应用,非常适合使用CMS作为垃圾收集器。

CMS 负责收集老年代区域,它采用标记-清除算法。

它的运行过程相对于前几个来说会复杂一些,可以分为四个步骤:

1、初始标记(CMS initial mark)

这个阶段需要 Stop Tow World(暂停暂停所有用户线程),但这个阶段的速度很快,因为只标记和根节点(GC Roots)直接关联的对象。

2、并发标记(CMS Concurrent mark)

这个阶段不需要 Stop Tow World,在初始标记完成后,并发标记从GC Roots直接关联的对象开始,遍历整个引用链,这个阶段耗时较长,但用户线程可以和GC线程一起并发执行

3、重新标记(CMS remark)

这个阶段需要 Stop Tow World,因为并发标记阶段,用户线程和标间线程同时在运行,相当于一边扫地一边丢垃圾,重新标记就是修正用户线程继续运行,导致的变动的那一部分对象。这一阶段的耗时比初始标记长一些,但远没有达到并发标记阶段那么长的时间。这个阶段可以多线程并行标记。

4、并发清理(Concurrent sweep)

这个阶段不需要 Stop Tow World,执行到这里,说明标记阶段已经完成,此时遍历整个老年代的内存空间,清理掉可回收的对象,由于不需要移动整理存活的对象,这个阶段可以允许用户线程和回收线程并发执行。在清理完成后,会重置CMS收集器的数据结构,等待下一次垃圾回收。

img

以上4个步骤可以看出,CMS之所以能实现低延迟,是因为它把垃圾搜集分成了几个明确的步骤,在一些耗时较长的阶段实现了用户线程和GC线程并发执行的能力。用两次短暂的 Stop Tow World 来代替了其他收集器一整段长时间的 Stop Tow World

CMS确实是非常优秀的垃圾收集器,但它也是有缺点的:

**1、内存碎片。**由于使用了 标记-清理 算法,回收结束后会产生大量不连续的内存空间,也就是内存碎片。

**2、GC进行时会降低吞吐量。**由于使用了并发处理,很多情况下都是GC线程和应用线程并发执行的,GC线程肯定会占用一部分计算资源,这个期间会降低一部分吞吐量(尽管这样,也比之前几个收集器好很多)。

**3、浮动垃圾。**CMS有两个阶段是可以用户线程和GC线程并发执行的,用户线程的继续执行自然会伴随垃圾的不断产生,这些就是浮动垃圾。这些垃圾只能等下次触发GC的时候才能清除了,也因为这些浮动垃圾的存在,CMS收集器需要留一手,JDK5的时候,在老年代内存空间使用了68%的时候就会触发一次GC,到了JDK6,觉得JDK5的这个设置太保守了,所以调整到了92%。

可以通过-XX:CMSInitiatingOccupancyFraction 调整这个阈值

Garbage First(G1)

G1 是 Garbage First 收集器的简称,它在JDK7的时候立项,JDK8 Update 40的时候才全部完工。这个收集器在JDK9 的时候成为了服务端模式下的默认垃圾收集器。

G1 收集器的设计理念是:实现一个停顿时间可控的低延迟垃圾收集器

G1 依然遵循分代回收的设计理论,但它对堆(Java Heap)内存进行了重新布局,不再是简单的按照新生代、老年代分成两个固定大小的区域了,而是把堆区划分成很多个大小相同的区域(Region),新、老年代也不再固定在某个区域了,每一个Region都可以根据运行情况的需要,扮演Eden、Survivor、老年代区域、或者Humongous区域。

大对象会被存储到Humongous区域,G1大多数情况下会把这个区域当作老年代来看待。如果对象占用空间超过Region的容量,就会存放到N个连续的 Humongous Region 中。

img

G1 收集器的内存空间结构

收集器的运行过程可以大致分成四个步骤:

初始标记(Initial Marking)- Stop Tow World

只标记 GC Roots 能直接关联的对象,还有一些额外的细节操作例如修改TAMS指针的值,保证后续阶段用户程序并发运行的时候,新对象分配在正确的位置。这个阶段需要暂停用户线程,但耗时很短。

并发标记(Concurrent Marking)- No Stop Tow World

从根节点(GC Root)开始,顺着引用链遍历整个堆,找出存活的对象。这个步骤耗时较长,但用户线程可以和GC线程并发执行。

最终标记(Final Marking)- Stop Tow World

处理并发标记阶段,用户线程继续运行产生的引用变动,这个阶段需要暂停用户线程,支持并行处理。

筛选回收(Live Data Counting and Evacuation)- Stop Tow World

根据以上三个阶段标记完成的数据,计算出各个Region的回收价值和成本,再根据用户期望的停顿时间来决定要回收多少个Region。回收使用的是复制算法,把需要回收的这些Region里存活的对象,复制到空闲的Region中,然后清理掉旧Region全部空间。因为需要移动存活的对象,所以不可避免的要暂停用户线程,这个步骤支持多条线程并行回收。

img

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值