参考链接:
- JVM 类加载机制
- 双亲委派模式的优点
- JVM 运行时数据区
- Java对象的内存布局
- Minor GC(Young GC)、Full GC、Major GC、Old GC
- JVM面试题一
- JVM面试题二
- 垃圾收集原理和垃圾收集器
- JVM面试题三
一、类加载过程
- 其中
验证、准备、解析
3个部分统称为连接
。
加载: 寻找class的字节流文件,并导入,内存中生成 java.lang.Class 对象
- 通过一个
类的全限定名
来获取定义此类的二进制字节流
。 - 将这个字节流所代表的
静态存储结构
转化为方法区的运行时数据结构
。 - 在
内存中生成一个代表这个类的java.lang.Class 对象
,作为方法区这个类的各种数据的访问入口。
验证:验证class字节流文件的正确性
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
准备:为static变量分配内存、设置零值
解析:常量池内的符号引用–>直接引用
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这7类符号引用进行。
初始化:static变量、static代码块 初始化
二、类加载器类别
启动类加载器
- 加载
<JAVA_HOME>\lib
目录中且虚拟机识别的类库 - 或被
-Xbootclasspath参数所指定的路径
中的、且虚拟机识别的类库
扩展类加载器
- 由
sun.misc.Launcher$ExtClassLoader
实现 - 负责加载
<JAVA_HOME>\lib\ext
目录中的,或者被java.ext.dirs系统变量所指定的路径
中的所有类库
应用程序类加载器(系统类加载器)
- 由
sun.misc.Launcher$AppClassLoader
实现 - 负责加载
用户类路径(ClassPath)上所指定的类库
三、双亲委派模型
什么是双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委派给父类加载器
去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器
中,只有当父加载器反馈无法完成
这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才尝试自己去加载
。
双亲委派模式的优点
避免重复加载
- Java 类随着它的类加载器一起具备了一种
带有优先级的层次关系
,可以避免类的重复加载。
- Java 类随着它的类加载器一起具备了一种
避免核心库篡改
- 发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
哪些场景破坏了双亲委派模型
-
线程上下文类加载器
,典型的:JDBC 使用线程上下文类加载器加载 Driver 实现类 -
Tomcat 的多 Web 应用程序
-
OSGI 实现模块化热部署
为什么破坏双亲委派模型
以Tomcat 为例
Tomcat 容器可以同时部署多个 Web 应用程序,多个 Web 应用程序很容易存在依赖同一个 jar 包,但是版本不一样的情况。例如应用1和应用2都依赖了 spring ,应用1使用的 3.2.* 版本,而应用2使用的是 4.3.* 版本。
如果遵循双亲委派模型,很容易出现兼容性问题。
因此,Tomcat 只能选择破坏双亲委派模型。
如何破坏双亲委派模型
-
继承 ClassLoader,重写 loadClass 方法,实现自己的逻辑,不要每次都先委托给父类加载
-
上下文类加载器
四、内存模型(运行时数据区)
程序计数器
-
线程私有,
当前线程所执行的字节码的行号指示器
-
Java 方法(不是 native 的),正在执行的 Java 虚拟机字节码指令的地址。
-
本地(native)方法,空(undefined)。
Java虚拟机栈
- 存储栈帧:
局部变量表、操作数栈、动态链接、方法出口
等信息
本地方法栈
- 与 Java 虚拟机栈作用相似,为Native方法服务
堆
- 线程共享,存储**
类实例对象
**
方法区
- 线程共享,存储**
类加载信息、常量、static变量
**
堆、栈区别
区别 | 堆 | 栈 |
---|---|---|
物理地址 | 不连续 | 连续 |
内存 | 运行期分配 | 编译期分配 |
存放内容 | 对象实例 | 局部变量、操作数栈、方法出口等信息 |
Java对象的内存布局
对象头
:包括堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本信息。mark word
存储运行时数据klass pointer
类型指针 通过该指针来确定对象是哪个类的实例。
实例数据
:存放类的数据信息,父类的信息,对象字段属性信息对齐填充
五、常量池类型
class 文件常量池
- 存放编译期生成的各种
字面量、符号引用
- 字面量:如文本字符串、声明为 final 的常量值
- 符号引用:以一组符号来描述所引用的目标
运行时常量池
- 运行时常量池是
class 文件中每一个类或接口的常量池表(constant_pool table)的运行时表示形式
。
字符串常量池(StringTable)
哈希表
,全局唯一、共享
运行时常量池、字符串常量池的关联
- 在字符串解析时会有关联
- CONSTANT_String_info类型的常量,经过解析(resolve)之后,同样存的是字符串的引用,并且和 StringTable 驻留的引用的是一致的。
六、四种引用
强引用
只要强引用还存在,就永远不会回收。
软引用
- 描述一些还有用但并非必需的对象
- 使用SoftReference类来实现软引用
- 在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
弱引用
- 描述非必需对象的
- 使用WeakReference类来实现弱引用
- 被弱引用关联的对象只能生存到下一次垃圾收集发生之前
虚引用
- 使用PhantomReference类来实现虚引用
- 无法通过虚引用来取得一个对象实例
- 虚引用对象被收集器回收时收到一个系统通知
七、垃圾回收
7.1 垃圾判断方法
引用计数法
可达性分析算法:对象到GC Roots不可达时回收
7.2 垃圾收集算法
标记 - 清除算法
- 标记、清除效率都不高
- 产生大量不连续的内存碎片
复制算法
- 内存缩小为原来的一半
标记 - 整理算法
- 存活的对象向一端移动,然后直接清理掉端边界以外的内存。
7.3 Eden、From、To区比例
8:1:1
-XX:SurvivorRatio=8
7.4 MinGC / Young GC
- Eden满时触发
- Eden -> s0
复制
算法 - s0 -> s1
复制
算法,s0、s1之间复制15次的对象放入老年区
7.5 FullGC
- 触发机制
- 调用System.gc,但不必然执行
- 老年代满
- 方法区满
- MinGC后进入老年代的平均大小大于老年代的可用内存
- Eden、s0、s1复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
7.6 新生代 --> 老年代
- YoungGC对象太大放不进Survivor区
- s0、s1之间复制15次的对象
- Survivor中
相同年龄所有对象大小的总和大于Survivor空间的一半
,年龄大于或等于该年龄的对象就可以直接进入老年代
7.7 Full GC后老年代空间变小?
- HotSpot的Full GC实现中,默认
新生代里所有活的对象都要晋升到老年代
,实在晋升不了才会留在新生代。
7.8 GC Root有哪些
- 虚拟机栈(栈帧中的
本地变量表
)中引用的对象。 - 方法区中
类静态属性引用的对象
。 - 方法区中
常量引用的对象
。 - 本地方法栈中
JNI
(即一般说的Native方法)引用的对象。
垃圾收集器
CMS
- 缺点:
对CPU资源非常敏感
无法处理浮动垃圾
内存碎片化
G1
- 特点
- 并行与并发
- 分代收集
- 空间整合
可预测的停顿
- 跟踪各个Region里面的垃圾堆积的价值大小
- 在后台维护一个优先列表
- 每次根据允许的收集时间,优先回收价值最大的Region
CMS、G1区别
区别 | CMS | G1 |
---|---|---|
区域 | 老年代 | 新生代、老年代 |
回收算法 | 标记-清除(内存碎片化 ) | 标记-整理 |
停顿时间 | 最短停顿时间 | 可建立可预测的停顿时间模型 |
回收步骤 | 1. 初始标记 2. 并发标记 3. 重新标记 4. 并发清理 | 1. 初始标记 2. 并发标记 3. 最终标记 4. 筛选回收 |