JVM虚拟机的整体结构
- 先看一张图
- class文件生成模块:jdk中的javac编译命令
- 类加载器子系统:将class字节码加载到内存空间中,核心就是ClassLoader,动态更新的核心。
- 内存空间包括:方法区()、Java堆、Java栈、本地方法栈。用来存储class字节码不同的部分。
- 垃圾收集器: (gc)
这四个模块是我们程序员经常打交道的,理解以上四个模块,就可以说对JVM有个深入的了解了。其他的模块:
指令计数器,执行引擎,本地方法接口等,都是JVM比较底层的与CPU打交道的接口了,不需要我们过多的了解。
JVM为我们提供的所有类加载器:
- BootStrapClassLoader:是用来加载JRE\lib\rt.jar或者-Xbootclasspath选项指定的jar包。
- ExtensionClassLoader:是用来加载JRE\lib\ext*.jar或-Djava.ext.dirs指定目录下的jar包。 它和上面的BootStrapClassLoader是用来加载JDK中特定的jar包的。
- AppClassLoader:这个是我们应用程序的ClassLoader。
- CustomClassLoader:自定义的ClassLoader。
Java代码的编译和执行过程
- classloader加载流程:
- Loading:从类文件中获取类的信息并且加载到JVM内存里,真正的把class字节码加载到内存中。
- Verifying:验证,检查读入的字节码结构是否符合JVM规范的描述。
- Preparing:分配一个数据结构来存储类信息。
- Resolving:把类的常量池的所有符号引用改变成直接引用。
- Initializing:执行静态初始化程序,把静态变量初始化成指定的值。
内存管理和垃圾回收
- java栈区:java内存管理中最重要的模块
- 作用:它存放的是java方法执时的所有数据。是描述java方法执行的完整的内存模型。
我们类中的方法,在执行的时候是被嵌套调用的,也就是方法a调用方法b,方法b在调用方法c,一直嵌套然后返回。那我们栈区是如何完整
的描述这个过程呢?这时候就引出了栈帧。把栈帧理解清楚了,就自然了解了java栈区。
- 组成:由栈帧组成,一个栈帧代表一个方法的执行。
- 栈帧:
- 作用:每个方法从调用到执行完成就对应一个栈帧在虚拟机栈中入栈到出栈的过程。
- 包含:局部变量表、栈操作数、动态链接、方法出口。存储了方法调用的所有内容。(Stack Overflow 异常)。
- 本地方法栈
- 作用:专门为native方法服务的。
- 也是由栈帧组成的。
-
方法区
1.作用:存储被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的数据,编译期需要保存的信息。 -
堆区
- 作用:所有通过new创建的对象的内存都在堆中分配。
- 特点:是虚拟机中最大的一块内存,是GC要回收的部分。
- 堆区对内存的分配:
- 垃圾收集算法:确定哪些数据属于垃圾
- 引用计数算法:最早使用的算法,1.2之前。对象通过引用的数量来确定是否能被GC回收。
缺陷:A引用B,B引用A,但是A,B都是不可达的。没有路径指向这两个对象。所以他们已经是垃圾了,但是在此算法的场景下,这两个对象并不能被JVM回收。
- 可达性算法:从GC Roots作为起点开始搜索,那么整个连通图中的对象便都是活对象,对于GC Roots无法到达的对象便成了垃圾回收的对象,随时可被GC回收。
ObjF,ObjD,ObjE都是垃圾。
- 引用的分类,带星号的是常用的引用。
- *强引用:
- 软引用:
- *弱引用:WeakReference
- 虚引用:
垃圾回收算法
标记-清楚算法:标记阶段和清除阶段构成。在标记阶段会把所有的活动对象都做上标记,然后在清除阶段会把没有标记的对象,也就是非活动对象回收。
- 优点
- 实现简单
- 与保守式 GC 算法兼容
- 缺点
- 碎片化严重(由上面描述的分配算法可知,容易产生大量小的分块
- 分配速度慢(由于空闲区块是用链表实现,分块可能都不连续,每次分配都需要遍历空闲链表,极端情况是需要遍历整个链表的。
- 与写时复制技术不兼容
复制算法:(Copying GC)是由Marvin L. Minsky在1963年研究出来的算法。原理是把内存分为两个空间一个是From空间,一个是To空间,对象一开始只在From空间分配,To空间是空闲的。GC时把存活的对象从From空间复制粘贴到To空间,之后把To空间变成新的From空间,原来的From空间变成To空间。
标记-整理算法:标记/整理算法与标记/清除算法非常相似,它也是分为两个阶段:标记和整理。下面LZ给各位介绍一下这两个阶段都做了什么。
标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。