1. JVM的主要组成部分及其作用
JVM包含两个子系统和两个组件,两个子系统为Class Loader(类装载)、Execution Engine(执行引擎);两个组件为Runtime Data Area(运行时数据区)、Native Interface(本地接口)。
-
Class Loader(类装载):
- 作用:根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime Data Area中的Method Area。
-
Execution Engine(执行引擎):
- 作用:执行classes中的指令。
-
Native Interface(本地接口):
- 作用:与native libraries交互,是其它编程语言交互的接口。
-
Runtime Data Area(运行时数据区域):
- 作用:这是我们常说的JVM的内存。
JVM的主要作用是通过编译器将Java代码转换成字节码,类加载器(ClassLoader)再将字节码加载到内存中,放在运行时数据区(Runtime Data Area)的方法区内。执行引擎(Execution Engine)将字节码翻译成底层系统指令,再交由CPU去执行,并通过本地库接口(Native Interface)调用其他语言的功能。
Java程序运行机制详细说明:
- 利用IDE编写Java源代码,生成.java文件。
- 使用编译器(javac命令)将源代码编译成字节码文件,生成.class文件。
- 类加载器将.class文件加载到JVM中,放在运行时数据区的方法区内。
- 执行引擎将字节码翻译成底层系统指令,交由CPU执行。
2. 堆栈的区别
物理地址
-
堆:
- 不连续的内存分配,性能相对较慢。
- 内存分配在运行时确定,大小不固定。
- 存储对象实例和数组。
-
栈:
- 连续的内存分配,性能快。
- 内存分配在编译时确定,大小固定。
- 存储局部变量、操作数栈和方法返回值。
存放的内容
- 堆:存放对象的实例和数组,关注数据的存储。
- 栈:存放局部变量、操作数栈、返回结果,关注程序方法的执行。
内存分配
- 堆:对整个应用程序共享、可见,生命周期较长,适用于占用内存较大的对象。
- 栈:线程私有,生命周期与线程相同,适用于局部变量和方法调用。
3. 强引用、软引用、弱引用、虚引用的区别
-
强引用
- 定义:直接引用对象,如
Object obj = new Object();
- 特点:不会被垃圾回收,直到没有引用指向该对象,才会被回收。
- 定义:直接引用对象,如
-
软引用
- 定义:用来描述一些还有用但是非必须的对象,通过
SoftReference
类实现。 - 特点:内存不足时会被回收,适用于缓存。
- 定义:用来描述一些还有用但是非必须的对象,通过
-
弱引用
- 定义:用来描述非必须的对象,通过
WeakReference
类实现。 - 特点:在GC时总会被回收,比软引用更易被回收,适用于内存敏感的缓存。
- 定义:用来描述非必须的对象,通过
-
虚引用
- 定义:提供一种对象被垃圾回收时收到系统通知的机制,通过
PhantomReference
类实现。 - 特点:本身不影响对象生命周期,只用于跟踪对象被回收。
- 定义:提供一种对象被垃圾回收时收到系统通知的机制,通过
4. JVM类加载机制的三种特性
全盘负责
当一个类加载器负责加载某个类时,该类所依赖的和引用的其他类也将由该类加载器负责载入,除非显式地使用另外一个类加载器来载入。
示例:
- 系统类加载器
AppClassLoader
加载入口类(含有main
方法的类)时,会将main
方法所依赖的类及引用的类也载入,依此类推。 - 以上步骤只是调用了
ClassLoader.loadClass(name)
方法,并没有真正定义类。真正加载class
字节码文件生成Class
对象由“双亲委派”机制完成。
父类委托
“双亲委派”是指子类加载器如果没有加载过该目标类,就先委托父类加载器加载该目标类,只有在父类加载器找不到字节码文件的情况下才从自己的类路径中查找并装载目标类。
具体过程:
ClassLoader
先判断该类是否已加载,如果已加载,则返回Class
对象;如果没有则委托给父类加载器。- 父类加载器判断是否加载过该类,如果已加载,则返回
Class
对象;如果没有则委托给祖父类加载器。 - 依此类推,直到始祖类加载器(引用类加载器)。
- 始祖类加载器判断是否加载过该类,如果已加载,则返回
Class
对象;如果没有则尝试从其对应的类路径下寻找class
字节码文件并载入。如果载入成功,则返回Class
对象;如果载入失败,则委托给始祖类加载器的子类加载器。 - 始祖类加载器的子类加载器尝试从其对应的类路径下寻找
class
字节码文件并载入。如果载入成功,则返回Class
对象;如果载入失败,则委托给始祖类加载器的孙类加载器。 - 依此类推,直到源
ClassLoader
。 - 源
ClassLoader
尝试从其对应的类路径下寻找class
字节码文件并载入。如果载入成功,则返回Class
对象;如果载入失败,源ClassLoader
不会再委托其子类加载器,而是抛出异常。
“双亲委派”机制只是 Java 推荐的机制,并不是强制的机制。我们可以继承 java.lang.ClassLoader
类,实现自己的类加载器。如果想保持双亲委派模型,就应重写 findClass(name)
方法;如果想破坏双亲委派模型,可以重写 loadClass(name)
方法。
缓存机制
缓存机制将保证所有加载过的类都将在内存中缓存,当程序中需要使用某个类时,类加载器先从内存的缓存区寻找该类,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成 Class
对象,存入缓存区。这就是为什么修改了类后,必须重启 JVM,程序的修改才会生效。对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass
方法不会被重复调用。
示例代码:
protected Class