一、体系结构体

image.png
灰色部分(java stack、Native Method Stack、Program counter register)线程私有,几乎不存在垃圾回收
黄色部分(Method area、head)存在垃圾回收
栈管运行、堆管存储
image.png

二、类装载器

1、简单描述

image.png
上图中,Car Class就在方法区,它就是类的结构信息
Class文件开头特定的文件标识,是 cafe babe,如下图
image.png

2、类装载器的种类

  • 启动类装载器(BootStrap)C++系统自带的类使用
  • 扩展类加载器(Extension)/(PlatformClassLoader)Java我们自己写的类使用
  • 应用程序类加载器(AppClassLoader),java也叫系统类加载器,加载当前应用的classpath的所有类
  • 用户自定义加载器,Java.lang.ClassLoader的子类,用户可以定制类的加载方式。

    3、装载器测试

    ```java public class Test01 { public static void main(String[] args) {
    1. Object o = new Object();
    2. // 查看Object是谁装载进来的
    3. System.out.println(o.getClass().getClassLoader());
    4. Person person = new Person();
    5. // 查看person是谁装载进来的
    6. System.out.println(person.getClass().getClassLoader());
    7. System.out.println(person.getClass().getClassLoader().getParent());
    } }

结果 null jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17 jdk.internal.loader.ClassLoaders$PlatformClassLoader@75bd9247

  1. 为什么o.getClass().getClassLoader()=null
  2. > 因为Object是最高父类,它是由BootStrapC++)装载进来的,java就显示null
  3. 为什么person.getClass().getClassLoader())不是null
  4. > 因为person不是最高父类,它是由AppClassLoader装载进来的
  5. 为什么person.getClass().getClassLoader().getParent()是PlatformClassLoader
  6. > PlatformClassLoader是拓展类加载器,因为Person不是系统自带的,使我们自己写的
  7. <a name="IMTtA"></a>
  8. ### 4、双亲委派
  9. 当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。<br />采用双亲委派的一个好处是比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个0bject对象。<br />当自己定义一个包,java.lang和系统自带的lang包冲突时,系统会优先加载系统自带的包,防止系统自己的代码被污染(即沙箱安全)。测试代码如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1460038/1636811262876-50d78ed6-6c35-42d1-8558-e79cc28f7175.png#clientId=u3cc3143d-e140-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=249&id=ube2598bc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=498&originWidth=1054&originalType=binary&ratio=1&rotation=0&showTitle=false&size=128375&status=done&style=none&taskId=u1f4b8e56-2143-4f4a-92b0-b3f246f4e6c&title=&width=527)
  10. <a name="liYQH"></a>
  11. # 三、Execution Engine
  12. 执行引擎负责解释命令,提交给操作系统执行。
  13. <a name="kJ3Yp"></a>
  14. # 四、本地方法接口
  15. 只要是被关键字native修饰的,都是调用的与java无关的、底层的操作系统库、C语言函数库
  16. ```java
  17. // Thread的start里面有个start0
  18. private native void start0();

native放在native method stack方法栈里面。

1、native interface 本地接口

本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序,Java诞生的时候是C/C++横行的时候,要想立足,必须有调用C/C++程序,于是就在内存中专门开辟了一块区域处理标记为native的代码,它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraries。
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用WebService等等,不多做介绍。

2、native method stack

它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。

五、程序计数器

Program Counter Register(程序计数器) Register(寄存器):cpu中的寄存器 存储的下一步要执行的程序的地址。

功能:程序的排班执行表,记录下一步要做什么事情,即这个代码从哪跳到哪?即from 地址1 to 地址2。

每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
这块内存区域很小,它是当前线程所执行的字节码的行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。
如果执行的是一个Native方法,那这个计数器是空的。
用以完成分支、循环、跳转、异常处理、线程恢复等基础功能。不会发生内存溢出(OutOfMemory=00M)错误。

六、方法区/元空间(metaspace)/永久代

线程共享,它存储每一个类的结构信息(例如:上面1、类装载器中的Car Class),例如运行时常量池(Runtime Constant Pool)、普通/静态常量和方法数据、构造函数和普通方法的字节码内容,即编译器编译后的代码,虚拟机加载的类信息等数据。上面讲的是规范虽然JVM规范将方法区描述为堆的一个逻辑部分,但是它却还有一个别名叫做Non-heap(非堆),目的就是要和堆分开。 实例变量存在堆内存中,和方法区无关。 对于HotSpot虚拟机 ,很多开发者习惯将方法区称之为“永久代(permanent gen)”,但是严格本质上说两者不同,或者说使用永久代来实现方法区而已,永久代是方法区(相当于是一个接口inteface)的一个实现,jdk1.7的版本中,已经将原本放在永久代的字符串、常量池移走了

image.png
jdk7是Permanent Space,jdk8变成了metaspace,由于现在

元空间/永久代 即 方法区,存储肯定会用的东西,例如rt.jar这个包就加载在这永久代里面,虚拟机加载的类信息,常量池,静态常量,即使编译后的代码。

jdk7及之前版本的叫法,永久存储区是一个常驻内存区域,用于存放JDK自身携带的Class,被装载进此区域的数据是不会被垃圾回收器回收的,关闭JVM才会释放此区域所占用的内存。
元空间和永久代最大的区别在于:

  • 永久带使用的是JVM的堆内存,而元空间使用的的本机的物理内存,因此默认情况下,元空间的大小仅受本地内存限制。
  • 在元空间中:类的信息放入native momery字符串,常量池和类的静态常量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。

一般默认元空间只占用本机物理内存的1/4。
总之:
方法区(永久代):存放的是类信息、字符串、常量池
元空间(metaspace):存放类信息(在机器内存中)、字符串和常量池(在堆中)

七、栈

栈(stack):虚拟机栈。用于存储局部变量表。局部变量表存放了编译期可以知道长度的各种基本数据类型(boolean、byte、short、char、int、float、long、double)、对象引用(reference类型,存储对象在堆内存的首地址)。方法执行完自动释放。 管运行,不存在垃圾回收

1、作用

java中的方法(例如main方法)== 虚拟机中的 栈帧
栈帧中主要保存三种数据

  • 本地变量(Local Variables):输入参数和输出参数以及方法内的变量
  • 栈操作(Operand Stack):记录出栈、入栈的操作
  • 栈帧数据(Frame Data):包括类文件、方法等等。

image.png

2、运行原理

image.png

3、栈溢出

  1. public class Test02 {
  2. public static void main(String[] args) {
  3. System.out.println("sssss");
  4. fun1();
  5. System.out.println("uuuuuu");
  6. }
  7. public static void fun1(){
  8. fun1();
  9. }
  10. }
  11. // Exception in thread "main" java.lang.StackOverflowError

以上代码递归调用自己,最终导致栈溢出,这是error级别错误,不是Exception。

八、堆

管存储,存储对象、实例,和数组 一个JVM实例只有一个堆内存,堆内存的大小是可调节的 逻辑上分三个部分:新生、养老、方法区(jdk8之后,方法区变成了metaspace,放到了内存中) 物理上分两个部分:新生、养老

什么是实例,什么是对象 参考,https://www.yuque.com/wangchao-volk4/fdw9ek/kwveif#BIIwr

例如Demo02 obj1 = new Demo02(),obj1在栈里面,new Demo02()在堆里面
image.png

1、新生代

伊甸园区(Eden Space)

不停的new。。。满了之后,会产生GC,这个GC发生在Eden Space,可以叫做GC=Young GC=轻GC=Minor GC之后,Eden 基本全部清空,剩下没有被清除的会移动到Survivor 0 Space

幸存者0区(Survivor 0 Space)

承接Eden Space的幸存者,如果0区满了,再进行垃圾回收,剩下的移到1区

幸存者1区(Survivor 1 Space)

承接0区过来的幸存者,满了就垃圾回收。
当一个类被回收过15次(该次数可以通过参数配置-XX:MaxTenuringThreshold=15)之后,进入老年代

注意:

Survivor 0 Space=S0=from
Survivor =1 Space=S1=to
from区和to区,他们的位置和名分不是固定的,每次GC后会交换,GC之后交换,谁空谁是to。

2、老年代((old)Tenure Generation Space)

满了之后会产生 Full GC = FGC=MajorGC,进行老年代的垃圾清理。
Full GC多次后发现养老区空间没办法腾出来,就会报错(OOM,java heap space堆内存溢出)

彩蛋:堆内存溢出,说明jvm的堆内存不够了,原因有二: 1、堆内存设置太小,java的堆内存可以通过参数-Xms 和 -Xmx来调整 2、代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)

3、GC流程简介

image.png
GC 详细介绍请看
https://www.yuque.com/wangchao-volk4/fdw9ek/kt1gmz