JVM总结 Dear 丶 2023-07-12 14:08 11阅读 0赞 ## 常见面试题 ## * 请你谈谈你对JVM的理解? * java8虚拟机和之前的变化和更新 * 什么是OOM?什么是栈溢出?怎么分析? * JVM常见的调优参数有哪些? * 内存快照如何抓取,怎么分析Dump文件? * 谈谈JVM中,类加载器你的认识 ## 常见名词 ## * JVM的位置 * JVM的体系结构 * 类加载器 * 双亲委派机制 * 沙箱安全机制 * Native * PC寄存器 * 方法区 * 栈 * 堆 * 三种JVM * 新生区、老年区、永久区 * 堆内存调优 * GC常用算法 * JMM ## JVM的位置 ## * JVM运行在操作系统之上,相当于一个软件 * JVM是使用C/C++语言写的 ## JVM结构 ## 简单示例图: ![JVM示意图][JVM] * JVM调优几乎都是在调堆里面的东西 * lombok插件,实际上是在执行引擎上动态加载了setter、getter方法 ## 类加载器 ## > 虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。——《深入理解java虚拟机》 当一个对象创建之后,它的引用放在栈里面,而对象本身放在堆里面 public class App { private String name; public static void main(String[] args) { App app = new App(); app.name = "小明"; App app1 = new App(); app1.name = "小红"; System.out.println(app == app1); // false Class<? extends App> clazz = app.getClass(); Class<? extends App> clazz1 = app1.getClass(); System.out.println(clazz == clazz1); // true System.out.println(clazz == App.class); // true ClassLoader cl = App.class.getClassLoader(); System.out.println(cl); // sun.misc.Launcher$AppClassLoader@18b4aac2 System.out.println(cl.getParent()); // sun.misc.Launcher$ExtClassLoader@76fb509a System.out.println(cl.getParent().getParent()); // null // 这里出现null是因为由于JVM底层是C++语言实现的,java程序调用不到 } } public static void main(String[] args) { System.out.println(ClassLoader.getSystemClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2 } 简单示例图: ![类加载器][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70] 类加载器的种类: * 启动类加载器(Bootstrap ClassLoader),或者叫根加载器。这个类加载器主要是去加载你在本机配置的环境变量 Java\_Home/jre/lib 目录下的核心API,如rt.jar * 扩展类加载器(Extension ClassLoader)。这个加载器负责加载 Java\_Home/jre/lib/ext 目录下的所有jar包。 * 应用程序类加载器(Application ClassLoader)。这个加载器加载的是你的项目工程的ClassPath目录下的类库。如果用户没有自定义自己的类加载器,这个就是程序默认的类加载器。 > 面试题:[Class.forName 和 ClassLoader 有什么区别?][Class.forName _ ClassLoader] > 简单总结: > > * Class.forName() 方法实际上也是调用的 CLassLoader 来实现的。 > * Class.forName 加载类是将类进了初始化,而 ClassLoader 的 loadClass 并没有对类进行初始化,只是把类加载到了虚拟机中。 ## 双亲委派机制 ## 当一个类在加载的时候,都会先委派它的父加载器去加载,这样一层层的向上委派,直到最顶层的启动类加载器。如果顶层无法加载(即找不到对应的类),就会一层层的向下查找,直到找到为止。这就是类的双亲委派机制。 目的:保证安全,防止程序员自定义与源码中相同的类。 package java.lang; /** * 错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为: * public static void main(String[] args) * 否则 JavaFX 应用程序类必须扩展javafx.application.Application */ public class String { public String toString() { return "Hello"; } public static void main(String[] args) { String str = new String(); str.toString(); } } 因为双亲委派机制的存在,去加载我们自己定义的“java.lang.String”类的时候,会最终委派到顶层的启动类加载器,然后找到了rt.jar包下的“java.lang.String”。找到之后,就直接加载rt.jar包的String类(也就是我们经常使用的那个字符串类),不再去向下查找,也就加载不了我们自定义的String类了。由于,rt.jar包下的String类中确实没有main方法,所以才会有以上的报错信息。 > 参考文章:https://mp.weixin.qq.com/s/gt9IjakAxk4ahzLpsEIBwg ## 沙箱安全机制 ## > 参考文章:https://blog.csdn.net/qq\_30336433/article/details/83268945 ## Native ## 凡是带有native关键字的,说明java的作用域达不到了,会去调用底层C语言的库。 native关键字修饰的方法会进入本地方法栈,然后调用本地方法,本地接口(JNI,即java native interface) JNI的作用:扩展java的使用,融合不同的编程语言为java所用! 历史原因:java诞生初期,`C、C++`横行,要想立足,必须有调用`C、C++`的程序,于是java在内存中开辟了“本地方法栈”,用于登记native方法,最终执行的时候通过JNI加载本地方法库中的方法。native方法可以驱动打印机,操作电脑上的硬件等等,但是native关键字的使用在企业级应用中较为少见。 例如:线程类中的`start0()`方法,就是native方法 ![线程中的JNI][JNI] 以及我们常用的`currentTimeMillis()`方法 public static native long currentTimeMillis(); ## PC寄存器 ## 即程序计数器(Program Counter Register) 每一个线程都有一个程序计数器,是线程私有的,相当于一个指针,指向方法区中方法的字节码,在执行引擎读取下一条指令。是一个非常小的内存空间,几乎可以忽略不计。 ## 方法区 ## 方法区被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享空间。 静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法无关(static、final、Class) ## 栈 ## 相关问题:为什么main函数先执行,最后结束? 因为main函数最先被压入栈中,最后弹出 栈内存主管程序的运行,生命周期和线程同步,线程结束,栈内存就会释放,不存在垃圾回收机制。一旦线程结束,栈就结束 栈存放的内容: * 8大基本类型 * 对象引用 * 实例方法 栈运行原理: ![栈][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 1] 程序正在执行的方法,一定在栈的顶部 如果递归次数太多,会发生栈溢出错误: /** * Exception in thread "main" java.lang.StackOverflowError */ public class Application { public static void main(String[] args) { new Application().a(); } public void a() { b(); } public void b() { a(); } } #### 对象创建的过程 #### 示例代码: class Pet { String name; public Pet(String name) { this.name = name; } public void say() { System.out.println("hello, world"); } } public class Application { public static void main(String[] args) { Pet pet = new Pet("小花"); Pet pet1 = new Pet("小丽"); } } 1、首先从main函数开始执行,main函数首先入栈,加载Application类 ![第一步][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 2] 2、当创建第一个对象Pet(即遇到new关键字)的实例的时候,加载Pet类 ![第二步][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 3] 3、创建pet对象,在栈中储存对象的引用、在堆中储存对象实例 ![第三步][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 4] ## 堆 ## 三种JVM * Sun公司的HotPot:普遍使用的JVM版本 * BEA 的JRockit * IBM公司的J9VM 我们所学习的堆的优化使用的是HotPot版本的JVM。 Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的。类加载器读取了类文件后,一般把类的实例放在堆中,即堆保存的是引用类型的真实对象 堆分为三个区域 * 新生区 * 伊甸园Eden Space * 幸存0区 * 幸存1区 * 养老区 * 永久存储区 垃圾回收分为两种:轻GC、重GC,垃圾回收一般在伊甸园区和养老区 如果堆内存满的话,会出现OOM错误,如下: /** * java.lang.OutOfMemoryError:Java heap space */ public class Application { public static void main(String[] args) { String str = "hello"; while (true) { str += "world"; } } } 出现OOM的几种情况 * 一个启动类加载了大量第三方jar包 * Tomcat部署了太多应用 * 大量动态生成反射类 #### 永久区 #### 这个区域常驻内存,用来存放JDK自身携带的Class对象、interface元数据,存储的是java运行时的一些环境或类信息,这个区域不存在垃圾回收,关闭JVM虚拟机才会释放这个区域的内存。JDK8以后,永久储存区改了个名字,叫做“元空间” * JDK1.6之前:永久代,常量池在方法区中 * JDK1.7 :永久代慢慢退化,常量池在堆中 * JDK1.8之后:无永久代,常量池在元空间中(方法区也在元空间中) 我们可以使用`Runtime.getRuntime()`方法拿到我们当前的运行环境 public class Application { public static void main(String[] args) { // 返回虚拟机视图使用的最大内存 long max = Runtime.getRuntime().maxMemory(); // 返回JVM的初始化总内存 long total = Runtime.getRuntime().totalMemory(); System.out.println("max:" + max / 1024 / 1024 + "MB"); System.out.println("total:" + total / 1024 / 1024 + "MB"); // max:2706MB // total:184MB } } 使用参数,可以调节上面的内存数量: // -Xms1024m -Xmx1024m -XX:+PrintGCDetails // -Xms 表示设置初始化内存大小,默认为计算机内存的1/64 // -Xmx 表示设置最大分配内存,默认为计算机内存的1/4 // -XX:+PrintGCDetails表示打印GC垃圾回收信息 运行结果: max:981MB total:981MB Heap PSYoungGen total 305664K, used 20971K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000) eden space 262144K, 8% used [0x00000000eab00000,0x00000000ebf7afb8,0x00000000fab00000) from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000) to space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000) ParOldGen total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000) object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000) Metaspace used 3499K, capacity 4496K, committed 4864K, reserved 1056768K class space used 383K, capacity 388K, committed 512K, reserved 1048576K > 面试题:在写程序的过程中,遇见过OOM吗?怎么解决的? > 1、尝试扩大堆内存,看效果 > 2、使用专业工具分析内存,看一下那个地方出现了问题 #### 使用jprofiler #### 使用方法: * 官网:https://www.ej-technologies.com/products/jprofiler/overview.html * IDEA插件名称jprofiler * 安装完插件之后要在`setting-->tools-->jprofiler`处设置`jprofiler.exe`的路径 运行程序(参数:`-Xms100m -Xmx800m -XX:+HeapDumpOnOutOfMemoryError`): > \-XX:+HeapDumpOnOutOfMemoryError 表示生成Dump文件记录OOM的信息 package com.qianyu.demo; import java.util.*; /** * java.lang.OutOfMemoryError: Java heap space * Dumping heap to java_pid16576.hprof ... * Heap dump file created [685667719 bytes in 3.876 secs] * 程序执行了:164 * java.lang.OutOfMemoryError: Java heap space * at com.qianyu.demo.Application.<init>(Application.java:7) * at com.qianyu.demo.Application.main(Application.java:15) */ public class Application { int[] arr = new int[1024 * 1024]; public static void main(String[] args) { ArrayList<Application> list = new ArrayList<>(); int count = 0; try { while (true) { count++; list.add(new Application()); } } catch (Error e) { System.out.println("程序执行了:" + count); e.printStackTrace(); } } } 打开项目根目录之后,会出现以hprof结尾的文件,双击即可打开 ![文件位置][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 5] 查看大对象,基本可以判断是哪一个对象引起的OOM ![查看大对象][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 6] 定位到某一行: ![定位][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 7] ## GC ## GC算法总结: > 参考文章:https://mp.weixin.qq.com/s/x83h2MKCxsHJDpoWbVtnig [JVM]: https://img-blog.csdnimg.cn/20200308193517941.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ==,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70]: https://img-blog.csdnimg.cn/20200308193734381.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ==,size_16,color_FFFFFF,t_70 [Class.forName _ ClassLoader]: https://mp.weixin.qq.com/s?__biz=Mzg2MjEwMjI1Mg==&mid=2247491888&idx=3&sn=e083c09de6ac36228e9a7b2e221b5f00&chksm=ce0e56b3f979dfa56af45c2fc64170bb5fb96aaec858cadd1ae4b096f699d94730b574f2cf26&scene=126&sessionid=1582094915&key=844c91e610caa57ac03b1b25b4c30a05c4472cd126c6d426335525ebb833fdd8770da82a2b521ecc9f8fac98e77fc0bdf7826ca0eb9b88f1b795028370308345c7b30b3a3ec2374217646cfe18394a4a&ascene=1&uin=MjE4MDM1NDU3NQ%3D%3D&devicetype=Windows+10&version=62080079&lang=zh_CN&exportkey=AZFNIYGAG3WuNjNUqNnVIkE%3D&pass_ticket=lzEKvogYfmiCivIDI99aI29f%2BHrSZ%2FVB3jxkbkoZzFPzqTZ7pCjkySptlukS7MFR [JNI]: https://img-blog.csdnimg.cn/20200308200114148.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ==,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 1]: https://img-blog.csdnimg.cn/2020030820042997.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ==,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 2]: https://img-blog.csdnimg.cn/20200308200629823.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ==,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 3]: https://img-blog.csdnimg.cn/2020030820070045.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ==,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 4]: https://img-blog.csdnimg.cn/20200308200723284.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ==,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 5]: https://img-blog.csdnimg.cn/2020030820092389.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ==,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 6]: https://img-blog.csdnimg.cn/20200308200945438.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ==,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ_size_16_color_FFFFFF_t_70 7]: https://img-blog.csdnimg.cn/20200308201016598.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpMzQ1NTI3NzkyNQ==,size_16,color_FFFFFF,t_70
相关 jvm总结 *目录** 1、什么情况下会发生堆、栈内存溢出。 2、JVM的内存结构,Eden和Survivor比例。 3、JVM内存为什么要分成新生代,老年代,持久代。新生代中... 刺骨的言语ヽ痛彻心扉/ 2024年04月17日 05:48/ 0 赞/ 64 阅读
相关 JVM总结 目录 一,Java虚拟机 1.概念 2.作用 3.运行流程 二,运行时数据区域 理解线程私有 1.Java虚拟机栈(线程私有) 作用 栈帧的组成 2.本地 阳光穿透心脏的1/2处/ 2023年09月30日 13:13/ 0 赞/ 9 阅读
相关 JVM总结 常见面试题 请你谈谈你对JVM的理解? java8虚拟机和之前的变化和更新 什么是OOM?什么是栈溢出?怎么分析? JVM常见的调优参数有哪些? Dear 丶/ 2023年07月12日 14:08/ 0 赞/ 12 阅读
相关 jvm总结 学习JVM的目的也很简单: 能够知道JVM是什么,为我们干了什么,具体是怎么干的。能够理解到一些初学时不懂的东西 在面试的时候有谈资 能装逼 一、简单聊 一时失言乱红尘/ 2023年06月19日 07:19/ 0 赞/ 5 阅读
相关 JVM总结 目录 1.什么是JVM 2.JVM基本结构 3.运行时数据区 4 hotspot方法区的实现 5 堆的结构 6 为何新生代要设置两个survivor区 7 对象访 桃扇骨/ 2023年01月01日 06:51/ 0 赞/ 145 阅读
相关 jvm总结 ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQy 旧城等待,/ 2022年12月27日 14:29/ 0 赞/ 183 阅读
相关 JVM 总结 [初期预调优][Link 1] JVM总结: 一、理论 一、JVM内存模型: 二、JVM垃圾回收机制(从新生代和老年代的角度分析): 三、JVM调优经历:[调优实战][ 向右看齐/ 2022年10月27日 12:17/ 0 赞/ 172 阅读
相关 JVM总结 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_Q1NETiBA5LiA54K55Y2a5a6i_ r囧r小猫/ 2022年09月08日 10:26/ 0 赞/ 158 阅读
相关 jvm总结 一、JVM结构 JVM是可运行Java代码的假想计算机 ![70][] 1.1 类加载器 1.2 执行引擎:执行包在装载类的方法中的指令,也就是方法,clas 矫情吗;*/ 2022年05月20日 05:13/ 0 赞/ 162 阅读
还没有评论,来说两句吧...