一.什么是jvm
java中的jvm又称java虚拟机。java之所以是跨平台的编程语言,和jvm有很大的关系。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。在引入了jvm后,java代码在不同的平台上运行不需要再重新编译,只需要编译成在jvm上运行的字节码文件,jvm会帮助我们把字节码文件翻译成在具体平台上的机器指令,这就是传说中的“一次编译,到处运行”。
二.jvm的内存模型
jvm在执行java代码时,把它管理的内存区域分为5个不同的区域,分别为堆,方法区,java虚拟机栈,本地方法栈和程序计数器。其中,堆和方法区是所有的线程所共享的,后三者是线程私有的。jvm划分的这5个区域,各有各的作用,共同管理内存。
- 程序计数器
每个线程都有自己的程序计数器,它记录着当前线程执行到字节码文件的哪一行。如果执行的是本地方法,则它的值为null - 虚拟机栈
虚拟机栈占用是操作系统的内存,一个线程的方法在执行的时候,就会创建一个栈帧,栈帧中储存的是局部变量表,方法出口等,当方法被调用时,栈帧入栈,当方法执行完毕后出栈。 - 本地方法栈
本地方法栈储存的是本地方法调用的状态,它于虚拟机栈唯一的区别就是,虚拟机栈是执行java方法的,而它是执行native方法的,在很多虚拟机中,会将本地方法栈和虚拟机栈放在一起使用。 - 方法区
方法区中存放了要加载的类的信息,如类的信息(类名,修饰符等),类中的静态变量,常量,属性和方法信息。它也被称为是“永久代”,但在一定的条件下,也会被gc回收。需要注意的是,运行时常量池也在方法区中。 - 堆
所有的对象实例和数组都要在堆上分配。它是垃圾回收器主要管理的区域。为了更好的回收,把堆分为新生代和老年代(1:2)。其中新生代又分为Eden空间和2个Survivor空间(8:1:1)。
三. 虚拟机中GC的过程
(s1代表“from Survivor”,s2代表“to Survivor”)
(判断一个对象是否存活简单的说就是判断这个对象是否被引用)
- 新创建的对象被分配在Eden区,它的内存空间是连续的,2个survivor空间是空的
- 当Eden区满了后,就触发minor GC,经过扫描和标记,把存活的对象复制到s1,不存活的对象被回收。
- 在下一次minor GC时,重复上一次操作,把存活的对象复制到s2,其余对象回收。其中,s1中存活的对象年龄加1。此时,eden区和s1区被清空,所有存活的对象复制到了s2区。
- 再下一次minor GC时,重复这个过程,保证Eden区和一个survivor是被清空的。
- 当存活中的对象的年龄在达到一个阈值时(默认是8,可配置),就会从年轻代移入老年代。
- 在发生minorGC时,虚拟机会检测老年代的剩余内存是否大于年轻代进入老年代的内存,如果小于,则执行Full GC。把老年代中没有引用的对象回收。
- Minor GC ,Full GC 触发条件
Minor GC触发条件:当Eden区满时,触发Minor GC。
Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法区空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
四. jvm中类加载的过程
类加载的过程包括了5个阶段:加载,验证,准备,解析和初始化
常量池:常量池中的数据在编译时就已被确定,储存了类,方法,接口等中的常量,字符串常量等。(和运行时常量池不同)
常量池中主要存放两大类常量:
字面量:文本字符串,声明为final的常量等。
符号引用:类和接口的完全限定名,字段的名称和描述,方法的名称和描述。
- 加载
通过类的权限定名称获取这个类的二进制字节流,然后将其代表的静态存储结构转化为方法区的运行时数据结构,最后再堆区创建这个类的Class对象. - 验证:确保Class中的字节流文件是否符合虚拟机的要求
- 准备:为类的静态变量分配存储空间,并赋予初始值。
- 解析:将常量池中的符号引用转化为直接引用。
- 初始化:执行java代码
五. jvm中对象的创建过程(new)
- 检查类是否被加载过,如果没有,则先加载类。
- 在堆中给对象分配内存
- 给分配的内存空间赋初始值
- 设置对象的头信息(对象的Gc年龄,哈希码等)
- 执行完new后,接着执行方法,按照代码进行初始化。