JVM原理

JVM探究

  • 请你谈谈对JVM的理解? Java8虚拟机和之前的变化更新?
  • 什么是OOM, 什么是栈溢出?怎么分析?
  • JVM的常用调优参数有哪些?
  • 内存快照如何抓取, 怎么分析Dump文件?
  • 谈谈JVM中, 类加载器的认识?

JVM的位置

JVM的体系结构

所谓的JVM调优, 99%的情况下都是在方法区和堆中调的. 这之中绝大部分都是在调堆.

运行时数据区中的 栈, 本地方法栈, 程序计数器这三个部分不可能有垃圾

类加载器

加载Class文件: new Student();

引用是在栈里, 具体的实例放在堆中

实例化一个Car类的过程:

生成的实例对象包括各种属性放在堆中, 名字也就是地址放在栈中. 引用时根据栈中的地址去堆中引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Car {

public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();

Class<? extends Car> aClass1 = car1.getClass();
Class<? extends Car> aClass2 = car2.getClass();
Class<? extends Car> aClass3 = car3.getClass();

System.out.println(aClass1);
System.out.println(aClass2);
System.out.println(aClass3);
}

}

输出结果为:

1
2
3
class Car
class Car
class Car

类加载器的分类:

  1. 虚拟机自带的加载器
  2. 启动类(根)加载器: 负责加载%JAVA_HOME%\bin目录下的所有jar包,或者是-Xbootclasspath参数指定的路径;
  3. 扩展类加载器: 负责加载%JAVA_HOME%\bin\ext目录下的所有jar包,或者是java.ext.dirs参数指定的路径;
  4. 应用程序加载器: 负责加载用户类路径上所指定的类库,如果应用程序中没有自定义加载器,那么次加载器就为默认加载器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Car {

public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();

Class<? extends Car> aClass1 = car1.getClass();

ClassLoader classLoader = aClass1.getClassLoader();
ClassLoader parent = classLoader.getParent();
ClassLoader parent1 = parent.getParent();
System.out.println(classLoader);
System.out.println(parent);
System.out.println(parent1);

}

}

输出为:

1
2
3
jdk.internal.loader.ClassLoaders$AppClassLoader@3fee733d
jdk.internal.loader.ClassLoaders$PlatformClassLoader@723279cf
null

双亲委派机制

  1. 类加载器收到类加载的请求
  2. 将这个请求向上委托给父类加载器去完成, 一直向上委托, 直到启动类加载器
  3. 启动类加载器检查是否能够加载当前这个类, 能加载就结束, 使用当前加载器, 否则, 抛出异常, 通知子加载器进行加载

接下来举个例子:

大家所熟知的Object类,直接告诉大家,Object默认情况下是启动类加载器进行加载的。假设我也自定义一个Object,并且制定加载器为自定义加载器。现在你会发现自定义的Object可以正常编译,但是永远无法被加载运行。

这是因为申请自定义Object加载时,总是启动类加载器,而不是自定义加载器,也不会是其他的加载器。

沙箱安全机制

Java安全模型的核心就是Java沙箱, 沙箱是一个限制程序运行的环境. 沙箱机制就是将Java代码限定在虚拟机特定的运行范围中, 并且严格限制代码对本地系统资源访问, 通过这样的措施来保证对代码的有效隔离, 防止对本地系统造成破坏. 沙箱主要 限制系统的资源访问 , CPU, 内存, 文件系统, 网络. 不同级别的沙箱对这些资源访问的限制也可以不一样

Native

在Java 的Thread 类中, 有这样的方法:

凡是带了native 关键字的, 说明java的作用范围达不到了, 会去调用底层c语言的库

会进入本地方法栈, 然后它会调用JNI(本地方法接口), 继而调用本地方法库

JNI的作用: 扩展Java的使用, 融合不同的编程语言为Java所用

PC寄存器

程序计数器: Program Counter Register

每个线程都有一个程序计数器, 是线程私有的, 就是一个指针, 指向方法区中的方法字节码(用来存储指向一条指令的地址, 也即将要执行的指令代码), 在执行引擎读取下一条指令, 是一个非常小的内存空间, 几乎可以忽略不计.

方法区

方法区是被所有线程共享, 所有字段和方法字节码, 以及一些特殊方法, 如构造函数, 接口代码也在此定义, 简单说, 所有定义的方法的信息都保存在该区域, 该区域属于共享区间;

静态变量, 常量, 类信息(构造方法, 接口定义), 运行时的常量池存在方法区中, 但是实例变量存在堆内存中, 和方法区无关.

栈

栈内存, 主管程序的运行, 生命周期和线程同步

线程结束, 栈内存也就释放, 对于栈来说, 不存在垃圾回收问题

一旦线程结束, 栈就Over

存放: 8大基本类型 + 对象引用 + 实例的方法

对象的实例化过程视频解析:

https://www.bilibili.com/video/BV1HE411E7kY?from=search&seid=7388971973574996958

三种JVM

  • Sun公司: HotSpot
  • BEA: JRockit
  • IBM: J9VM

堆

Heap, 一个JVM只有一个堆内存, 堆内存的大小是可以调节的.

类加载器读取了类文件后, 一般会把什么东西放到堆中? 类, 方法, 常量, 变量

堆内存中还要细分为三个区域:

  • 新生区(伊甸园区)
  • 养老区
  • 永久区

新生区

类诞生, 成长的地方, 甚至死亡

伊甸园区, 所有的对象都是在 伊甸园 区

永久区

这个区域常驻内存的, 用来存放JDK自身携带的Class对象, Interface元数据, 存储的是Java运行时的一些环境或类信息

  • 1.6之前 : 永久代, 常量池实在方法区
  • 1.7: 永久代, 但是慢慢退化了, “去永久代”, 常量池在堆中
  • 1.8之后 : 无永久代, 常量池在元空间

默认情况下, JVM分配的总内存是电脑内存的1/4, 而初始化的内存为: 1/64

  1. JVM的体系结构
  2. 类加载器
  3. 双亲委派机制
  4. 沙箱安全机制
  5. Native
  6. PC寄存器
  7. 方法区
  8. 栈
  9. 三种JVM
  10. 堆
  11. 新生区
  12. 老年区
  13. 永久区
  14. 堆内存调优
  15. GC
    1. 常用算法
  16. JMM
  17. 总结
  1. 百度
  2. 思维导图
-------------本文结束感谢您的阅读-------------
可以请我喝杯奶茶吗