根据JVM规范,JVM 内存共分为虚拟机栈,堆,方法区,程序计数器,本地方法栈五个部分。为了方便培养大家的全局观,本文从java文件编译开始介绍
本文基于jdk1.8,其他版本的介绍文中会有标明
Java内存模型
- 线程私有:虚拟机栈,本地方法栈,程序计数器
- 线程共享:堆,方法区,直接内存
1. java文件编译成Class文件
编译过程
从javac的代码的总体结构来看,编译过程大致分为1个准备过程和3个处理过程
- 准备过程:初始化插入式注解处理器
- 解析和填充符号表过程,包括:词法语法分析,将源代码的字符流转变成标记集合,构造出抽象语法树,填充符号表。产生符号地址和符号信息
- 插入式注解处理器的注解处理过程
- 分析与字节码生成过程
a:标注检查。对语法的静态信息进行检查
b:数据流及控制流分析,对程序动态运行过程进行检查
c:解语法糖。将简化代码编写的语法糖还原为原有的格式
d:字节码生成。将前面代码编写的语法糖还原为原有的格式
编译做了什么
1.常量
2.静态变量
3.静态常量
1.1 jvm有多少中常量池
一提到常量池就头疼,到底有多少常量池呢?位置在哪?都有什么作用?目前查资料了解到的有:Class文件常量池、运行时常量池,全局字符串常量池,以及基本类型包装类对象常量池
Class文件常量池
class文件中有一项是constant_pool(常量池),里面主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
字面量如:文本字符串、final常量值,
举个例子:string a="abc"中,"abc"就是字面量
符号引用包含下面三类:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
运行时常量池
是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,Java语言并不要求常量一定只有编译期才能产生,运行时也可以增加常量。
全局字符串常量池
从jdk7开始,方法区中的字符串常量池移到了Java堆中,这也意味着字符串常量池是所有线程共享的,JVM做的一个优化就是字符串常量池的常量是唯一的。
举个例子:下面这个代码创建了几个对象
String a=new String ("abc")
回答:可能是一个,也可能是两个,因为String的构造函数,JVM会先去字符串常量池去找"abc",如果有就不再创建,根据new命令会去堆中创建一个String对象,所以是一个;如果字符串常量池中没有,就会在常量池中创建一个,堆中创建一个,一共创建了2个。
再深入分析一下,(==如果只基本类型比较就是比较值,如果是对象就是比较地址)
上图中,s1==s2值为false,印证了 s1指向的是堆中的String对象,s2指向的是字符串常量池中的String对象,s3也是指向字符串常量池中的String对象。
基本类型包装类对象常量池
2. 类加载子系统加载.Class文件(类加载机制)
类加载机制:https://blog.csdn.net/yujing1314/article/details/105935391
双亲委派机制:https://blog.csdn.net/yujing1314/article/details/105838620
3. 运行时数据区
3.1 堆
堆的内存分配
简述
堆分为新生代和老年代,比例为1:2,新生代又分为伊甸园区,幸存者0区和幸存者1区
主要存储
JVM百分之95的对象都存在于堆中,所以垃圾回收算法主要针对的也是堆区
垃圾回收器
新生代的垃圾回收算法有Serial、Parnew等
老年代的垃圾回收器有CMS、PS等
垃圾回收算法
新生代的垃圾回收算法:标记-复制
老年代的垃圾回收算法:标记清除、标记整理
3.2 方法区
方法区是一种规范,1.7之前的实现是永久带,因为永久带容易导致内存溢出,1.7以后就去掉了永久带,将方法区的实现移到了元空间之中
3.3 虚拟机栈
栈是由栈帧组成的,一个栈帧中存了局部变量表、操作数栈、动态链接、方法返回地址
3.4 程序计数器
程序计数器中储存了下一条指令的地址
3.5 本地方法栈
本地方法栈主要是针对本地方法的