- 本文以 hotspot 虚拟机- jvm是一个内存中的虚拟机,也就代表着它的存储就是内存,我们所写的类、常量、方法都在内存中
1. 引言
1.1 java是如何实现平台无关性

- 程序员编写一个
hello.java文件 - 使用
javac hello.java命令开启java编译器进行编译,生成一个hello.class文件 - 使用
java hello启动虚拟机运行程序 - 虚拟机首先会将字节码加载到内存中,这个过程称为类加载,是由类加载器
(class loader)完成 然后虚拟机针对加载到内存中的字节码进行解析,比如实例化对象……
2. Jvm的构造
2.1 类加载器(class loader)
2.1.1 作用
主要是从系统外部获取特定格式的字节码文件,将其转换为二进制数据流,加载进入系统,交给虚拟机进行连接,初始化等操作,因为虚拟机就在内存中所以,类加载器也可以说将字节码文件加载到内存中
2.1.2 种类
对用户不可见,想看的话去看
jvm的源码BootStrapClassLoader:C++编写,加载核心类库java.*ExtensionClassLoader:java编写,加载扩展库javax.*,自定义jar包AppClassLoader:java编写,加载程序所在目录,加载classPath,即环境变量CustomClassLoader:java编写,自定义class load定制化加载2.1.3 双亲委派机制

双亲委派机制(上图↑)
- 自底向上的检查类是否已经加载,若发现没有加载
- 自顶向下的尝试加载类
- 该加载机制的优势
- 避免多份同样的字节码文件被夹在,浪费内存
源码解析
// 注意: 一些不重要的代码已经删除,想看直接来看源码protected Class<?> loadClass(String name, boolean resolve) throwsClassNotFoundException {// 需要用同步锁,防止在高并发下多个线程同时加载synchronized (getClassLoadingLock(name)) {// 首先检查类是否已经加载,如果已经加载,因为resolve默认是false直接返回加载的类Class<?> c = findLoadedClass(name);// 如果类没有加载,就开始下面的骚操作if (c == null) {// 这里就是一层一层的向上询问,父类加载器是否加载过该类// 最后因为BootstrapClassloader是c++写的需要调用native的方法if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}// 检查完4层的类加载器,发现类还是没有被加载过,则尝试从上到下不断尝试加载该类if (c == null) {c = findClass(name);}}return c;}}
2.2 运行时区域(runtime data area)
运行时区域这个就是
**Jvm**的内存结构模型
2.2.1 线程问题

- 线程私有: 程序计数器、虚拟机栈、本地方法栈
-
2.2.2 程序计数器
记录当前线程所执行的字节码文件的行号指示器,改变计数器的值选取下一行所需要执行的指令
- 如果是执行java方法则是计数能力,如果是
native的方法此时的值是Undefined - 和线程是一对一的关系,所以“线程私有”
-
2.2.3 虚拟机栈
java方法执行的内存模型
- 包含多个栈帧,每个方法被执行的时候都会创建一个栈帧,即方法运行期间的基础数据结构,当方法调用结束的时候帧才会被销毁
- 栈帧存储的信息:局部变量表、操作栈、动态连接、返回地址
- 局部变量表:包含方法执行过程中的所有变量
- 操作栈:入栈、出栈、复制、交换、产生消费变量
问题一:递归为什么会引起
**java.lang.stackOverFlowError**,栈溢出?- 每创建一个方法就会产生一个栈帧,系统会在该方法执行完成之后销毁栈帧
- 每个线程的虚拟机栈深度是有限的
- 递归调用会不断的产生栈帧,并且不会销毁,所以造成栈溢出
问题二:虚拟机栈过多,会引起
**java.lang.OutOfMemoryError**,内存溢出?- 虚拟机栈会进行动态扩展内存,如果未能申请到扩展内存,便会报出上面一错误
2.2.4 本地方法栈
2.2.5 方法区
- 方法区只是
JVM的规范,它的实现是元空间(mate space),jdk7以前叫做永久代(PermGen) - 两者都是用来存储
class的相关信息,包含class对象的method等 jdk7之前字符串常量池位于方法区,jdk8之后移动到了堆内存中问题三:
**Mate Space**相较于**PermGen**的优势?(核心是**mate space**使用本地内存产生的好处)**Mate Space**使用本地内存;**PermGen**使用的**jvm**的内存- 字符串常量池存在
**PermGen**中容易出现性能问题和内存溢出 - 类和方法的信息大小不确定,给永久代大小指定带来困难,太小容易永久代溢出,太大容易导致老年代溢出
**PermGen**会为**GC**带来不必要的复杂性,并且回收效率偏低,是指永久代不足的时候会触发**Full GC**
2.2.6 堆
- 对象实例的分配区域,并且堆内存是可扩展的,物理地址可以是不连续的,逻辑是连续的即可,如果堆内存申请扩展没有收到相应的内存,会抛出
java.lang.OutOfMemoryError -
2.3 执行引擎(execution engine)
负责对
class load加载到内存中的命令进行解析,解析完成提交到操作系统执行2.4 本地接口(native interface)
融合不同语言的原生开发库为
java所用,比如“Class.forName()”,锁底层实现CAS一些问题
类装载的过程
加载: 通过classLoader加载class字节码文件,生成class对象
- 链接
a. 校验: 检查加载的class的正确性和安全性
b. 准备: 为类变量分配存储空间,并设置类变量的初始值
c. 解析: jvm将常量池内的符号引用转换为直接引用 -
JVM三大内存调优参数的意义
-Xms -Xmx -Xss 静态存储: 编译时确定每个数据目标在运行的的存储空间需求
- 栈式存储: 数据需求在编译时未知,运行时模块入口前确定
-
内存模型中堆和栈的区别
联系:引用对象、数组时,栈里面定义变量保存堆中目标的首地址;当他们没有引用对象指向的时候,在一个不确定的时间被GC回收
- 管理方式:栈自动释放,堆需要GC
- 空间大小:栈比堆小
- 碎片相关:栈产生的碎片远小于堆
- 分配方式:栈能支持静态分配和动态分配,堆只支持动态分配
- 效率:栈的效率比堆高很多
元空间、堆、线程独占部分之间的联系-内存角度
- 翔仔面试视频
6-11的9:36 - 执行
main方法之内存中的变化,看图即可


