巨大的JVM知识点思维导图

之前总结过的一份JVM思维导图,分享一下。撑撑博客数量,哈哈。。

图太大了,无法上传,传一份文字版吧。需要图的可以移步我的资源里下载。思维导图

 

类加载子系统
    加载
        字节码文件通过加载器CLassLoader加载到内存元空间
            ClassLoader
                BootstrapClassLoader
                    启动类加载器,顶级类加载器,负责加载JDK核心包rt.jar等,由C编写,所以在java中没有类与之对应,无法获取。
举例就是获取String变量的Class实例的Classloader返回NULL
                ExtensionClassLoader
                    扩展类加载器,负责加载系统变量java.ext.dirs目录下的jar,他的双亲是Bootstrap
                AppClassLoader
                    App应用类加载器,负责加载java.class.path下和我们写的类的类加载器,他的双亲是Ext
                自定义CLassLoader
                    自定义ClassLoader,一般由开发人员实现,低于AppCLassLoader,他的双亲是App
    连接
        验证
            验证classloader加载的字节码文件的正确性
                文件格式验证
                版本号验证
                字节码文件验证
        准备
            在JVM层面为程序的的常量赋值,当存在static final类型的变量时,程序会在JVM层面上将值直接放入常量池。这里并没有执行任何字节码代码。
        解析
            符号引用
                符号引用即以一组符号来描述所引用的目标。例如People类中引用了Langage类,编译成.class文件后,会有一个符号引用Constant_Fieldref_info langage这只是一个和内存地址无关的字面量。只是为了可以唯一的表示出当前对象即可。
            直接引用
                直接引用即对象的内存指针。如果有一个对象有了直接引用,那么必定被加载到了内存中。
    初始化
        这个阶段开始开始执行字节码文件,java文件被编译成字节码文件后,会生成一个cinit和一个init方法
            clinit
                这个方法是java文件被编译成字节码文件时自动生成的。只在初始化阶段执行一次
                主要目的是对静态变量、静态代码块等变量赋值。当存在继承时,会先执行父类的clinit再执行子类的clinit
            init
                这个方法对应的是类的构造器,主要是响应new关键字。用于创建对象实例。


    JVM为每个线程分配的一块私有内存
        为什么线程私有内存要被设计成栈这种数据结构?
            因为代码在执行过程中,都是一个方法套着一个方法的,所以其实是一种FILO的访问模式。所以使用栈这种数据结构最为合适
    栈帧
        每进入一个方法,就会想栈中压入一个栈帧,即为当前帧
        局部变量表
            当前实例方法的自身引用this;静态方法则不存在这项
            局部变量
            基本数据类型
                long、double是64位长度,会占用两个局部变量slot
            引用类型的变量
                其实这个也是GC Roots的枚举列表
        操作数栈
            主要用于存放计算的中间结果
    栈上分配
        对方法内的对象进行逃逸分析,如果不存在逃逸可能,将小对象直接分配在栈上。
        好处是栈帧会随着线程执行完毕被回收。无需进行GC,减轻GC压力。
        缺点是大对象无法进行栈上分配,并且逃逸对象无法分配。
        开启栈上分配时必须同时开启逃逸分析
-XX:+DoEscapeAnalysis

直接方法栈
    一块线程私有的内存,和栈不一样的地方在于栈用于存放执行我们写的代码的数据,直接方法栈用于native方法

java对象内存结构
    对象头
        mark word
            一个不固定结构的数据结构,用于存放hashcode、GC年龄、偏向锁标志、锁状态等
        类型指针
            确定对象类型
        数组长度
    实例数据
    对齐填充
        hotspot要求对象的大小是8的倍数,cpu的cache line也是这么要求的。这样就可以在cpu读取对象时一次性读取充分利用cpu缓存性能。


    synchronize
        对象锁
        类锁
        java关键字,对方法、对象等加锁,防止并发访问
        偏向锁
            核心思想是资源不会被其他线程竞争,这种情况下就使用无锁操作。
            实现方式:开启偏向锁,在mark word中偏向锁标示开启,当对象被加偏向锁,会把当前获取锁的线程的线程号存入当前对象的mark word,之后每次获取这个对象锁时,判断当前对象头mark word中的线程号是否是当前获取锁的线程,如果是直接不加锁执行。当出现其他线程竞争后,会将新的线程号存入对象头的mark word。如果由于竞争出现导致偏向锁失效,那么锁会升级到轻量级锁。
        轻量级锁
            核心思想是允许一定的资源竞争。使用无锁CAS的竞争的方式。
        锁膨胀
            当竞争过于激烈之后,由于CAS的死循环式的比较并交换。会导致CPU飙升。这时候轻量级锁会膨胀为重量级锁,也就是通过对象的monitor监视器实现加锁。这时的锁最重效率最低
        锁消除
            当我们在编写代码时使用了一些SDK也好,API使用不当也好(比如使用stringbuffer 而不是stringbuilder)可能会导致一些没必要的锁的申请,JVM提供了基于JIT编译模式下,配合逃逸分析技术,对那些不可能存在资源共享竞争的加锁,进行锁消除。加快操作速度。

元空间
    主要用于存放类加载系统加载的class文件的数据结构,一块直接内存。


    一块共享内存,当new一个新对象时,java对象在堆中,变量存放在栈里,并指向堆中对象的内存地址。java堆也是GC的主要区域
    跟堆相关还涉及到java的内存模型JMM(java memory model),有些会把这部分内容规划到java并发编程里。java内存模型由cpu,本地缓存,主内存组成。本地内存是一个虚拟概念,由CPU L1、L2、L3级缓存、写缓冲区等组合而成。
cpu执行代码时当从main memory中获取某个值,会将这个内存从主内存加载CPU的缓存中,然后被加载到cpu,当对某个值进行赋值时,只是对当前缓存中的值进行修改,并将这个值的写操作写入写缓冲区,这时候main memory中的值没变。
写缓冲区,当cpu向内存中写入数据时,由于cpu的执行非常快,而写入一次缓存非常耗时,大约需要100个时钟周期。所以通过写缓冲区将数值刷入main memory。
这里就涉及到一个volatile关键字,当一个变量被volatile所修饰,会在CPU执行时加上一个lock信号,实现一个轻量级多线程同步机制,CPU核心之间有一条数据总线,当volatile变量被某个线程修改值后,会在总线上发布一条当前key失效的消息,其他CPU核心收到会清空大钱cache line中这个变量的值,并且这时候只有当值被重新写会内存后方可重新被加载到。从而保证了变量在多线程环境下的可见性。
    垃圾回收机制GC
        STW(Stop the world),JVM进行GC时是无法和应用程序线程一起执行的。所以每次当出现GC时,应用程序的线程都要被迫暂停,等待GC结束再继续运行。所以GC的STW时间长短,决定了程序运行的流畅程度。
        堆内存是垃圾回收的主要区域,核心内容是垃圾回收算法和垃圾回收器
            对象可达性分析
                垃圾回收主要回收的是垃圾对象,那么就需要判断哪些对象属于垃圾,利用的是可达性分析。当一个对象存在一个及以上的变量引用时对象属于可达状态;当对象失去全部引用后,对象由可达状态转为可恢复状态;这时候如果进行垃圾回收时,会先调用对象的finalize方法,尝试恢复对象,如果对象仍然无法没有获取引用,那么就会标记对象为不可达状态。等待垃圾回收器进行回收。
            引用类型
                强
                    即平时使用的,只要对象存在引用就不会被垃圾回收器回收。
                软
                    当内存不足进行垃圾回收时,软引用的对象可能会被进行回收。这时候再通过变量去获取对象时NPE
                弱
                    和软引用不同的是,只要进行垃圾回收,弱引用类型的变量会立刻清理
                虚
                    几乎等同于没有引用
            垃圾回收算法
                引用计数法(JVM没用)
                    即每个对象配备一个计数器,对象被其他对象所引用计数器+1,当引用失效时计数器-1
                    为什么JVM不使用引用计数法?
                        性能问题,每次产生引用或者引用失效时,都要对计数器加减操作。性能低
                        循环引用问题。AB两个对象都没有在被其他对象引用时互相持有彼此的引用。导致AB对象的引用计数无法归零
                标记清除法
                    标记清除法现代垃圾回收器的思想基础,将垃圾回收划分为两个阶段
                        标记阶段,利用可达性分析。从一个根节点向下遍历,搜索到的对象被标记为可达对象;无法搜索到的被标记为不可达对象
                            什么是根节点?
                                根节点即是GC Roots。是一组必须活跃的引用。一般有
                                    栈中局部变量表中的引用类型变量所指向的堆中对象
                                    本地方法栈中native引用的对象
                                    常量引用的对象
                                    静态变量引用的对象
                                举例:一个线程在执行中,栈的局部变量表中存在一个变量指向堆中A对象,A对象中引用了B对象。这时候进行标记时,A对象会被认为是GC Roots对象,从这个对象往下找到B对象。AB都是可达对象。当进行回收时,内存中的AB对象不会被清理,而没有引用的CDE对象会被清理掉。
                        回收阶段,标记阶段未被标记的对象都认为是不可达对象,一次性进行清除
                        标记清除法有个很明显的缺点,因为可达对象并不是内存连续的,所以清理之后,所有可达对象是零散分布在内存中的。这就导致了大量的内存碎片
                标记压缩法
                    对标记清除法的内存碎片问题进行了优化,标记阶段和标记清除法一致;回收阶段标记压缩法会先把可达对象压缩到内存一端,之后一次性清理边界之外的所有对象
                复制算法
                    核心思想是将内存分为AB两块内存,每次只使用其中一块。当需要GC时,将存活对象从A复制到B中,全部存活对象复制结束后,将A清空,交换两个内存的角色。
                    优点是当存活对象少时非常高效,并且处理后,没有碎片问题。
                    缺点是如果使用了复制算法,内存就要折半。非常浪费资源。
                分代算法
                    核心思想是将内存分为年轻代和老年代,不同的代使用不同的垃圾回收算法。进行可能减少STW时间加快回收速度
                        新生代
                            绝大多数的新对象被分配在新生代,新生代的特点就是朝生夕灭。不可达对象数量 > 可达对象数量。所以特别适合于使用复制算法,效率高,内存碎片少,移动对象少。当对象经历多次回收仍存活,将会转入老年代。
                        老年代
                            即被多次回收仍存活的对象存储区域。特点是对象几乎都是可达对象,数据量比较大,并且不易移动。所以特别不适合于使用复制算法,所以一般采用标记清除或者标记压缩法。
                分区算法
                    每次GC时,GC的时间会随着内存的增大时间而变长。所以分区算法的核心思想是将大内存拆分为多个小内存。每次可以只清理其中部分内存区域。并且小内存也会减小GC的STW时间
                常见模型
                    GC垃圾回收的经典模式是由上述所有的内容组合而成的。首先将内存划分为新生代和老年代,然后将年轻代分区为eden区和survivors区。
                        新生代
                            eden区
                                所有的新对象都被创建在eden区,当发生GC时,他会将存活对象年龄+1,然后复制到survivors区
                            survivors区
                                幸存者区域,当对象在eden区幸存下来会被放入幸存者区,幸存者区采用复制算法,from和to是两块相同的内存区域。每次清空其中一块区域,将幸存对象移动到另一块中。直到他们的年龄到达15次,进而存入老年代。
                                from区
                                to区
                        老年代
                            老年代一般采用标记压缩法进行Full GC,所以一般都会比较慢。调优时应当尽量避免Full GC的出现。
            垃圾回收器
                串行新生代垃圾回收器
                    简单、稳定可靠、没有线程间切换的性能消耗
                    复制算法
                    独占式回收,STW影响体验
                    JVM client模式下的默认新生代垃圾回收器
                串行老年代垃圾回收器
                    简单、稳定可靠、没有线程间切换的性能消耗
                    标记压缩法
                    独占式回收
                并行新生代parNew
                    串行新生代serialGC的多线程版本
                    独占式回收
                    复制算法
                    适用于多线程能力强的CPU
                    一般线程数选择与核心数一致
                新生代parallelGC
                    关注吞吐量的并行垃圾回收器,可以通过参数配置GC的最大STW时间或者GC所占用的总百分比
                    独占式回收
                    复制算法
                老年代parallelOldGC
                    和parallelGC一致的是关注吞吐量的并发老年代垃圾回收器。
                    标记压缩法
                    独占式回收
                CMS
                    采用并发标记的方式实现了非独占式回收
                G1
                    将内存先进行分区,每次只清理一部分内存,加快内存GC速度
                    分区为eden区,survivor区,old区等
                    采用并发标记方式,实现了非独占式回收
                        G1进行GC时先进行初始化标记,这时候必然会开启一次新生代GC。eden区被清空,survivor区存在对象
                            什么是G1的新生代GC?
                                G1的新生代GC是一个独占式的回收过程,主要回收的是分区里的eden区和survivor区,回收结束会清空eden区,并将存活对象移动到survivor区。此时也可能存在old区变大或增多,因为survivor区到年龄的对象可能会升到老年代的old区。
                        此时进入并发标记阶段,根据survivor区域中的GC Roots对象开始进行标记,这个标记过程实现了和应用程序的并发执行。但是此时如果再出现一次新生代GC,那么也会受到STW的影响。
                        然后进入重新标记阶段。此时第一次的并发标记完成,但是这期间由于是存在应用程序运行的,又会出现新的垃圾。此时使用一个独占式的重新标记阶段,对并发标记的结果进行修正补充
                        然后进入独占清理阶段,此时会先计算每个区域的存活对象和回收比例,再排序。对需要进行混合回收的区域做标记。
                        再进入并发清理阶段,和应用程序并发执行。此时已经识别出了哪些区域完全空闲,进行回收。
                        然后进入混合回收阶段,上述已经进行过并发清理,但是只回收了很少一部分的完全空闲区域。有一些区域在独占清理阶段被标记为需要混合回收,这里主要是回收这些区域的内存。这个阶段会执行一次新生代GC,也会选取一些old区域进行回收。同时处理了新生代和老年代,清理结束后,eden区又被清空,同时有些old区也被清理。但这里因为触发了新生代GC,所以又会出发一次新的初始化标记。这样循环往复,直到G1认为已经清理出了足够的内存为止。当然在这个过程中,如果出现内存不足时,G1也会进行一次大的Full GC
                        所以G1是一个不分新生代、老年代的垃圾回收器
                        G1的目标就是CMS的长期替代方案

执行引擎
    JIT
        即时编译技术
        我们从前都说java是一门半编译半解释型语言。先将java文件编译成字节码文件,再同过解释器一条条解释成机器码执行的。而解释执行的执行效率不高。虽然java提供了两种解释执行的方案:
1、字节码解释器,即对字节码文件一行行进行解释,效率非常低。
2、模版解释器,每一个字节码都对应一个模版,解释执行时直接套用模版,效率比字节码解释器高。
但是相比于直接将源代码编译成机器码执行,效率还是低,所以jvm提供了JIT功能,它可以让字节码文件直接被编译成机器码执行,从而从根本上加快执行速度。
        JVM JIT的三种模式
            解释执行
                即不使用JIT,所有代码都通过解释器执行
            混合执行
                部分热点代码会进行JIT编译执行;而其他代码还是通过解释执行。
                server模式下阀值是10000次,client模式下阀值是1500次。
            编译执行
                即将所有代码通过编译执行,不再进行解释执行
        可以通过java -version中的信息看出来当前的执行模式
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值