Java虚拟机
谈谈你对Java的理解
- 平台无关性
- GC
- 语言特性
- 面向对象
- 类库
- 异常处理
Java的平台无关性
(javap -c 对代码进行反编译,生成class文件里面的内容)
Java源代码首先被编译为字节码(.class),再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要进行重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令。
为什么JVM不直接将源码解析为机器码:
- 准备工作:每次工作都需要语法等检查
- 兼容性:也可以将其他语言解析成字节码进行运行,兼容性强
JVM如何加载.class文件
Java虚拟机(在内存中)
- Class Loader:依据特定格式,加载class文件到内存
- Execution Engine(执行引擎):对命令进行解析
- Native Interface(本地库接口):融合不同开发语言的原生库为Java所用
- Runtime Data Area(运行时数据区):JVM内存空间结构模型
注:运行时常量池在JDK1.7由方法去转移进堆中。
图片来源:《深入理解Java虚拟机(第二版)》
Java内存模型(Java Memory Model)(重要)
- 程序计数器
- 线程私有
- 当前线程所执行的字节码行号指示器
- 改变计数器的值来选取下一条需要执行的字节码指令
- 如果执行Native方法,计数器的值为Undefined
- 不会发生内存泄露
- Java虚拟机栈
- 线程私有
- Java方法执行的内存模型
- 包含多个栈帧
- 局部变量表:用于存放方法参数和方法内部定义的局部变量
- 操作数栈:入栈、出栈、复制、交换、产生消费变量
- 动态链接
- 返回地址
- 本地方法栈
- 线程私有
- 与虚拟机栈类似,主要作用于native方法
- 元空间(MetaSpace)与永久代(PermGen)区别——均是方法区的实现
- 元空间使用本地内存,永久代使用jvm内存
- MetaSapce相比PermGen(已被去掉)的优势
- 字符串常量池存在永久代中,容易出现性能问题和溢出
- 类和方法的信息大小难以确定,给永久代的大小指定带来困难
- 永久代会为GC带来不必要的复杂性
- 方法区会记录类星系(方法表等)
- Java堆(Heap)
- 存放对象实例的分配区域
- GC管理的主要区域
JVM三大性能调优参数 -Xss -Xmx -Xms含义
- -Xss:规定了每个线程虚拟机栈(堆栈)的大小
- -Xms:堆的初始值
- -Xmx:堆能达到的最大值
Java内存模型中堆和栈的区别——内存分配策略
- 静态存储:编译时确定每个数据目标在运行时的存储空间需求
- 栈式存储:数据区需要在编译时位置,运行时模块入口前确定数据区大小
- 堆式存储:编译时或运行时模块入口都无法确定,动态分配
- 联系:引用对象、数组实,栈里定义变量保存堆中目标的首地址(保证栈式存储在运行时能确定大小,堆式存储可以动态分配内存)
- 区别:
- 管理方式:栈自动释放,堆需要GC
- 空间大小:栈比堆小
- 碎片相关:栈产生的碎片远小于堆
- 分配方式:栈支持静态和动态分配,而堆仅支持动态分配
- 效率:栈的效率比堆高
图片来源:慕课网
不同JDK版本之间intern()方法的区别——JDK6 vs JDK6+
- JDK6:当调用intern方法时,如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用。否则,将此字符串对象添加到字符串常量池中,并且返回该字符串对象的引用。
- JDK6+:当调用intern方法时,如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用。否则,如果该字符串对象已经存在于Java堆中,则将堆中对此对象的引用添加到字符串常量池中,并且返回该引用;如果堆中不存在,则在池中创建该字符串并返回其引用。
类的加载方式
- 隐式加载:new
- 显示加载:loadClass,forName等(需要手动调用newInstance()方法)
类的装载过程
- 加载:通过ClassLoader加载class文件字节码,生成Class对象
- 链接:
- 校验:检验加载的class的正确性和安全性
- 准备:为类变量分配存储空间并设置类变量(static变量)初始值(各种形式的0)
- 解析:JVM将常量池内的符号引用转换为直接引用
- 初始化:执行类变量赋值和静态代码块
.class、loadClass和forName的区别
- .class操作不进行加载
- ClassLoader.loadClass得到的class是还没有链接的(只完成了加载)
- Class.forName得到的class是已经完成初始化的
Java反射
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java的反射机制。
1 | public class Robot { |
类从编译到执行的过程
- 编译器将.java源文件编译为.class字节码文件
- ClassLoader将字节码转换为JVM中的Class对象
- JVM再利用Class\<T>对象实例化为T对象
ClassLoader
- ClassLoader主要工作在Class装载的加载阶段,其主要作用是从系统外部获得Class二进制数据流。
- 它是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等操作。
ClassLoader种类
- BootStrapClassLoader(启动类加载器):C++编写,加载核心库java.*
- ExtClassLoader(扩展类加载器):Java编写,加载扩展库javax.*
- AppClassLoader(应用程序类加载器):Java编写,加载程序所在目录
- UserClassLoader(自定义类加载器):Java编写,定制化加载
为什么要使用双亲委派模型
双亲实际上是一种对parents的翻译错误,实际上并没有“双”这个概念!
对于任意一个类,都需要有加载它的类加载器(ClassLoader)和这个类本身来一同确立其在Java虚拟机中的唯一性。
双亲委派:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
图片来源:【JVM】浅谈双亲委派和破坏双亲委派
一个符合规范的类加载器,应当仅覆写ClassLoader#findClass(),以支持自定义的类加载方式。
使用双亲委派模型的意义
避免多份同样字节码的加载
Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object类。容易引发安全问题。
作者:shuixingge
链接:https://www.jianshu.com/p/6dbe64a333e7
来源:简书
JVM调优
查找占用内存最大的进程:ps -ef | grep java 或者top 再按m获得占用内存最大的进程的pid
jmap -histo:live 25085 | head -20 查看前20个占用最大的对象
jps:JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。-l : 输出主类全名或jar路径
jstat:JVM statistics Monitoring,是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
jmap:Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47>jmap -heap 14732
Attaching to process ID 14732, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.151-b12
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2124414976 (2026.0MB)
NewSize = 44564480 (42.5MB)
MaxNewSize = 707788800 (675.0MB)
OldSize = 89653248 (85.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 34078720 (32.5MB)
used = 26630104 (25.396446228027344MB)
free = 7448616 (7.103553771972656MB)
78.14291147085336% used
From Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
To Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
PS Old Generation
capacity = 89653248 (85.5MB)
used = 0 (0.0MB)
free = 89653248 (85.5MB)
0.0% used
5611 interned Strings occupying 466504 bytes.- jmap -histo | more:打印堆的对象统计,包括对象数、内存大小等等 (因为在dump:live前会进行full gc,如果带上live则只统计活对象,因此不加live的堆大小要大于加live堆的大小 )