1.jvm内存区域
类通过类装载到相应内存区域,每一个线程都有单独的栈,本地方法栈,程序计数器,然后将每个方法进行压栈,每个方法压入站后为一个栈帧,栈帧中常用的几个区域为局部变量表,操作数栈,动态链接,方法出口。
栈帧
局部变量表:存储的局部变量
操作数栈:方法栈进行例如a+b这样的计算在这个内存区域进行,将a和b的值取到操作数栈中进行计算
动态链接:如果需要用到其他的类,会进行装载
方法出口:方法结束后出去对应的位置
程序计数器
记录执行到哪一行
在jvm指令中有例如 1:iload_0这样的指令,旁边的1就是执行的位置
本地方法栈
底层对内存操作需要用的c++/c的代码
线程共享的区域
堆:给对象内存空间,栈帧中的局部变量存储的是对应对象的地址
方法区: 之前叫永久代,容易发生oom,现在由元空间代理,直接使用本地内存,jdk1.8将字符串常量池和运行时常量池移到堆里,逻辑上看元空间、运行时常量池还是属于方法区,方法区存放类加载的各类元信息,从方法区中读取类的字节码文件执行。
jdk1.8
当常量池中没有该字符串时,JDK7的intern()方法的实现不再是在常量池中创建与此String内容相同的字符串,而改为在常量池中记录java Heap中首次出现的该字符串的引用,并返回该引用。
2.jvm指令分析
public class aaaa {
public static int math(){
int a = 0;
int b = 1;
int c = a+b;
return c;
}
public static void main(String[] args) {
int math = math();
}
}
我们将aaaa给编译,会发现class文件我们看不懂,我们可以通过jdk自带的javap命令反汇编来看
Compiled from "aaaa.java"
public class com.aaaa {
public com.aaaa();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static int math();
Code:
0: iconst_0
1: istore_0
2: iconst_1
3: istore_1
4: iload_0
5: iload_1
6: iadd
7: istore_2
8: iload_2
9: ireturn
public static void main(java.lang.String[]);
Code:
0: invokestatic #2 // Method math:()I
3: istore_1
4: return
}
我们可以看到每一行jvm指令旁边都有行号,这就是程序计数器记录的。
简单分析下这个代码,还有许多jvm指令没有在这个例子,我们可以查下jvm指令所对应的信息
iconst_0 将int型(0)推送至栈顶
istore_0 将栈顶int型数值存入第一个本地变量
iload_0 将第一个int型本地变量推送至栈顶
iadd 将栈顶两int型数值相加并将结果压入栈顶
3.内存回收
jvm会对没用的对象进行gc,依据的算法是gc root可达性分析算法,如果一个类被引用,以这个类为root根向下伸展,他所依赖的其他类都被标记为使用的,如果没有根,就会被标记为可gc的。
在堆中,我们堆内存2/3是老年代,1/3是年轻代
年轻代:内存分布如上图
在年轻代中发生的gc是Minor GC,会将Eden中没用的类清理,将剩下的类放到From区域,下次一gc时,会将Eden和From中的类清理,将幸存下来的类方法To区域,再下一次的时候就是Eden+To->From,也就是说一直有一个区域会是空的,这样更好的利用内存空间。
在年轻代中gc了15次还幸存下来的类会直接放到老年代,老年代中内存区域过大发生的gc为full gc,full gc会stw(停止线程工作来进行gc),这对线上业务影响巨大,我们应该减少full gc的出现。
4.垃圾回收算法
-
标记-清除
-
复制
-
标记-整理
5.垃圾回收器(各个回收器简介)
- 年轻代
Serial: 串行收集,单线程, 使用复制算法,STW
(可配合SerialOld)
parNew: 并行收集,多线程,使用复制算法,STW
(Serial的多线程版本,可配合CMS)
parallel Scavenge :并行,复制算法,多线程,STW
(关注吞吐量的垃圾收集器,可配合Parallel Old),吞吐量指一段时间内gc的时间短,不是指单次gc. - 老年代
Serial old : 串行收集, 标记-整理单线程,STW
parallel old :标记-整理,并行收集,STW
(和parallel Scavenge配合,适合关注吞吐的场景)
CMS: 标记-清理
1、CMS intial mark(初始标记):标记GC Roots能直接关联到的对象,速度很快
2、CMS concurrent mark(并发标记):进行GCRoot Tracing过程(用来判断对象是否与GC root有引用链)
3、CMS remark(重新标记):修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,停顿时间长于初始标记,但是时间远远少于并发标记。
4、CMS concurrent sweep(并发清除):清除回收对象
其中初始标记与重新标记需要“stop the world”。其他两个并发标记与并发清除过程CMS收集器线程可以与其他用户线程一起运行,所以从总体上来说,该手机其是与用户线程一起并发执行的。
(极大的降低StopTheWorld时间,是服务端常用的垃圾收集器,配合PawNew使用。不过标记清理是有内存碎片的,这是个比较明显的缺点)
JDK8默认收集器:Parallel Scavenge + Serial Old
JDK9默认收集器: G1
6.G1垃圾收集器
作用范围:老年代+年轻代
jdk9后默认的收集器,用g1专有的mixedGc最大程度减少full gc的发生,如果发生full gc,会使用单线程执行的serial old gc,会导致异常长时间的暂停时间,需要进行不断的调优,G1尽可能的避免了full gc(gc收集器本身不对full gc 进行处理的)
1.内存结构:
g1回收器将老年代和新生代的内存空间划分成了一个个region,将堆内存划分成了2000块左右的小块,每块大小1-32m(2的幂次),分配灵活,具体可以通过参数设定,但是存在大对象的问题:名称沿用年轻代的eden,to ,from
1)小于一半region size的可以正常存入E区(Eden)
2)一半到一个region size的直接存入O区一个region,这个region又叫Humongous region,叫H区(本质是O区的)
3)比一个region size还要大的对象,需要存入连续的多个region,这多个region都是H区
RememberSets(Rsets):是每个region中都有的一份存储空间,用于存储本region的对象被其他region对象的引用记录。(region中所引用到的其他对象的region的记录)
CollectionSets(Csets) :又叫Csets是一次GC中需要被清理的regions集合,注意G1每次GC不是全部region都参与的,可能只清理少数几个,这几个就被叫做Csets.(需要清理的region集合)
Young GC
将region中e区和from区的通过复制算法到region的to区
使用复制算法
Mix GC:和CMS有点类似
1)初次标记,标记GcRoot直接引用的对象所在的Region,和CMS不同的是,不止标记Old区,和YGC同时发生,利用YGC的STW时间完成。 (STW)
2)RootRegion扫描,扫描GcRoot所在的region到Old区的引用(利用了rset)
标记的部分(阴影部分)
3)并发标记,同cms,不过通过上一步的标记部分减少了搜索范围,如果发现某个region所有对象都是垃圾,则标记为X
4)重新标记,删除X区,采用初始标记阶段的SATB,速度很快。 (STW)
5)复制/清理,选择Y区的垃圾region和垃圾比较多的O区regions组成Csets,进行复制清理 (STW)
G1提高效率的点有哪些?
1 重新标记时X区域直接删除。
2 Rset降低了扫描的范围。
3 重新标记阶段使用SATB速度比CMS快。
4 清理过程为选取部分垃圾多的的Region进行清理,不是全部,提高了清理的效率。