文章探讨的是关于JVM的底层架构,包括内存模型,调优等等…
1. 平台无关性
Java程序在运行时,会经历两个过程:编译与运行。编译时,IDE在保存java程序时会直接编译好程序,你只需直接运行就行,如果想要查看具体的编译文件,可以在CMD模式或者在IDEA环境下进行反编译(Javap)编译过后的.Class文件。
Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要进行重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令。
2. JVM如何加载.class文件
JVM的具体架构:
- Class Loader:依据特定的格式,加载class文件到内存。
- Execution Engine:对命令进行解析。
- Native Interface:融合不同开发语言的原生库为Java所用。
- Runtime Date Area:JVM开辟的内存空间。
3. 反射
反射就是把Java类中的属性或方法映射为Java对象,不管是私有(公有)属性/方法,对于任意一个对象,都能狗调用它的任意的方法或属性,这栋动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
如何获取一个类?
第一步:
用Class类的forName方法,必须用全路径,例如:
第二步:
用newInstance方法创建类的实例
获取方法的声明:
Method 方法名=获取类的名字.getDeclareMethod(类是方法名,参数类型.class);
方法名.setAccessible(true);
获取Public方法:
4. ClassLoader
ClassLoader工作在Class装载的加载阶段,器主要作用是从系统外部获得Class二进制数据流。它是Java的核心组件,所有的Class都是有ClassLoader进行加载的,ClassLoader负责通过将class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等操作。
Bootstrap ClassLoader/启动类加载器
主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作,用C++编写
Extension ClassLoader/扩展类加载器
主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作
App ClassLoader/系统类加载器
主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作
Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)
用户自定义类加载器,必须继承自java.lang.ClassLoader
自定义ClassLoader的实现:
关键函数:
5. 类加载器的双亲委派机制
为什么要使用双亲委派机制?
- 避免多份同样字节码的加载
6. LoadClass和forName的区别
类的加载过程
- Class.forName得到的Class是已经初始化完成的
- ClassLoader.loadClass得到的class是还没有链接的,没有初始化完成的,多用于SpringMVC
7. Java内存模型
Java程序运行在IDE上面,需要内存空间,就需要划分内存。以JDK8举例:
JVM内存模型:
线程私有:
程序计数器(Program Counter Register):
当前线程所执行的字节码行号指示器(逻辑)
改变计数器的值来选取下一条需要执行的字节码指令(循环、分支、跳转等等)
和线程是一对一的关系即“线程私有”
对Java方法计数,如果是Native方法则计数器值为Undefined
不会发生内存泄露
Java虚拟机栈(Stack):
Java方法执行的内存模型
包含多个栈帧
栈帧示意图:
栈帧用于存储局部变量表,对应了各个方法的入栈出栈的过程,Java虚拟机栈用于存储栈帧,当方法调用结束时,帧才会被销毁。
局部变量表和操作数栈
局部变量表:包含方法执行过程中的所用变量
操作数栈:入栈,出栈,复制,交换等等操作
本地方法栈:主要用于标注Native方法
线程共享:
线程共享包含元空间(MetaSpace)和永久代(PermGen)
元空间使用本地内存,永久代使用JVM的内存
元空间的优势:
字符串常量池存在永久代中,容易出现性能问题和内存溢出
类和方法的信息大小难易确定,给永久代的大小指定带来困难
永久代会为GC带来不必要的复杂性
Java堆(Heap):
对象实例的分配区域
GC管理的主要区域
JVM调优:
参数实例:
-Xss:规定了每个线程虚拟机栈(堆栈)的大小
-Xms:堆的初始值
-Xmx:堆能达到的最大值
Java内存模型中堆和栈的区别-内存分配策略:
静态存储:编译时确定每个数据目标在运行时的存储空间需求
栈式存储:数据区需求在编译时未知,运行时模块入口确定
堆式存储:编译时或运行时模块入口都无法确定,动态分配
管理方式:栈自动释放,堆需要GC
空间大小:栈比堆小
碎片相关:栈产生的碎片小于堆
分配方式:栈支持静态和动态分配,而堆仅支持动态分配
效率:栈的效率比堆高