(一)JVM的组成

一、JVM介绍
(1)JVM的作用
我们知道,Java代码要想在计算机中正常运行,就需要经过编译为class二进制字节码文件,而JVM就提供了class二进制字节码的运行环境。
- 一次编写,到处运行
因为JVM是运行在操作系统当中的,通过屏蔽掉不同操作系统之间的差异 ,来实现自己作为跨平台语言的这种特性。因为无论在任何操作系统当中真正运行代码的不是这些操作系统,而是JVM,所以才能做到一次编写,到处运行。 - 自动内存管理,垃圾回收机制
与C语言相比,C语言需要程序员自行管理内存,如果编码不当很容易出现内存泄漏的现象。而Java虚拟机自带的垃圾回收机制就大大减轻了程序员的负担,减少出错机会。
(2)JVM的组成部分与运行流程

- Java Source
指的是我们自己编写的Java代码,也就是.java文件 - Java Class
将java文件编译为class文件 - 类加载子系统
用于将java代码转换为字节码 - 运行数据区
主要作用是把字节码加载到内存当中,因为程序必须要加载到内存才能运行。
具体其他模块的功能将在后面进行细致介绍。 - 执行引擎
用于把字节码翻译为底层系统指令。
解释器:用于解释字节码信息:即使编辑器:会针对你的代码进行优化;GC垃圾回收:主要指的是运行数据区中的堆空间,后期将重点去讲解该垃圾回收机制。 - 本地方法接口与本地库
由C/C++来实现,因为Java代码有时候并不能完全实现某些功能,只需要去记住系统提供的接口,再去进行调用即可。
(3)学习什么
二、程序计数器

线程私有的也就没有线程安全问题,因为每一个线程内部都有这样一个程序计数器。
在Java程序的class字节码文件中详细说明了代码的执行过程。
要想查看class字节码的信息就可以通过javap命令来查看字节码的反汇编信息:
这里面详细记录了main方法的执行过程,并且可以发现原来一行的sout打印代码被拆成了多行来执行。左侧代码的执行地址(0,3,5,8)可以理解为代码的执行行号。
- getstatic代表获取一个静态的变量,而System命令的out属性其实就是一个静态变量,类型是PrintStream。
- ldc加载常量,这里指的是String类型的字符串(hello world)。
- invokevirtual表面将要去调用的方法(println)。
- return指的是结束该方法。
假设现在在多线程环境下,要去执行当前代码,程序计数器就会给每一个线程去记录该行号。并且假设此时存在两条线程互相争夺执行权,线程1执行到第9行代码时被线程2夺走执行权,那么等线程2执行完毕或是线程1夺回执行权后,就会从先前执行到的第9行代码继续执行。
总结:

三、堆的详细介绍
(1)堆的介绍

1.线程共享区域肯定就会存在线程安全问题。
2.堆的划分
堆中又划分为了两个主要部分:年轻代与老年代;年轻代划分的三种类型Eden、S0、S1也存在一定区别,而S0、S1也被称为Survivor幸存者区。
通常情况如下:一个对象进来后先到Eden区,假如该对象在垃圾回收后依然存活,它就会被复制移动到S0或S1,且在挪动达到一定次数后也仍然存活,就会被放到老年代当中,老年代主要指的是生命周期比较长的对象。
而对象的挪动规则将会在后期讲解垃圾回收机制时会再详细说明。
3.元空间的作用
用于保存类的信息、静态变量、常量、编译后的代码。
(2)Java1.7与1.8的堆的区别是什么
在Java1.8之前,堆的区域当中还存在一个永久代,它的作用与元空间一样。
到了Java1.8之后,它把方法区/永久代放到了本地内存当中,也就是元空间中。为什么要放到本地内存:因为元空间与方法区主要存储的是一些类或常量,而随着项目运行时加载的类越来越多,这块方法区就会变得不可控 ,容易出现内存溢出或浪费内存 ,所以到了Java1.8优化过后就把他们都放到了本地内存,能够让堆来节省空间 ,最终目的都是避免OOM(内存溢出)。
(3)总结

四、虚拟机栈
(1)什么是虚拟机栈
因为虚拟机栈是每个线程在运行时所需要的内存,多个线程运行时就会创建多个虚拟机栈,所以栈内存也是线程安全的 。
面试题:
- 垃圾回收是否涉及栈内存
栈帧弹栈后释放内存的过程并不需垃圾回收器
(2)栈内存溢出情况

(3)总结
五、方法区
(1)方法区概述

元空间当中的Class用于存储类的信息,包括类的结构、方法、字段等;Classloader就是类加载器;运行时常量池将会在后面详细介绍。
通过Java代码来模拟本地内存溢出的情况:
因为元空间的默认大小是没有上限的,要想模拟内存溢出的情况首先就需要设置元空间大小上限为8m
执行该段代码,发现控制台中会提示元空间大小过小
(2)常量池

(3)运行时常量池

(4)总结

六、直接内存
(1)直接内存介绍

既然是操作系统的内存,那么是可以使用java程序来使用它的。
我们平时的IO是BIO,此处的NIO要比BIO的吞吐量高很多。
(2)常规IO数据拷贝流程

Java本身并不具备磁盘读写的能力,要想进行磁盘读写就必须调用操作系统所提供的函数,也就是本地的native方法。
常规IO的问题:在内存当中有两块缓冲区,这样读取数据时就必须要去读取、存储两份。因为java代码本身是访问不到系统缓冲区的,必须要把数据读到java缓冲区后才能使用java代码进行操作。这样就造成了一个不必要的数据复制,因此效率就比较低。
(3)NIO数据拷贝流程

这里出现的直接内存就相当于在操作系统中划出了一块缓冲区,可供系统与java代码共同访问,也就是共享的内存区域。这样java代码操作起来就非常方便了,提高运行效率,比较适合文件的IO操作。
(4)总结
