JVM的简单了解逐步深入

在我们了解JVM的前提先了解一下JDK和JRE和JVM它们是什么以及之间的关系

1.JDK

1). JDK是Java Development Kit的缩写,是Java的开发工具包。
2). JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。
3). JDK是整个Java开发的核心,包括了Java运行环境,Java工具和Java基础类库。
JDK包含的基本组件包括:
  • javac – 编译器,将源程序转成字节码
  • jar – 打包工具,将相关的类文件打包成一个文件
  • javadoc – 文档生成器,从源码注释中提取文档
  • jdb – debugger,查错工具
  • java – 运行编译后的java程序(.class后缀的)
  • appletviewer:小程序浏览器,一种执行HTML文件上的Java小程序的Java浏览器。
  • Javah:产生可以调用Java过程的C过程,或建立能被Java程序调用的C过程的头文件。
  • Javap:Java反汇编器,显示编译类文件中的可访问功能和数据,同时显示字节代码含义。
  • Jconsole: Java进行系统调试和监控的工具。

2.JRE

1). JRE是Java Runtime Environment的缩写。
2). JRE是可以在其上运行、测试和传输应用程序的Java平台。它包括Java虚拟机(jvm)、Java核心类库和支持文件。它不包含开发工具(JDK)–编译器、调试器和其它工具。

3.JVM

1). JVM是Java Virtual Machine的缩写,也就是java虚拟机的意思
2). 要知道java之所以能够跨平台是通过java编译器编译后的.class文件在JVM上运行的

在这里插入图片描述

3.1 类加载器ClassLoader

在这里插入图片描述

负责加载class文件,class文件在文件开头有特定的文件标示(是为了识别是否是jvm识别的class文件),将class文件字节码内容加载到内存中,并将这些内容转换成 方法区 中的运行时数据结构并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定

JVM自带的加载器有三种类加载器(当然用户可以自定义加载器,继承java.lang.ClassLoader),当一个 JVM启动的时候,Java开始使用如下三种类加载器

在这里插入图片描述

1)根类加载器(Bootstrap Class Loader): 它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class(java核心类),由C++实现,不是ClassLoader子类)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

2)扩展类加载器(Extened Class Loader):它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null。

3)系统类加载器(System Class Loader 也叫AppClassLoader):它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器,由Java语言实现,父类加载器为ExtClassLoader。

4) 三个类加载器加载顺序:

Bootstrap Class Loader会在JVM启动后产生加载Java 的核心类(jre/lib/rt.jar),接着Bootstrap Class Loader会产生Extended Class Loader会加载Java拓展类(jre/lib/ext/*.jar)并设定Bootstrap Class Loader为父加载器,然后Bootstrap Class Loader再产生System Class Loader并将Extened Class Loader为父加载器,接着System Class Loader会加载你的类

4.1) 下面我写个程序验证一下

	@Test
    public void test13(){
        Object object = new Object();
		/* 因为Object是Java核心类库中的类 一开始
		* Bootstrap Class Loader就会把它加载进来 
		* 然而Bootstrap Class Loader是C++编写的,
		* 由于引导类加载器涉及到虚拟机本地实现细节,
		* 开发者无法直接获取到启动类加载器的引用,
		* 所以不允许直接通过引用进行操作所以是null。
		*/
        System.out.println(object.getClass().getClassLoader()); 
        Student student = new Student(); //这是我自己编写的类
        System.out.println(student.getClass().getClassLoader());
        System.out.println(student.getClass().getClassLoader().getParent());
        System.out.println(student.getClass().getClassLoader().getParent().getParent());
    }

结果

在这里插入图片描述

5)类加载机制

5.1)双亲委派机制: 则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载,如果都找不到就会抛出java.lang.NoClassDefFoundError。

双亲委派机制的作用就是沙箱安全(就是保证java编程者编写的类不污染java类库中的类) 看个例子

public class String {

    public static void main(String[] args) {
		/* 会报错java.lang.String中找不到main方法 
		* 这下明白了吧 可能我们只知道自定义类不能定义
		* java中类库存在的,但只知其然不知其所以然,因
		* 为自定义的类的类加载器SystemClassLoader会依
		* 次递归找其父类加载器,就是BootstrapClassLoader,
		* 这个加载器他rt.jar里面java.lang包下就有String
		* 这个类他找到了并返回
		* /
        System.out.println("你好"); 
    }
    
}

5.2)缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

5.3)全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。

3.2 个区的介绍(需注意线程共享[存在GC]/独享区[不存在GC])

1)方法区: 放类的结构信息(模板) 如上图的car类

2)堆:实例变量存在堆内存中

3)Java栈: 8种基本类型的变量+对象的引用变量+实例方法都是函数的栈内存中分配。每个方法执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息

4)本地方法栈: native关键字修饰的方法(native关键字作用是 此方法是非java方法,是涉及底层的第三方库的方法比如C/c++语言实现的)

5)程序计数器(PC寄存器): 用来存储指向下一条指令的地址,也即将要执行的指令代码,它是当前线程所执行的字节码的行号指示器

4.三者的关系

在这里插入图片描述

JDK = JRE + JAVA开发工具(javac.exe,java.exe,javadoc.exe)

JRE = JVM + JAVASE API + JAVA Web Start + Applet/JAVA Plugin(过时) + JavaFx(过时)

5.JVM可视化工具

这个工具叫jconsole.exe在jdk文件夹下的bin目录
连接进去后可以看到

在这里插入图片描述
JVM区域总体分两类,heap区(堆内存)和非heap区(非堆内存)。

heap区(堆内存)又分为:
  • Eden Space(伊甸园)、 ----> 新生区
  • Survivor Space(幸存者区里面有两个去From和To)、 ----> 新生区
  • Old Gen(老年代)。 --> 老年区
非heap区又分:
  • Code Cache(代码缓存区);
  • Metaspace(元空间) 在java8中元空间(Metaspace)替换持久代(PermGen space) 持久待就是方法区(相当于一个接口)的一个实现(java7之前方法区相当一个标准/接口在java7他用持久区实现java8换元空间)
    在这里插入图片描述
  • Compressed Class Space(压缩类空间)

6. 各个区域讲解

6.1.

Eden Space:字面意思是伊甸园,对象被创建的时候首先放到这个区域,进行垃圾回收后,不能被回收的对象被放入到空的survivor区域。

6.2.

Survivor Space:幸存者区,用于保存在eden space内存区域中经过垃圾回收后没有被回收的对象。Survivor有两个,分别为To Survivor、 From Survivor,这个两个区域的空间大小是一样的。执行垃圾回收的时候Eden区域不能被回收的对象被放入到空的survivor(也就是To Survivor,同时Eden区域的内存会在垃圾回收的过程中全部释放),另一个survivor(即From Survivor)里不能被回收的对象也会被放入这个survivor(即To Survivor),然后To Survivor 和 From Survivor的标记会互换,始终保证一个survivor是空的。

在这里插入图片描述

Eden Space和Survivor Space都属于新生代,新生代中执行的垃圾回收被称之为Minor GC(Garbage Collection)(因为是对新生代进行垃圾回收,所以又被称为Young GC),每一次Young GC后留下来的对象age加1。

6.3.

Old Gen:老年代,用于存放新生代中经过多次垃圾回收仍然存活的对象,也有可能是新生代分配不了内存的大对象会直接进入老年代。经过多次垃圾回收都没有被回收的对象,这些对象的年代已经足够old了,就会放入到老年代。

6.4.

Code Cache:代码缓存区,它主要用于存放JIT所编译的代码。CodeCache代码缓冲区的大小在client模式下默认最大是32m,在server模式下默认是48m,这个值也是可以设置的,它所对应的JVM参数为ReservedCodeCacheSize 和 InitialCodeCacheSize,可以通过如下的方式来为Java程序设置。

6.5

Metaspace: 元空间,用于存放JDK自身所携带的Class,Interface的元数据,简单的说他主要存放java核心类库里面的类,他这里不存在GC,关闭GC才会释放此空间

7. GC(Garbage Collection)垃圾收集器

在这里插入图片描述

1.eden,Survivor From复制到 Survivor To 年龄+1
首先,当Edem区满的时候回触发第一次GC(Young GC),把还活着的对象拷贝到Survivor From区,当Eden区再次触发GC的时候会扫描到Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄已经达到了老年区的标准,则移动到老年区),同时把这些对象年龄+1
2.清空eden,Survivor From区
然后,清空eden和Survivor From区中的对象,复制之后有交换,谁空数就是To区
3.Survivor To和Survivor From互换
最后Survivor To和Survivor From互换,原Survivor To成为下一次GC时的Survivor From区,部分对象会在From和To区复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定这个参数默认是15)最后如果还是存活则进入老年区

8.JVM调优

我们深入学习JVM不仅是了解其内部工作原理,更重要想通过了解其内部工作原理去调整到自己期望的效果
在上面的学习我们知道 要想调优其实就是调节JVM的堆内存(heap区) 在进行调优学习之前
我们先总结一下之前的java7和java8之间JVM的堆内存的区别

在这里插入图片描述

在这里插入图片描述

可以看到区别在于java8把持久区变为元空间
元空间本质和持久区类似但最大的区别是 元空间并不在JVM虚拟机中而是使用本机物理内存,这样可以加载多少类的元数据就不在由MaxPermSize控制,而是由系统的实际可用空间来控制
我们看一下xms xmx所占大写是不是这么多 测试一下
		long maxMemory = Runtime.getRuntime().maxMemory();//返回java虚拟机视图使用的最大内存  Xmx
        long totalMemory = Runtime.getRuntime().totalMemory();//返回Java虚拟机中的内存总量
        System.out.println("-Xmx : "+ (maxMemory / (double)1024 / 1024) + "MB" );
        System.out.println("-Xms : "+ (totalMemory / (double)1024 / 1024) + "MB" );

在这里插入图片描述

在实际生成中建议是 把xms和xmx的值调成一致,避免GC和应用程序争抢内存,jvm运行内存忽高忽低
怎么取配置这两值呢?
8.1在idea里面的配置这两个值

在这里插入图片描述

在这里插入图片描述

可以看到 这再次验证那句话 堆内存逻辑上由新生区,老年区,元空间组成 物理上由新生区,老年区组成因为上图控制台结果新生区内存大小+老年区内存大写/1024转换成MB 大小就是 Xmx最大分配内存
-XX:+PrintGCDetails 打印GC详细信息
现在我们分析一下GC详细信息

在这里插入图片描述

9.垃圾回收机制算法

9.1.标记-清除算法
该算法先标记,后清除,将所有需要回收的算法进行标记,然后清除;这种算法的缺点是:效率比较低;标记清除后会出现大量不连续的内存碎片,这些碎片太多可能会使存储大对象会触发GC回收,造成内存浪费以及时间的消耗。
9.2.复制算法
复制算法将可用的内存分成两份,每次使用其中一块,当这块回收之后把未回收的复制到另一块内存中,然后把使用的清除。这种算法运行简单,解决了标记-清除算法的碎片问题,但是这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工作,效率比较低。
9.3.标记整理算法
标记整理算法是针对复制算法在对象存活率较高时持续复制导致效率较低的缺点进行改进的,该算法是在标记-清除算法基础上,不直接清理,而是使存活对象往一端游走,然后清除一端边界以外的内存,这样既可以避免不连续空间出现,还可以避免对象存活率较高时的持续复制。这种算法适合老生代。
9.4.分代收集算法
分代收集算法就是目前虚拟机使用的回收算法,它解决了标记整理不适用于老年代的问题,将内存分为各个年代,在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率高,没有额外空间对它进行分配担保,所以使用标记整理算法。





一键查询淘宝/拼多多内部优惠券,每日大额外卖红包,购物省钱的宝藏工具
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值