jvm总结 一时失言乱红尘 2023-06-19 07:19 5阅读 0赞 学习JVM的目的也很简单: * 能够知道JVM是什么,为我们干了什么,具体是怎么干的。能够理解到一些初学时不懂的东西 * 在面试的时候有谈资 * 能装逼 # 一、简单聊聊JVM # ## 1.1先来看看简单的Java程序 ## package cn.myframe.demo; public class D5 { public static void main(String[] args) { System.out.println("this is D5"); } } 我们在初学的时候肯定用过`javac`来编译`.java`文件代码,用过`java`命令来执行编译后生成的`.class`文件。 > java cn.myframe.demo.D5,不需要.class而且要在cn的上级目录执行命令 ## 1.2编译过程 ## `.java`文件是由Java源码编译器(上述所说的java.exe)来完成,流程图如下所示: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70] ![在这里插入图片描述][20191204144233881.png] ### 1.2.1编译时期-语法糖 ### > 语法糖可以看做是编译器实现的一些“小把戏”,这些“小把戏”可能会使得效率“大提升”。 最值得说明的就是泛型了,这个语法糖可以说我们是经常会使用到的! * 泛型只会在Java源码中存在,编译过后会被替换为原来的原生类型(Raw Type,也称为裸类型)了。这个过程也被称为:泛型擦除。 有了泛型这颗语法糖以后: * 代码更加简洁【不用强制转换】 * 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】 * 可读性和稳定性【在编写集合的时候,就限定了类型】 ## 1.3JVM实现跨平台 ## 至此,我们通过`java.exe`编译器编译我们的`.java`源代码文件生成出`.class`文件了! 这些`.class`文件很明显是不能直接运行的,它不像C语言(编译cpp后生成exe文件直接运行) 这些`.class`文件是交由JVM来解析运行! * JVM是运行在操作系统之上的,每个操作系统的指令是不同的,而JDK是区分操作系统的,只要你的本地系统装了JDK,这个JDK就是能够和当前系统兼容的。 * 而class字节码运行在JVM之上,所以不用关心class字节码是在哪个操作系统编译的,只要符合JVM规范,那么,这个字节码文件就是可运行的。 * 所以Java就做到了跨平台—>一次编译,到处运行! ## 1.4class文件和JVM的恩怨情仇 ## ### 1.4.1类的加载时机 ### 虚拟机规范则是严格规定了有且只有5种情况必须立即对类进行“初始化”(class文件加载到JVM中): * 创建类的实例(new 的方式)。访问某个类或接口的静态变量,或者对该静态变量赋值,调用类的静态方法 * 反射的方式 * 初始化某个类的子类,则其父类也会被初始化 * Java虚拟机启动时被标明为启动类的类,直接使用java.exe命令来运行某个主类(包含main方法的那个类) * 当使用JDK1.7的动态语言支持时(…) 所以说: * Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销 ### 1.4.2如何将类加载到jvm ### class文件是通过类的加载器装载到jvm中的! Java默认有三种类加载器: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70 1] 各个加载器的工作责任: * 1)Bootstrap ClassLoader:负责加载$JAVA\_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类 * 2)Extension ClassLoader:负责加载java平台中扩展功能的一些jar包,包括$JAVA\_HOME中jre/lib/\*.jar或-Djava.ext.dirs指定目录下的jar包 * 3)App ClassLoader:负责记载classpath中指定的jar包及目录中class > 1. src路径下的文件在编译后都会放到 WEB-INF/classes 路径下。默认classpath 就是指这里。 > 2. 用maven构建项目时,resources目录就是默认的classpath 工作过程: * 1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。 * 2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。 * 3、如果BootStrapClassLoader加载失败(例如在$JAVA\_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载; * 4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载 * 5、如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException 其实这就是所谓的双亲委派模型。简单来说:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。 好处: * 防止内存中出现多份同样的字节码(安全性角度) 特别说明: * 类加载器在成功加载某个类之后,会把得到的 `java.lang.Class`类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。 ### 1.4.2类加载详细过程 ### 加载器加载到jvm中,接下来其实又分了好几个步骤: * 加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象。 * 连接,连接又包含三块内容:验证、准备、初始化。 - 1)验证,文件格式、元数据、字节码、符号引用验证; \- 2)准备,为类的静态变量分配内存,并将其初始化为默认值; \- 3)解析,把类中的符号引用转换为直接引用 * 初始化,为类的静态变量赋予正确的初始值。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70 2] ### 1.4.3 JIT即时编辑器 ### > Just in time编译,也叫做运行时编译,不同于 C / C++ 语言直接被翻译成机器指令,javac把java的源文件翻译成了class文件,而class文件中全都是Java字节码。那么,JVM在加载了这些class文件以后,针对这些字节码,逐条取出,逐条执行,这种方法就是解释执行。 > > 还有一种,就是把这些Java字节码重新编译优化,生成机器码,让CPU直接执行。这样编出来的代码效率会更高。通常,我们不必把所有的Java方法都编译成机器码,只需要把调用最频繁,占据CPU时间最长的方法找出来将其编译成机器码。这种调用最频繁的Java方法就是我们常说的热点方法(Hotspot,说不定这个虚拟机的名字就是从这里来的)。 > > 这种在运行时按需编译的方式就是Just In Time。 一般我们可能会想:JVM在加载了这些class文件以后,针对这些字节码,逐条取出,逐条执行–>解析器解析。 但如果是这样的话,那就太慢了! 我们的JVM是这样实现的: * 就是把这些Java字节码重新编译优化,生成机器码,让CPU直接执行。这样编出来的代码效率会更高。 * 编译也是要花费时间的,我们一般对热点代码做编译,非热点代码直接解析就好了。 > 热点代码解释:一、多次调用的方法。二、多次执行的循环体 使用热点探测来检测是否为热点代码,热点探测有两种方式: * 采样 * 计数器 目前HotSpot使用的是计数器的方式,它为每个方法准备了两类计数器: * 方法调用计数器(Invocation Counter) * 回边计数器(Back EdgeCounter)。 * 在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70 3] ## 1.5类加载完以后JVM干了什么? ## 在类加载检查通过后,接下来虚拟机将为新生对象分配内存。 ### 1.5.1JVM的内存模型 ### 首先我们来了解一下JVM的内存模型的怎么样的: * 基于jdk1.8画的JVM的内存模型—>我画得比较细。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70 4] 简单看了一下内存模型,简单看看每个区域究竟存储的是什么(干的是什么): * 堆:存放对象实例,几乎所有的对象实例都在这里分配内存 * 虚拟机栈:虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息 * 本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务。 * 方法区:存储已被虚拟机加载的类元数据信息(元空间) * 程序计数器:当前线程所执行的字节码的行号指示器 ### 1.5.2例子中的流程 ### ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70 5] 我来宏观简述一下我们的例子中的工作流程: * 1、通过`java.exe`运行`Java3yTest.class`,随后被加载到JVM中,元空间存储着类的信息(包括类的名称、方法信息、字段信息…)。 * 2、然后JVM找到Java3yTest的主函数入口(main),为main函数创建栈帧,开始执行main函数 * 3、main函数的第一条命令是`Java3y java3y = new Java3y();`就是让JVM创建一个Java3y对象,但是这时候方法区中没有Java3y类的信息,所以JVM马上加载Java3y类,把Java3y类的类型信息放到方法区中(元空间) * 4、加载完Java3y类之后,Java虚拟机做的第一件事情就是在堆区中为一个新的Java3y实例分配内存, 然后调用构造函数初始化Java3y实例,这个Java3y实例持有着指向方法区的Java3y类的类型信息(其中包含有方法表,java动态绑定的底层实现)的引用 * 5、当使用`java3y.setName("Java3y");`的时候,JVM根据java3y引用找到Java3y对象,然后根据Java3y对象持有的引用定位到方法区中Java3y类的类型信息的方法表,获得`setName()`函数的字节码的地址 * 6、为`setName()`函数创建栈帧,开始运行`setName()`函数 从微观上其实还做了很多东西,正如上面所说的类加载过程(加载–>连接(验证,准备,解析)–>初始化),在类加载完之后jvm为其分配内存(分配内存中也做了非常多的事)。由于这些步骤并不是一步一步往下走,会有很多的“混沌bootstrap”的过程,所以很难描述清楚。 ## 1.6简单聊聊各种常量池 ## 在写这篇文章的时候,原本以为我对`String s = "aaa";`类似这些题目已经是不成问题了,直到我遇到了`String.intern()`这样的方法与诸如`String s1 = new String("1") + new String("2");`混合一起用的时候 * 我发现,我还是太年轻了。 public static void main(String[] args) { String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4); } 打印结果是 * jdk6 下`false false` * jdk7 下`false true` ### 1.6.1各个常量池的情况 ### 针对于jdk1.7之后: * 常量池位于堆中 * 运行时常量池位于堆中 * 字符串常量池位于堆中 常量池存储的是: * 字面量(Literal):文本字符串等---->用双引号引起来的字符串字面量都会进这里面 * 符号引用(Symbolic References) - 类和接口的全限定名(Full Qualified Name) \- 字段的名称和描述符(Descriptor) \- 方法的名称和描述符 > 常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放 > > –>来源:深入理解Java虚拟机 JVM高级特性与最佳实践(第二版) 现在我们的运行时常量池只是换了一个位置(原本来方法区,现在在堆中),但可以明确的是:类加载后,常量池中的数据会在运行时常量池中存放! > HotSpot VM里,记录interned string的一个全局表叫做StringTable,它本质上就是个HashSet。注意它只存储对java.lang.String实例的引用,而不存储String对象的内容 字符串常量池只存储引用,不存储内容! ## 1.7GC垃圾回收 ## 可以说GC垃圾回收是JVM中一个非常重要的知识点,应该非常详细去讲解的。但在我学习的途中,我已经发现了有很好的文章去讲解垃圾回收的了。 所以,这里我只简单介绍一下垃圾回收的东西,详细的可以到下面的面试题中查阅和最后给出相关的资料阅 读吧~ ### 1.7.1JVM垃圾回收简单介绍 ### 在C++中,我们知道创建出的对象是需要手动去delete掉的。我们Java程序运行在JVM中,JVM可以帮我们“自动”回收不需要的对象,对我们来说是十分方便的。 虽然说“自动”回收了我们不需要的对象,但如果我们想变强,就要变秃…不对,就要去了解一下它究竟是怎么干的,理论的知识有哪些。 首先,JVM回收的是垃圾,垃圾就是我们程序中已经是不需要的了。垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”。判断哪些对象“死去”常用有两种方式: * 引用计数法–>这种难以解决对象之间的循环引用的问题 * 可达性分析算法–>主流的JVM采用的是这种方式 现在已经可以判断哪些对象已经“死去”了,我们现在要对这些“死去”的对象进行回收,回收也有好几种算法: * 标记-清除算法 * 复制算法 * 标记-整理算法 * 分代收集算法 (这些算法详情可看下面的面试题内容)~ 无论是可达性分析算法,还是垃圾回收算法,JVM使用的都是准确式GC。JVM是使用一组称为OopMap的数据结构,来存储所有的对象引用(这样就不用遍历整个内存去查找了,时间换空间)。 并且不会将所有的指令都生成OopMap,只会在安全点上生成OopMap,在安全区域上开始GC。 * 在OopMap的协助下,HotSpot可以快速且准确地完成GC Roots枚举(可达性分析)。 上面所讲的垃圾收集算法只能算是方法论,落地实现的是垃圾收集器: * Serial收集器 * ParNew收集器 * Parallel Scavenge收集器 * Serial Old收集器 * Parallel Old收集器 * CMS收集器 * G1收集器 上面这些收集器大部分是可以互相组合使用的 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70 6] ## 1.8JVM参数与调优 ## 很多做过JavaWeb项目(ssh/ssm)这样的同学可能都会遇到过OutOfMemory这样的错误。一般解决起来也很方便,在启动的时候加个参数就行了。 上面也说了很多关于JVM的东西—>JVM对内存的划分啊,JVM各种的垃圾收集器啊。 内存的分配的大小啊,使用哪个收集器啊,这些都可以由我们根据需求,现实情况来指定的,这里就不详细说了,等真正用到的时候才回来填坑吧~~~~ 参考资料: * [http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html][http_www.cnblogs.com_redcreen_archive_2011_05_04_2037057.html]—JVM系列三:JVM参数设置、分析 # 二、JVM面试题 # 拿些常见的JVM面试题来做做,加深一下理解和查缺补漏: * 1、详细jvm内存模型 * 2、讲讲什么情况下回出现内存溢出,内存泄漏? * 3、说说Java线程栈 * 4、JVM 年轻代到年老代的晋升过程的判断条件是什么呢? * 5、JVM 出现 fullGC 很频繁,怎么去线上排查问题? * 6、类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式? * 7、类的实例化顺序 * 8、JVM垃圾回收机制,何时触发MinorGC等操作 * 9、JVM 中一次完整的 GC 流程(从 ygc 到 fgc)是怎样的 * 10、各种回收器,各自优缺点,重点CMS、G1 * 11、各种回收算法 * 12、OOM错误,stackoverflow错误,permgen space错误 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70]: https://img-blog.csdnimg.cn/20191204144042720.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI=,size_16,color_FFFFFF,t_70 [20191204144233881.png]: https://img-blog.csdnimg.cn/20191204144233881.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70 1]: https://img-blog.csdnimg.cn/20191204150029534.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI=,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70 2]: https://img-blog.csdnimg.cn/20191204151334495.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI=,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70 3]: https://img-blog.csdnimg.cn/20191204151750212.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI=,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70 4]: https://img-blog.csdnimg.cn/20191204154510333.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI=,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70 5]: https://img-blog.csdnimg.cn/20191204154631261.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI=,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI_size_16_color_FFFFFF_t_70 6]: https://img-blog.csdnimg.cn/20191204170108729.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvd2JpbjIwMTI=,size_16,color_FFFFFF,t_70 [http_www.cnblogs.com_redcreen_archive_2011_05_04_2037057.html]: http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
相关 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 赞/ 6 阅读
相关 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 阅读
还没有评论,来说两句吧...