您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

揭秘JAVA JVM内幕(不合适初学者)

时间:2020-07-06 17:08:17  来源:  作者:

篇文章将重点分析jvm,涉及到的内容包括jvm内存模型,类加载器,GC回收算法,GC回收器,整体偏向于理论。

本篇文章不适合初学者,适合具有3年以上开发经验的技术人员,欢迎大家一起交流分享,文章若有不足之处,欢迎读者朋友们指出,先感谢。

一 明确jdk,jre和jvm之间关系

下图为官网关于jdk,jre和jvm的架构图,从该架构图,很容易看出三者之间关系:

(1)jdk包含jre,而jre又包含jvm

(2)jdk主要用于开发环境,jre主要用于发布环境,当然,发布环境用jdk也没问题,仅仅是性能可能会有点影响,jdk与jre关系有点类似程序debug版本和release版本之间关系

(3)从文件大小来说,jdk比jre大。从图中可以看出,jdk比jre多了一层工具包,如常用的JAVAc,java命令等

揭秘JAVA JVM内幕(不合适初学者)

 

二 类加载器

关于jvm类加载器,可概括为如下图:

揭秘JAVA JVM内幕(不合适初学者)

 

1.为什么要有类加载器?

(1)将字节码文件加载到运行时数据区。.java源码通过Javac命令编译后形成的字节码文件(.class),通过类加载器加载进入jvm中的。

(2)确定字节码文件在运行时数据区的唯一性。相同的字节码文件,通过不同的类加载器,就形成不同的文件,因此字节码文件在运行时数据区的唯一性是由字节码文件和加载它的类加载器共同决定的

2.类加载器的种类

从种类上来划分,类加载器主要划分为四大类

(1)启动类加载器 (根类加载器Bootstrap ClassLoader):该类加载器位于类加载器的最顶层,主要加载jre核心相关jar包,如 /jre/lib/rt.jar

(2)扩展类加载器(Extension ClassLoader):该类加载器位于类加载器层次的第二层,主要加载 jre扩展相关jar包,如/jre/lib/ext/*.jar

(3)应用程序类加载器(Application ClassLoader) App:该类加载器位于类加载器的第三层,主要加载类路径(classpaht)下的相关jar包

(4)用户自定义类加载器(User ClassLoader):该类加载器为用户自定义类加载器,主要加载用户指定的路径下的相关jar包

3.类加载器的机制(双亲委派)

对于字节码的加载,类加载机制为双亲委派,什么叫双亲委派呢?

类加载器获取字节码文件后,不是直接加载,而是将该字节码文件传递给其直接父级类加载器,其直接父加载器又继续传递给其直接父加载器的直接父加载器,依次类推到根父加载器,若根父加载器

能加载,则加载,否则交给其直接孩子加载器加载,直接孩子加载器能加载就加载,若不能,依次类推其直接孩子类加载器,若都不能加载,最后才由用户自定义类加载器加载。

4.jdk 1.8 如何实现类加载器?

如下为jdk 1.8 类加载器的实现,采用递归方式

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

5.破坏双亲委派模型

在某些情况下,由于受加载范围限制,父类加载器无法加载到需要的文件,因此父类加载器需要委托其子类加载器去加载相应的字节码文件。

如在jdk中定义的数据库驱动接口Driver,但该接口的实现却由不同的数据库厂商来实现,这就产生这样一个问题:由启动类(Bootstrap ClassLoader)

执行的DriverManager要加载实现了Driver接口的相关实现类,从而实现统一管理,但Bootstrap ClassLoader只能加载jre/lib下的相应文件,不能加载

由各个厂商实现的Dirver接口相关实现类(Dirver实现类是由Application ClassLoader加载),这时就需要Bootstrap ClassLoader委托其子类加载器加载Driver

来实现,从而破坏了双亲委派模型。

三 类的生命周期

java中的类,在jvm中的生命周期,大概分为五个阶段:

1.加载阶段:获取字节码二进制流,并将静态存储结构转化成方法区的运行时数据结构,且在方法区生成相应的类对象(java.lang.Class对象),作为该类的数据访问入口。

2.连接阶段:该阶段包括三个小阶段,即验证,准备和解析三阶段

(1)验证:确保字节码文件符合虚拟机规范要求,如元数据验证,文件格式验证,字节码验证和符号验证等

(2)准备:为内的静态表里分配内存,并且设置jvm默认值,对于非静态变量,此阶段,不需分配内存。

(3)解析:将常量池内的符号引用转化为直接引用

3.初始化阶段:类对象使用前的一些必要初始化工作

如下引用自一位博友的观点,个人认为解释得很好。

在 Java 代码中,如果要初始化一个静态字段,我们可以在声明时直接赋值,也可以在静态代码块中对其赋值。

除了 final static 修饰的常量,直接赋值操作以及所有静态代码块中的代码,则会被 Java 编译器置于同一方法中,并把它命名为 < clinit > 。初始化的目的是是为标记为

常量值的字段赋值,以及执行< clinit > 方法的过程。Java 虚拟机会通过加锁来确保类的 < clinit > 方法仅被执行一次。

哪些条件会发生类初始化呢?

(1)当虚拟机启动时,初始化用户指定的主类(main函数);

(2)当遇到用于新建目标类实例的 new 指令时,初始化 new 指令的目标类;

(3)当遇到调用静态方法的指令时,初始化该静态方法所在的类;

(4)子类的初始化会触发父类的初始化;

(5)如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;

(6)使用反射 API 对某个类进行反射调用时,初始化这个类;

(7)当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。

4.使用阶段:jvm中使用对象

5.卸载阶段:将对象从jvm中卸载(unload),哪些条件会使jvm发生类卸载呢?

(1)加载该类的类加载器被回收

(2)该类的所有实例已经被回收

(3)该类对应的java.lang.Class对象没有任何地方被引用

揭秘JAVA JVM内幕(不合适初学者)

 

四 jvm内存模型

1.JVM内存模型是怎样的?

如下为JVM内存模型架构图,由于在之前的文章中论述过,这里就不再一 一论述,主要讲解堆区。

揭秘JAVA JVM内幕(不合适初学者)

 

在jdk 1.8前,堆区主要分为新生代、老年代和永久代。jdk 1.8后,去掉了永久代,增加了MetaSpace区。这里,主要分享jdk 1.8。

根据jdk1.8,堆区逻辑抽象为三个部分:

(1)新生代:包括Eden区,S0区(也叫from区),S21(也叫TO区)

(2)老年代

(3)Metaspace区

2.新生代和老年代的内存大小是怎样的?

根据官方建议,新生代占三分之一(Eden:S0:S1=8:1:1),老年代占三分之二,因此内存分配图如下:

揭秘JAVA JVM内幕(不合适初学者)

 

3.GC回收是怎样进行的?

对象先在Eden区运行,当Eden内存用占用满时,Eden会进行两个操作:回收不用的对象和将未回收对象放入s0区,此时s0区和s1区互换名称,即s0->s1,s1->s0,Eden区经过一次对象回收后,释放了空间,当Eden下次再满时,执行相同步骤,依次循环执行,当Eden区回收后,剩下的对象超过s0容量,则将触发一次Minor GC,此时将未回收的对象放入老年区,依次循环执行,当Eden区触发Minor GC时,剩余的对象容量大于old区剩余容量时,则old区将触发一次Major GC,此时便会触发一次Full GC。需要注意的是,一般发生Major GC,基本都都会伴随一次Full GC回收,Full GC非常损耗性能,在JVM调优时,要注意。

下图我在生产环境截的一张GC图,监控工具VisualVM

揭秘JAVA JVM内幕(不合适初学者)

 

4.垃圾回收算法有哪些?

(1)标记-清除算法

该算法分为2个阶段,即标记阶段和清除阶段,首先标记所有要回收的对象,然后回收被标记的对象。该算法效率低,且容易产生内存碎片。

a.效率低:需要遍历两次内存,第一次标记,第二次回收被标记对象

b.由于是非连续内存片段,容易产生碎片,当对象过大时,容易发生Full GC

下图为标记-清除算法 回收前和回收后对比示意图

揭秘JAVA JVM内幕(不合适初学者)

 

(2)标记-复制算法

该算法解决了“标记-清除”算法效率低和大部分内存碎片问题,它将内存分为大小相等的两块,每次只使用其中一块,当其中一块需要回收时,只需将该块区域还存活的对象复制到另一块,然后再把该块内存一次性清理掉,循环往复。

下图为标记-复制算法回收前和回收收简要示意图

揭秘JAVA JVM内幕(不合适初学者)

 

然而,由于年轻代大部分对象驻留时间都非常短,98%的对象都很快被回收,存活的对象非常少,不需要按照内存1:1来划分,而是按照8:1:1来划分,

将2%存活的对象放在s0(from区)即可。

如下为按照Eden:s0:s1 =8:1:1 划分示意图

揭秘JAVA JVM内幕(不合适初学者)

 

(3)标记-整理算法

该算法分为两阶段,即标记和整理,首先标记所有存活对象,将这些对象向一端移动,然后直接清理掉端边界以外的内存。由于老年代的对象存活时间比较长,因此适合用该算法。

标记过程仍与“标记-清除”过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象向一端移动,然后直接清理掉端边界以外的内存。

如下为"标记-整理算法"回收期和回收后示意图

揭秘JAVA JVM内幕(不合适初学者)

 

(4)分代收集算法

该算法为目前jvm算法,采用分代思想,模型如下:

揭秘JAVA JVM内幕(不合适初学者)

 

5.常见GC回收器有哪些?

(1)SerialGC

SerialGC又叫串行回收器,也是最基础的GC回收器,主要适用于单核cpu,新生代采用复制算法, 老年代采用标记-压缩算法,在运行的过程中需要暂停应用程序,

因此会造成STW问题,在JVM标注参数为:-XX:+UseSerialGC 。

(2)ParallelGC

ParallelGC基于SerialGC,主要解决SerialGC串行问题,改为并行问题,解决多线程问题,但同样会产生STW问题,jvm关键参数:

a.-XX:+UseParNewGC,表示新生代并行(复制算法) 老年代串行(标记-压缩)

b.XX:+UseParallelOldGC,老年代也是并行

(3)CMS GC

CMSGC属于老年代回收器,采用“标记-清除算法”,不会发生STW问题,在jvm中参数设置:

-XX:+UseConcMarkSweepGC,表示老年代使用CMS收集器

(4)Garbage First

Garbage First面向jvm垃圾收集器 ,它满足短时间停顿的同时达到一个高的吞吐量,适用于多核cpu和大内存的服务端,也是jdk9的默认垃圾回收器。

五 总结

深入分析了JVM内存模型,其中重点分析了jdk,jre和jvm关系,jvm类加载器,jvm堆内存划分,GC回收器和GC回收算法等,整体偏向于理论,由于篇幅有限,本篇文章未分析这些技术在JVM实际调优中是如何运用的,将在接下来的文章中与大家分享。



Tags:JAVA   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Tags: JAVA  点击:(5)  评论:(0)  加入收藏
文章目录1、Quartz1.1 引入依赖<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency>...【详细内容】
2021-12-22  Tags: JAVA  点击:(11)  评论:(0)  加入收藏
1 前言ObjectiveSQL 是一个Java ORM 框架,它不仅是Active Record 模式在Java 中的应用,同时还针对复杂SQL 编程提供近乎完美的解决方案,使得Java 代码与SQL 语句有机的结合,改变...【详细内容】
2021-12-13  Tags: JAVA  点击:(14)  评论:(0)  加入收藏
本系列为 Netty 学习笔记,本篇介绍总结Java NIO 网络编程。Netty 作为一个异步的、事件驱动的网络应用程序框架,也是基于NIO的客户、服务器端的编程框架。其对 Java NIO 底层...【详细内容】
2021-12-07  Tags: JAVA  点击:(17)  评论:(0)  加入收藏
流为什么动不动就说 io 流? 这个“流”是什么意思呢?流这个词,也常常出现在电竞选手的领域。大家都说,哦,这个队伍经常上去卖人头来取得局面优势的这种打法,叫献祭流。而到了 Java...【详细内容】
2021-11-15  Tags: JAVA  点击:(33)  评论:(0)  加入收藏
1. 字符串有整型的相互转换String a = String.valueOf(2); //integer to numeric stringint i = Integer.parseInt(a); //numeric string to an int 2. 向文件末尾添加内容B...【详细内容】
2021-10-13  Tags: JAVA  点击:(92)  评论:(0)  加入收藏
负载均衡是将客户端请求访问,通过提前约定好的规则转发给各个server。其中有好几个种经典的算法,下面我们用Java实现这几种算法。 轮询算法轮询算法按顺序把每个新的连接请求...【详细内容】
2021-09-27  Tags: JAVA  点击:(52)  评论:(0)  加入收藏
1 背景近日在给公司同事分享Arthas 工具使用时候,被它强悍的功能震撼到了就好奇研究了下它的原理及底层实现,其实它是通过Java agent 来实现的,也就深入地学习了一下Java agent...【详细内容】
2021-09-09  Tags: JAVA  点击:(67)  评论:(0)  加入收藏
近日浏览网上一些图片提取文字的网站,觉得甚是有趣,花费半日也做了个在线图片识别程序,完成了两个技术方案的选择,一是 tesseract + Python flask的方案实现,二是 tesseract + Sp...【详细内容】
2021-09-07  Tags: JAVA  点击:(81)  评论:(0)  加入收藏
Java String的判空方法是Java开发中的一个很基础的方法,下面列举了一些常用的方法。 方法一:效率高,也是最常用的方法。if(s == null || s.length() <= 0) 方法二:也是常看到的...【详细内容】
2021-09-03  Tags: JAVA  点击:(120)  评论:(0)  加入收藏
▌简易百科推荐
面向对象的特征之一封装 面向对象的特征之二继承 方法重写(override/overWrite) 方法的重载(overload)和重写(override)的区别: 面向对象特征之三:多态 Instanceof关键字...【详细内容】
2021-12-28  顶顶架构师    Tags:面向对象   点击:(2)  评论:(0)  加入收藏
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(11)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(13)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(11)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(11)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(17)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(19)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(21)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条