JVM入门学习
1、JVM的位置
2、JVM的体系结构
3、类加载器
1.虚拟机自带的加载器
2.启动类(根)加载器
3.扩展类加载器
4.应用程序(系统类)加载器
4、双亲委派机制

1 package java.lang; 2 3 public class String { 4 //双亲委派机制:安全 5 //1.APP-->EXC-->BOOT(最终执行) 6 //BOOT 7 //EXC 8 //APP 9 10 public String toString(){ 11 return "Hello"; 12 } 13 14 public static void main(String[] args) { 15 String s =new String(); 16 System.out.println(s.getClass().getClassLoader()); 17 s.toString(); 18 19 new Thread().start(); 20 21 } 22 /* 23 1.类加载器收到类加载器的请求! 24 2.将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载 BOOT 25 3.启动类加载器检查是否能够加载当前这个类,能加载就结束,使用当前加载器,否则,抛出异常,通知子类加载器进行加载 26 4.重复步骤3 27 Class Not Found 28 29 null : java调用不到~C、C++ 或者没有 30 Java = C++--; 去掉繁琐的东西,指针、内存管理 31 */ 32 33 }
5、沙箱安全机制
沙箱是一个限制程序运行的环境
基本组成
字节码校验器(bytecode verifier):确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。
类装载器(class loader):其中类装载器在3个方面对Java沙箱起作用
它防止恶意代码去干涉善意代码;//双亲委派机制
它守护了被信任的类库边界;
它将代码归入保护域,确定了代码可以进行哪些操作。
6、Native

1 package com.fang; 2 3 public class TestNative { 4 public static void main(String[] args) { 5 new Thread(()->{ 6 7 },"My thread name").start(); 8 } 9 10 // native : 凡是带了 native 关键字的,说明java的作用范围达不到了,回去调用底层C语言的库! 11 // 会进入本地方法栈 12 // 调用本地方法接口 JNI 13 // JNI 的作用:扩展Java的使用,融合不同的编程语言为Java所用!最初C、C++ 14 // Java诞生的时候 C、C++ 横行,想要立足,必须要有调用C、C++的程序 15 // 它在内存区域中专门开辟了一块标记区域:Native Method Stack,登记 native 方法 16 // 在最终执行的时候,加载本地方法库中的方法,通过JNI 17 18 // 一般驱动硬件时用,Java程序驱动打印机,管理系统,掌握即可,在企业级应用中较为少见 19 private native void start0(); 20 21 //调用其他接口: Scoket通信,WebService,http 22 }
具体做法是Native Method Stack中登记native方法,在(Execution Engine)执行引擎执行的时候加载 Native Libraies(本地库)。
7、PC寄存器
程序计数器:Program Couunter Register
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计
8、方法区
Method Area方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所定义的方法的信息都保存在该区域,此区域属于共享区间;
//1.6即以前在方法区,1.7以后在堆里
静态常量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是,实例变量存在堆内存中,和方法区无关。
static、final、Class模板、运行时的常量池
9、栈
数据结构
栈 :后进先出
队列:先进先出 FIFO
栈:栈内存,主管程序的运行,生命周期和线程同步;线程结束,栈内存也就释放。对于栈来说,不存在垃圾回收问题。一旦线程结束,栈就Over了!
存放:8大基本类型+引用类型+实例的方法
栈运行原理:栈帧
栈满:StackOverflowError
栈 + 堆 + 方法区 :交互关系
10、三种JVM
Sun公司 HotSpot “Java HotSpot(TM) 64-Bit Server VM (build 25.301-b09, mixed mode)”
BEA “JRockit”
IBM “J9VM”
11、堆
Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的
类加载器读取了类文件后,一般会把 类、方法、常量、变量~,保存我们所有引用类型的真实对象
堆内存还要细分为三个区域:
新生区(伊甸区) Young/New
养老区 Old
永久区 Perm(jdk1.8被元空间取代了)
GC垃圾回收,主要在新生区和养老区
假设内存满了,OOM,堆内存不够! java.lang.OutOfMemoryError:Java heap space
在JDK8以后,永久存储区改了个名字(元空间);
新生区:
类:诞生、成长的地方,甚至死亡
伊甸园区:所有对象都是在这里new出来的!
幸存者区(0,1)
老年区
真理:经过研究,99%的对象都是临时对象
永久区
这个区域常驻内存的。用来存放JDK自身携带的Class对象。Interface元数据,存储的是Java运行时的一些环境~,这个区域不存在垃圾回收!关闭虚拟机就会释放这个区域的内存~
一个启动类,加载了大量的第三方jar包。Tomcat部署了太多的应用,大量动态生成的反射类。不断地被加载。直到内存满,就会出现OOM
jdk1.6 之前:永久代,常量池是在方法区
jdk1.7 :永久代,但慢慢的退化了,“去永久代”,常量池在堆中
jdk1.8 之后: 无永久代,常量池在元空间
逻辑上存在,物理上不存在

1 package com.fang; 2 3 public class TestHeap { 4 public static void main(String[] args) { 5 //返回虚拟机试图使用的最大内存 6 long max = Runtime.getRuntime().maxMemory(); 7 //返回JVM的初始化总内存 8 long total = Runtime.getRuntime().totalMemory(); 9 10 System.out.println("max="+max+"字节\t"+(max/(double)1024/1024)+"MB"); 11 System.out.println("total="+max+"字节\t"+(total/(double)1024/1024)+"MB"); 12 13 //默认情况下:分配的总内存 是电脑内存的1/4,而初始化的内存:1/64 14 } 15 16 //OOM 17 // 1. 尝试扩大堆内存看结果 18 // 2.分析内存,看一下那个地方出现了问题(专业工具) 19 20 //-Xms1024m -Xmx1024m -XX:PrintGCDetails 21 }

package com.fang; import java.util.Random; //-Xms8m -Xmx8m -XX:+PrintGCDetails public class TestOOM { public static void main(String[] args) { String str = "HappyEveryDay"; while(true){ str += str + new Random().nextInt(100000000)+new Random().nextInt(999999999); } } }
在一个项目中,突然出现了OOM故障,那么该如何排除~研究为什么会出错
能够看到代码第几行出错:内存快照分析工具,MAT,Jprofiler
Dubug,一行行分析代码
MAT,Jprofiler作用
分析Dump内存文件,快速定位内存泄漏;
获得推中的数据
获得大的对象
。。。

1 package com.fang; 2 3 import java.util.ArrayList; 4 5 //Dump 6 7 // -Xms 设置初始化内存分配大小 默认1/64 8 // -Xmx 设置最大分配内存,默认 1/4 9 // -XX:PrintGCDetails //打印GC垃圾回收信息 10 // -XX:HeapDumpOnOutOfMemoryError //OOM Dump 11 // -Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError 12 public class TestJprofiles { 13 byte[] array = new byte[1*1024*1024]; //1m 14 15 public static void main(String[] args) { 16 ArrayList<TestJprofiles> list = new ArrayList<>(); 17 int count=0; 18 19 try{ 20 while(true){ 21 list.add(new TestJprofiles()); 22 count += 1; 23 } 24 }catch(Error e){ 25 System.out.println("count:"+count); 26 e.printStackTrace(); 27 } 28 } 29 }
12、GC
JVM在进行GC时,并不是对这三个区域统一回收,大部分时候,回收都是新生代~
新生代
幸存区(from,to)
老年区
GC两种类:轻GC(普通的GC)、重GC(全局GC)
GC题目:
JVM的内存模型和分区~详细到每个区放什么?
堆里面的分区有哪些?Eden、from、to、老年区、说说他们的特点
GC的算法有哪些?标记清楚法,标记整理(标记压缩),复制算法,引用计数法,怎么用的
轻GC和重GC分别在什么时候发生?
引用计数法
复制算法
幸存区的存在就是为了解决Eden区的空间碎片问题,from和to同理
优点:没有内存碎片~
缺点:浪费了内存空间~:多了一半空间永远是空的。假设对象100%存活(极端情况)OOM
复制算法最佳使用场景:对象存活度较低的时候;新生区~
标记清除算法
优点:不需要额外的空间!
缺点:两次扫描,严重浪费时间。会产生内存碎片
标记压缩
再优化
13、总结
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记压缩算法=标记清楚算法>复制算法
没有最好的算法,只有最合适的算法---->GC:分代收集算法
年轻代:
存活率低 复制算法
老年代:
区域大、存活率高 标记清楚(内存碎片不是太多)+标记压缩 混合实现
14、JMM
1、什么是JMM
Java Memory Model Java内存模型
2、作用:
缓存一致性协议,用于定义数据读写的规则(遵守找到这个规则)。
JMM定义了线程工作内存和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory)
解决共享对象可见性这个问题:volatile
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)