1.JVM位置
Jdk包含jre+开发工具,jre包括了jvm+java标准类库。简单来说,jvm保证了Java的跨平台性,只要在不同的操作系统安装了jvm(Java虚拟机)就可以将java字节码(.java)编译成相同的字节码文件(.class)。jre(java runtime environment)负责运行java已编译程序所必须的软件环境,不能将java源代码编译成字节码文件。jdk可以创建、编译、运行java程序。
2.JVM的体系结构
3.类加载器
1.虚拟机自带的加载器
2.启动类(根)加载器Boot
3.扩展类加载器ExtClassLoader
4.应用程序加载器AppClassLoader
4.双亲委派机制
1.类加载器收到类加载的请求
2.将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
3.启动类加载器检查是否能够加载这个类,能加载就结束、否则抛出异常,通知子加载器进行加载
4.重复步骤3
5.沙箱安全机制
沙箱是一个限制程序运行的环境,沙箱机制就是将Java代码限定在虚拟机特定的运行范围中,并且严格限制代码对本地资源的访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏,沙箱主要限制系统资源(CPU、内存、文件系统、网络)访问,不同级别的沙箱对这些资源访问的权限也可以不一样。
现在的安全模型就是,jvm给不同的代码分配不同的域,该代码就拥有这个域所拥有的对于本地资源的全部权限。(域类似于角色)
沙箱安全机制的组成部分:
1.字节码校验器:它保证java代码符合java语言规范,核心类由于已经校验过了封装好的,字节码不会校验核心类
2.类加载器,类加载器是利用了双亲委派机制,它保证了好的代码不会被坏的代码污染 它定义了被信任类库的边界 为代码归入域(类似于分配角色,分配权限)
6.Native
1.native:凡是带了native关键字的,说明如果Java的作用范围达不到了,会去调用底层C语言的库!
2.进入本地方法栈
3.调用本地方法本地接口 JNI
4.JNI作用:扩展Java的使用,融合不同的编程语言为java所用!(java诞生的时候C、C++横行,想要立足,必须要有调用C、C++的程序)
5.它在内存区域中专门开辟了一块标记区域:Native Method Stack中登记native方法
6.在最终执行的时候,加载本地方法库中的方法通过JNI
7.PC寄存器(程序计数器)
每个线程都有一个程序计数器,时线程私有的,就是一个指针,指向方法区中的方法字节码
8.方法区
方法区是被所有线程共享的,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,该区域属于共享区间。
静态变量、常量、类信息(构造方法、接口定义)、运行时常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关。
9.栈
- 栈内存,主管程序的运行,生命周期和线程同步
- 线程结束,栈内存释放,对于栈来说,不存在垃圾回收的问题
- 当前正在执行的方法永远在栈顶,方法执行完了就会被弹出栈
- 栈里存放着:8大基本类型+对象引用+实例的方法
栈运行原理:栈帧
[图片上传中...(image.png-72806-1653390101960-0)]
栈溢出:StackOverflowError
栈+堆+方法区的交互关系
10.三种JVM
1.HotSpot
2.BEA JRockit
3.IBM J9 VM
11.堆
一个JVM只有一个堆内存,堆内存的大小是可以调节的。
堆内存中还要细分为三个区域:
- 新生区(伊甸园区)
- 养老区
- 永久区
GC垃圾回收机制主要发生在伊甸园区和养老区,假设内存满了就会报OOM(堆内存满了)
垃圾回收大致过程:
1.所有的对象在伊甸园区诞生成长,之后会经历一次轻GC
2.幸存下来的对象进入幸存区
3.当幸存区满了之后,对象转移到养老区,并进行重GC
在JDK8以后,永久存储区改为元空间
12.新生区,老年区、永久区
12.1新生区
-类诞生和成长的地方
-伊甸园,所有的对象都是在伊甸园中new出来的
-幸存区(0,1)
真理:经过研究,99%的对象都是临时对象
12.3永久区
这个区域是常驻内存的,用来存放JDK携带的Class对象,Interface元数据,存储的是Java运行时的一些环节或类信息,这个区域不存在垃圾回收,关闭虚拟机就会释放这个区域的内存。
- jdk1.6之前:永久代,常量池在方法区中
- jdk1.7:永久代,但是慢慢退化了,去永久代,常量池在堆中
-
jdk1.8之后:无永久代,常量池在元空间
注:这里的云空间只是逻辑上的存在,物理是不存在
13.堆内存调优
OOM:
1.尝试扩大堆内存看结果(-Xms1024m -Xmx1024m -XX:+PrintGCDetails)
2.分析内存,看一下那个地方出现了问题(MAT,Jprofiler)
package com.jvm;
import java.util.Random;
/**
* @author:wangjie
* @create:2022-05-25 18:15
* @Description:模拟堆满了java.lang.OutOfMemoryError: Java heap space
*/
public class OOM {
// -Xms8m -Xmx8m -XX:+PrintGCDetails 设置运行时初始内存分配大小默认1/64,最大分配内存大小1/4,打印GC垃圾回收机制详细信息
//-Xms8m -Xmx8m -XX:+HeadDumpOnOutOfMemoryError 如果发生OutOfMemoryError错误就将信息dump下来分析
public static void main(String[] args) {
String str = "wangjie";
while (true){
str+=str+new Random().nextInt(1111111111)+new Random(222222222);
}
}
}
14.GC(垃圾回收机制)
JVM在进行GC时,并不是对这三个区域统一回收,大部分时候,回收都是新生代。
- 新生代
- 幸存区(from,to)
- 老年区
GC两种类:轻GC、重GC
GC题目: - JVM的内存模型和分区 详细到每个区放什么?
- 堆里面的分区有哪些?Eden、from、to,老年区,说说他们的特点
- GC的算法有哪些?标记清除法、标记压缩、复制算法、引用计数器
14.1常用算法
14.1.1引用计数法
14.2复制算法
- 好处:没有多余的内存碎片
- 坏处:浪费了一个内存空间(to区总是空的)
复制算法最佳使用场景:对象存活时间较低
14.3标记清除法
- 好处:不需要额外空间
- 坏处:两次扫描,严重浪费时间,会产生内存碎片
标记清除压缩算法
15.JMM
Java 内存模型
作用:缓冲一致性协议,用于定义数据读写的规则
JVM定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存。
解决共享对象可见性:validate
16.对象创建过程
1.类加载检查
检查这条new指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否被加载过、解析、和初始化过,如果没有,就要先执行类加载过程。
2.分配内存
在类加载检查通过之后,虚拟机为新生对象分配内存,分配内存的方式有两种,指针碰撞(规整情况)和空闲列表(不规整情况),选择那种分配方式由java堆的内存是否规整决定,而java堆是否规整又由垃圾回收算法决定。
3.初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
4.设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
5.执行init方法
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。