JVM学习总结
JVM
本文主要记录学习JVM的一些学习笔记,讲师是bilibili up主:狂神说Java
参考书:《深入理解Java虚拟机》
一些 JVM相关面试题
- 请你谈谈你对JVM的理解?Java8虚拟机和之前的变化更新?
- 什么是OOM,什么是栈溢出StackOverFlowError?怎么分析?
- JVM的常用调优参数有哪些?
- 内存快照如何抓取,怎么分析Dump文件?知道吗?
- 谈谈JVM中,类加载器你的认识?
JVM的位置
JVM的体系结构
Java内存模型
线程私有
栈(Java虚拟机栈)
先进后出,后进先出
栈内存,主管程序的运行,生命周期和线程同步;线程结束,栈内存就立即释放,对于栈来说,不存在垃圾回收问题
基本类型对象(byte、short、int、long、float、double、boolean、byte)
局部变量表:存放局部变量–堆内存中对象的地址
操作数栈
动态链接:比如调用其他方法,就用动态链接将相应方法压入栈
方法出口
本地方法栈
存放native方法
native
凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层C语言的库,会进入本地方法栈,调用本地方法接口(JNI,Java Native Interface,作用:扩展Java的使用,融合不同的编程语言为Java所用!最初:C、C++),Java诞生的时候,C、C++横行,想要立足,必须要有调用C、C++的程序,它在内存中专门开辟了一块标记区域:Native Method Stack(本地方法栈),登记native方法 ,在最终执行的时候,通过JNI加载本地方法库中的方法
PC寄存器(程序计数器)
可以理解为jvm执行到当前字节码的行号, 比如cpu被抢占,jvm需要记录位置来继续执行
唯一不会出现内存泄漏的区域
线程共有
堆
Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把类、方法、常量、变量等所有引用类型的真实对象保存到堆中
唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存
新生区(年轻代、伊甸园区)
Edan Space(伊甸园区)
new出来的对象放在edan,通过minor gc,方法是gc root,查找对象引用的根来查找 没有被引用的对象。占8/10
from survivor(幸存0区)
占1/10
to survivor(幸存1区)
占1/10
为什么要有两个survivor,避免内存碎片化, survivor回收后的内存空间不连续,可能无法存放大对象。
养老区(老年代)
大概占堆内存2/3,占满会引发full gc
永久区(永久代)
jdk1.8被删除,jdk1.8以后更改为元空间
这个区域是常驻内存的。用来存放Jdk自身携带的Class对象,Interface元数据,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收,关闭VM虚拟机就会释放这个区域的内存。
- 在 jdk1.6(含)之前也是方法区的一部分,并且其中存放的是字符串的实例;
- 在 jdk1.7(含)之后是在堆内存之中,存储的是字符串对象的引用,字符串实例是在堆中;
- jdk1.8 已移除永久代,字符串常量池是在本地内存当中,存储的也只是引用。
方法区(元空间)
元空间的本质和永久代类似,都是对JVM规范中方法区的实现 并不在虚拟机中,而是在本地内存
启动时创建,所有线程共享
可以选择不实现垃圾收集
新版本中是存储在堆内存中的
常量,静态变量引用,类信息,方法代码,常量和静态变量也是存的堆内存中对象地址
运行时常量池,存放编译期生成的各种字面量和符号引用
类加载器
作用:加载Class文件
分类
BootstrapClassLoader(启动类加载器)
是JVM自带的类加载器,负责Java核心库,用来装载核心类库。该加载器无法直接获取。 rt.jar
c++
编写,加载java
核心库 java.*
,构造ExtClassLoader
和AppClassLoader
。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
ExtClassLoader (标准扩展类加载器)
负责jre/lib/ext
目录下的jar包或者-D java.ext.dirs
指定目录下的jar包装入工作库
java
编写,加载扩展库,如classpath
中的jre
,javax.*
或者java.ext.dir
指定位置中的类,开发者可以直接使用标准扩展类加载器。
AppClassLoader(系统类加载器)
java
编写,加载程序所在的目录,如user.dir
所在的位置的`class
负责java -classpath 或 -D java.class.path所指的目录下的类与jar包装入工作,是最常用的类加载器
CustomClassLoader(用户自定义类加载器)
java
编写,用户自定义的类加载器,可加载指定路径的class
文件
双亲委派机制
当某个类加载器需要加载某个.class
文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
流程
作用
- 防止重复加载同一个
.class
。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。 - 保证核心
.class
不能被篡改。通过委托方式,不会去篡改核心.class
,即使篡改也不会去加载,即使加载也不会是同一个.class
对象了。不同的加载器加载同一个.class
也不是同一个Class
对象。这样保证了Class
执行安全。
沙箱安全机制(了解)
JVM调优
目的
目的是减少STW(stop the world)时间, 所有GC都会引发STW,暂停用户进程
常用参数
参数名称 | 含义 | ** 默认值** | ** 备注** |
---|---|---|---|
-Xms | 初始堆大小 | 物理内存的1/64(<1GB) | 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制. |
-Xmx | 最大堆大小 | 物理内存的1/4(<1GB) | 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制 |
-Xmn | 年轻代大小(1.4or lator) | ** 注意**:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小. 增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8 | |
-XX:NewSize | 设置年轻代大小(for 1.3/1.4) | ||
-XX:MaxNewSize | 年轻代最大值(for 1.3/1.4) | ||
-XX:PermSize | 设置持久代(perm gen)初始值 | 物理内存的1/64 | |
-XX:MaxPermSize | 设置持久代最大值 | 物理内存的1/4 | |
-XX:+PrintGCDetails | 输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs] |
||
-XX:+HeapDumpOnOutOfMemoryError | 生成OOM错误的dump文件 |
GC(垃圾回收)
作用区域
JVM在进行GC时,并不是对着三个区域统一回收,大部分时候,回收都是新生代
- 新生代
- 幸存区(from survivor、to survivor)
- 老年区
GC两种类型:轻GC(普通的GC),重GC(全局GC)
GC常见题目
- JVM的内存模型和分区,详细到每个区防渗膜?
- 堆里面的分区有哪些?Eden、From survivor、To survivor、老年区,说说他们的特点
- GC的算法有哪些?标记清除法、标记压缩、赋值算法、引用计数器、怎么用的?
- 轻GC和重GC分别在什么时候发生?
基本机制
分代收集,所有GC都会引发STW
垃圾搜索算法
引用计数法
对象中存在一个引用计数器,一旦该对象被引用则计数器加1, 一旦对象应用被释放,则计数器减1。因为这种算法无法解决相互引用的问题,所有虚拟机并没有使用这个垃圾搜索算法。
可达性分析算法
遍历所有的GC Roots,从GC root开始可达的为存活对象,不可达的为待gc对象。
GC Roots的条件:
- 栈桢的局部变量表所引用的对象
- 方法区的静态变量和常量所引用的对象
- 本地方法栈的JNI所引用的对象
垃圾清除算法
复制算法
主要用于年轻代
内存划分为空闲区域和活动区域,把可达对象复制到空闲区域, 空闲区域变为活动区域,清空之前的活动区域,变为空闲区域
好处:没有内存碎片
坏处:浪费内存空间,有一半空间永远是空to survivor
复制算法最佳使用场景:对象存活度较低的时候(主要是在新生区的时候)
标记-清除算法
从GCROOT开始,如果是可达对象标记为存活对象,然后对不可达对象进行清除
优点:不需要额外的空间
缺点:两次扫描,浪费时间,会产生内存碎片
标记-压缩算法
对标记-清除算法进行优化,防止内存碎片产生
总结
内存效率:复制算法>标记-清除算法>标记-压缩算法(时间复杂度)
内存整齐度:复制算法=标记-压缩算法>标记-清除算法
内存利用率:标记-压缩算法=标记-清除算法>复制算法
思考:有没有最优的算法?
没有。没有最好的算法,只有最合适的算法------>GC:分呆收集算法。
年轻代:
- 存活率低
- 复制算法
老年代:
- 区域大
- 存活率高
- 标记-清除和标记-压缩混合实现