目录
一、JVM的内存划分
一个运行起来的Java进程,就是一个Java虚拟机,就需要从操作系统中申请一大块内存。申请的内存会划分为不同的区域,每个区域有不同的作用。
1、方法区/元数据区 存储的内容为类对象
2、堆 存储的内容为代码中new的对象
3、栈代码执行过程中,方法之间的调用关系
4、程序计数器 存放"地址",表示下一条指令在内存的哪个地方
其中,栈和程序计数器每个线程都有一个。
一个变量处于哪个区域和变量的形态密切相关局部变量处于栈上
成员变量处于堆上
静态变量(类属性)处于方法区/元数据区上
局部变量和成员变量的区别1.在类中的位置不同
成员变量:在类中,方法外
局部变量:在方法定义中或者方法声明上
2.在内存中的位置不同:
成员变量:在堆内存中
局部变量:在栈内存中
3.生命周期不同:
成员变量:随着对象的创建而存在,随着对象的消失而消失
局部变量:随着方法的调用而存在,随着方法的调用完毕而消失
4.初始化值不同
成员变量:有默认的初始化值
局部变量:没有默认的初始化值,必须定义、赋值,然后才能使用。
二、JVM的类加载过程
Java程序想要运行起来,就需要让JVM读取到Java中的文件,并且把里面的内容构造成类对象,保存在内存的方法区中。
基本流程:
1、加载
找到.class文件,打开文件,读取文件内容。往往代码中会给某个类"全限定类名",方便区分。
例如:java.long.String java.util.ArrayList
JVM就会根据这个类名,在一些指定的目录范围内查找。
2、验证.class文件是一个二进制的格式(每个字节都是有特定含义的),就需要验证当前读到的这个格式是否符合要求。
3、准备给类对象分配内存空间,只是分配空间,还没有初始化。
4、解析针对类对象中的字符串常量进行处理,进行一些初始化操作,这个过程把"符号引用"替换成"直接引用"。
5、初始化针对类对象进行初始化,把类对象中的各个属性都设置好。
三、JVM的垃圾回收机制(GC)
在Java中,new一个对象,也就是"动态内存申请",当内存不再使用,JVM会自行判断,如果这个内存后面确实不用了,JVM就会自动的把这个内存给回收。
GC的缺陷:1、系统开销大,要有特定的线程进行扫描
2、效率问题,扫描线程要有一定的周期,不能及时释放
回收步骤:1、找到要回收的对象,利用可达性分析。
有一组线程,周期性的扫描代码中的所有对象,从特定的对象出发,尽可能的进行访问的遍历,把所有能够访问的对象,都标记成"可达",反之,经过扫描后,未被标记的对象,就是需要回收的垃圾。
2、释放垃圾
1、标记清除
直接把垃圾进行释放,可能会产生很多的内存碎片。
2、复制算法
通过复制的方式,把有效的对象,归类在一起,再统一进行释放。
把内存分成两份,如果1、3、5是垃圾,就把2、4复制到另一份,然后把前一份整体释放。
优点:可以有效的解决内存碎片化的问题
缺点:1、内存要浪费一半,利用率不高
2、如果有效对象非常多,拷贝的开销就非常大
3、标记整理
如果1、3、5是垃圾,就将2、4往前搬运,然后整体释放。
既能解决内存碎片化的问题,又能处理算法的利用率,但是搬运的开销很大。
四、分代回收
实际上,JVM采用的思路是基础思路的结合体,分代回收。
在新生代中会包含伊甸区和幸存区
**伊甸区:**刚new出来的新对象,放到伊甸区。从对象诞生,到第一轮扫描分析,这个时间不长,但是,在这个时间里,大部分对象都会变成无用的对象。
幸存区:
1、伊甸区到幸存区
复制算法,每一轮GC扫描后,都能把有效对象复制到幸存区,伊甸区就可以整体释放了。
2、幸存区到幸存区
GC扫描线程也会扫描幸存区,就会把可达的对象,拷贝到幸存区的另一部分。
会进行多轮扫描,每一轮会拷贝很多对象,也会淘汰一部分对象。
3、幸存区到老年代
当对象经过多轮扫描后,JVM认为这个对象短期无法释放,就会把这个对象拷贝到老年代。
老年代:
进入老年代的对象,虽然也会被GC扫描,但频率会降低很多,可以减少开销。
新生代,主要使用复制算法。老年代,主要使用标记整理。