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. 三种JVM
  9. 新生区
  10. 老年区
  11. 永久区
  12. 堆内存调优
  13. GC
    1. 常用算法
  14. JMM
  15. 总结
  1. 百度
  2. 思维导图
-------------本文结束感谢您的阅读-------------
可以请我喝杯奶茶吗