1、JVM的重要组成部分
1、类加载器
Java转换字节码
2、运行时数据区
把字节码加载到内存中
3、执行引擎
将字节码转换成为操作系统指令,交由CPU执行
4、本地库接口
执行过程可能调用其他语言的本地库接口
2、类的加载过程
1、加载(根据路径加载文件)
2、检查(检查文件是否正确)
3、准备
准备给类的静态变量在堆内分配内存空间,静态变量只初始化一次
4、**解析:**将常量池中的符号引用替换成直接引用,符号引用一个标识,直接引用可以理解为直接指向内存中的地址,常量池在元空间
5、初始化对静态变量和静态代码块进行初始化
3、双亲委派机制删除线格式
双亲委派机制就是,加载一个类,会现获取到类的系统类加载器,层层网上,先是应用类加载器(Application ClassLoader),然后拓展类加载器(Extension ClassLoader),最后还是启动类加载器(Bootstrap ClassLoader),会有系统类加载,从上面的启动类开始加载,如果没有就层层往下,如果都没有就报错
4、运行时数据区
-
JVM虚拟机栈
存储局部操作数栈,方法出口等,为每个被执行的方法创建一个栈帧,线程私有这点跟堆是不同的。
-
java堆
内存最大的一块,多有new的对象都在这里分配内存,被所有线程共享
-
程序计数器
保存字节码行号。
-
方法区
存储类信息,常量,静态变量,即编译的代码数据。
-
本地方法栈
服务虚拟机调用本地方法,
5、垃圾回收机制
1、什么是垃圾回收?
垃圾回收就是,回收那些死亡对象所占据的堆空间
2、如何判断一个对象是垃圾
**引述计数法:**需要额外的空间存储数据,如果一个引用指向某一个对象,则改该对象的引用计数器+1,如果该引用指向另一个对象,则原先的对象计数器+1
但这种算法,回存在循环引用的bug问题,存在内存溢出的风险 。
可达性分析法是以GC Root作为起点,能够引用到的对象则是有用对象,反之,则是死亡的,
什么是GC Root,一般理解为对外指向堆内的引用,包裹以下常见的两种,
1、java方法栈帧中的局部变量
2、已被加载的类静态变量
(1)虚拟机(JVM)栈中引用对象
(2)方法区中的类静态属性引用对象
(3)方法区中常量引用的对象(final 的常量值)
(4)本地方法栈JNI的引用对象
2、垃圾回收算法**
-
标记清除算法
是现在垃圾算法的思想基础,它将垃圾回收分为两个阶段:
标记阶段和清除阶段。
首先是通过根节点GC Root,标记所有从根节点开始的可达对象
因此未被标记的对象都是垃圾对象
然后在清除阶段,则删除所有未被标记的对象
标记清楚算法的缺点:
- 效率不高
- 该算法会产生不连续的内存碎片,当我们需要分配较大对象时 ,因为无法找到足够的连续内存空间,而不得不再次提前出发垃圾回收,如果内存还是不够,则报内存不足异常。
标记压缩算法**
标记压缩算法是老年代的一种回收算法,
首先,标记阶段跟"标记清楚算法"一致
区别在于清理阶段,为了避免内存碎片产生,所有的对象会被压缩到内存的一端
这个算法解决之前标记清除算法的碎片问题
但是标记和压缩的效率依然不高
3、复制算法**
复制算法是为了解决效率问题,他们将内存一分为二,每次只是用其中一块
这样,当一块内容用完了,就将内存的对象复制到另一块内存一次清理掉,这样回收的效率提高,也不存在内存碎片的的问题。
算法优点是回收效率高,不存在内存碎片,但是浪费一半的内存空间,另外在对象存货效率高的情况下, 采用复制算法,效率将会降低。
4、分代收集法
目前,主流的虚拟机大都采用分代收集算法,它根据对象存货周期不同,而将内存划分多块区域,一般是我们耳熟能详的新生代和老生带,然后采用不同的回收算法。
新年代(Eden),对象的存活率低,所以采用复制算法。
老年代(Old),对象的存活率高,所以采用标记清除算法或标记整理算法。
对象优先分配到新生代,如果长期存活或者对象过大会直接分配到老年代(新生)
算法细节:
-
对象新建,将存放在新生代的Eden区域,注意Suvivor(幸存者)又分为两个区域,FromSuv和ToSuv,分配为8:1:1
-
当年轻的Eden满时,将会触发MinorGC ,如果对象依然存活,对象将会被转移到FromSuvivor空间,对象还是在新生代
-
再次发生 minor GC,对象还存活,那么将会采用复制的算法,将对象转移到ToSuvivor,此时对象的年龄+1
-
再次发生minor GC,对象依然存活,此时Survivor中跟对象Object同龄的对象还没到达Survivor区一半,所以还是会采用复制算法,将fromSuv和ToSuv的区域进行交换
-
当防身多次Minor GC,对象Object仍然存活,且此时,此时Suvivor中跟对象OBject同龄的对象达到Survivor的一半,name对象Object将会移动到老年带区域,或者对像经过多次回收,年龄达到了15岁,那么也会迁移到老年代
元空间
JDK1.8之前叫永久代,和老年区捆绑在一块,无论谁满了,都会触发永久代和老年代的垃圾回收。存放类信息、常量、静态变量,即编译器编译后的代码。
元空间中,每个加载器都有单独的存储空间,类和其元数据的生命周期与其对应的类加载器相同,省略掉了GC扫描及压缩的的时间,当GC发现某个类加载器不再存活了,会把对应的空间整个回收。
5**、JVM配置的相关参数**
- Xms2g;初始化堆大小为2g
- Xmx4g:堆内存大小为4g
- XX:NewRatio=4 :设置年轻的和老年代的内存比例为1:4;
- XX:SurvivorRatio=8:设置新生代Eden和Survivor比例为8:2;
6、垃圾回收器有哪些?
垃圾回收的时候,都有一个统一的的特点,叫stop the
world
往回收率越来越高的方向来走的,垃圾回收的时间(stop the
wrold)时间在变短
-
单线程回收器
采用多线程回收,效率一般,服务器是多核CPU资源无法得到更好的利用
-
多线程回收器
可以充分利用CPU资源
-
CMS回收器
3.1初始化标记
GCRoot
public class Gc{ private static SomeObject = new SomeObject(); } class SomeObject{ }
个时候会stop the world,但是由于我们只是标记GCRoot,所以花费的时间很短
3.2、并发标记
一遍可以继续往下跟踪,做可达性分析,相比比较耗时100
一遍可以让程序继续运行,可能重新创建对象,叶铿能制造垃圾20
3.3、重新标记
出处理在并发标记过程中,再次产生的垃圾, stop the world 20
3.4并发回收
一遍针对为我们刚才的垃圾对象进行回收
一遍程序继续运行
-
G1垃圾回收器
将内存划分多个块 ,每个块再独立进行回收
6、堆和栈
堆(heap) | 栈(stack) |
---|---|
存储是随意的 | 后进先出 |
程序员分配释放空间 | 操作系统释放空间 |
动态申请空间大 | 空间小 |
保存方法中的参数值,局部变量 | 存放new出来的具体实例 |
对所有线程可见 | 归属于单个线程,每个线程都会有宇哥栈内存, |