pppenger.github.io

目录

编译/执行编译过程编译时期-语法糖JVM实现跨平台class(字节码)文件和JVM什么是类的加载一、类的加载时机二、如何将类加载到jvmJava默认有三种类加载器三、类加载详细过程四、JIT即时编辑器Java虚拟机图示jvm内存空间结构模型一、Java堆(Heap)二、JVM栈(JVM Stacks)(堆栈)三、本地方法栈(Native Method Stacks)四、程序计数器(Program Counter Register)五、方法区(Method Area):元空间(MetaSpace)和永久代(PermGen)的区别:六、堆和栈的区别七、存储例子八、intern()的使用intern解析题目JVM垃圾回收(GC)一:GC的分类:1、年轻代(1/3堆空间):2、对象如何晋升到老年代3、老年代4、触发Full GC的条件二、判断一个对象是否可被回收1、引用计数算法2、可达性分析算法finalize()方法三、引用类型1. 强引用2. 软引用3. 弱引用4. 虚引用四、垃圾回收(GC)算法1、标记-清除算法2、复制算法3、标记-整理算法4、分代收集算法五、垃圾收集器1、Serial收集器2、ParNew收集器3、Parallel Scavenge收集器4、Serial Old收集器5、Parallel Old收集器6、CMS收集器7、G1收集器JVM参数与调优JVM面试题1详细jvm内存结构2讲讲什么情况下回出现内存溢出,内存泄漏?3说说线程栈4JVM 年轻代到年老代的晋升过程的判断条件是什么呢?5JVM 出现 fullGC 很频繁,怎么去线上排查问题6类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?7类的实例化顺序8JVM垃圾回收机制,何时触发MinorGC等操作9JVM 中一次完整的 GC 流程(从 ygc 到 fgc)是怎样的10各种回收算法11各种回收器,各自优缺点,重点CMS、G112stackoverflow错误,permgen space错误

 

大纲:

 

 

 

参考链接:

https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20%E8%99%9A%E6%8B%9F%E6%9C%BA.md

链接:https://www.zhihu.com/question/36204510/answer/667969441

https://zhuanlan.zhihu.com/p/25511795

 

编译/执行

编译过程

.java文件是由Java源码编译器(上述所说的javac.exe)来完成,流程图如下所示:

 

imgimg

 

Java源码编译由以下三个过程组成:

 

imgimg

 

编译时期-语法糖

语法糖可以看做是编译器实现的一些“小把戏”,这些“小把戏”可能会使得效率“大提升”。

最值得说明的就是泛型了,这个语法糖可以说我们是经常会使用到的!

有了泛型这颗语法糖以后:

了解泛型更多的知识:

##

 

JVM实现跨平台

至此,我们通过javac.exe编译器编译我们的.java源代码文件生成出.class文件了!

 

imgimg

 

这些.class文件很明显是不能直接运行的,它不像C语言(编译cpp后生成exe文件直接运行)

这些.class文件是交由JVM来解析运行(JVM解析,转换成特定平台的执行指令)

 

imgimg

 

 

 

class(字节码)文件和JVM

什么是类的加载

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

wps5F9E.tmp

类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

 

一、类的加载时机

现在我们例子中生成的两个.class文件都会直接被加载到JVM中吗??

虚拟机规范则是严格规定了有且只有5种情况必须立即对类进行“初始化”(class文件加载到JVM中):

所以说:

二、如何将类加载到jvm

class文件是通过类的加载器装载到jvm中的!

Java默认有三种类加载器

img

各个加载器的工作责任:

工作过程:

其实这就是所谓的双亲委派模型。简单来说:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上

好处:

特别说明:

 

三、类加载详细过程

加载器加载到jvm中,接下来其实又分了五个步骤

- 1)验证,文件格式、元数据、字节码、符号引用验证; - 2)准备,为类的静态变量分配内存,并将其初始化为默认值; - 3)解析,把类中的符号引用转换为直接引用

img

 

四、JIT即时编辑器

一般我们可能会想:JVM在加载了这些class文件以后,针对这些字节码,逐条取出,逐条执行-->解析器解析。

但如果是这样的话,那就太慢了!

我们的JVM是这样实现的:

热点代码解释:一、多次调用的方法。二、多次执行的循环体

使用热点探测来检测是否为热点代码,热点探测有两种方式:

目前HotSpot使用的是计数器的方式,它为每个方法准备了两类计数器:

img

 

详情参考:

扩展阅读:

##

 

Java虚拟机图示

img

Class Loader :依据特定格式,加载class文件到内存 Execution Engine:对命令进行解析 Native Interface:融合不同开发语言的原生库为Java所用 Runtime Data Area:jvm内存空间结构模型

 

jvm内存空间结构模型

img

jdk 1.8

img

方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈和程序计数器是运行是线程私有的内存区域。

 

 

一、Java堆(Heap)

是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域存放对象实例,几乎所有的对象实例都在这里分配内存。static 成员变量在 Class 对象中。

是垃圾收集的主要区域("GC 堆")。

现代的垃圾收集器基本都是采用分代收集算法,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法。可以将堆分成两块:

堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。

可以通过 -Xms 和 -Xmx 这两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置堆能达到的最大值。

常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = "java"这种申明方式;当然也可扩充,执行器产生的常量也会放入常量池,故认为常量池是JVM的一块特殊的内存空间。

二、JVM栈(JVM Stacks)(堆栈)

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

img

-Xss:规定了每个线程虚拟机栈(堆栈)的大小

 

三、本地方法栈(Native Method Stacks)

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。

 

四、程序计数器(Program Counter Register)

,程序计数器(Program Counter Register)是一块较小的内存空间,它的作用是记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)。

 

五、方法区(Method Area):

存储已被虚拟机加载的类元数据信息(元空间)、静态变量等数据,方法区只是一种jvm的规范,在jdk8之后,原先在方法区里的字符串常量池和静态成员变量已被移动到java堆中,在jdk8之后,使用元空间替代了永久代,存储类和类加载器的元数据信息。

 

元空间(MetaSpace)和永久代(PermGen)的区别:

元空间使用本地(服务器)内存,永久代使用的是jvm的内存

好处:

1、OutOfMemoryError:PermGen Space异常不复存在

2、字符串常量池存在永久代中,容易出现性能问题和内存溢出

3、类和方法的信息大小难以确定,给永久代的大小指定带来困难

4、永久代为GC带来不必要的复杂性

5、方便HotSpot与其他JVM如Jrockit的集成

 

六、堆和栈的区别

管理方式:栈自动释放,堆需要GC

空间大小:栈比堆小

碎片相关:栈产生的碎片远小于堆

分配方式:栈支持静态和动态分配,而堆仅支持动态分配

效率:栈的效率比堆高

 

七、存储例子

img

img

元空间:保存类,以及类的方法 java堆:保存类的实例以及String实例“test” main线程:分配对应的虚拟机栈,本地栈以及程序计数器,栈里存有string类型的引用参数,保存对应于堆中“test”字符串对象的地址引用,还存有本地变量hw,保存堆中“helloworld”这个对象的地址引用,还有局部变量a,保存1这个值,以及系统自带的lineNo(行号),记录代码的执行,方便对程序进行追踪

 

八、intern()的使用

JDK6:调用intern方法时,如果字符串常量池中有该字符串对象,则返回池中该字符串的引用,否则,将此字符串对象添加到字符串常量池中,并返回改字符串对象的引用

JDK6+:调用intern方法时如果字符串常量池中有该字符串对象,则返回池中该字符串的引用,否则,如果堆中已经存在该对象,则将堆中此对象的引用添加到字符串池中,并且返回该引用;如果堆中不存在,则在池中创建该字符串的引用并返回其引用

 

intern解析题目

本来打算写注释的方式来解释的,但好像挺难说清楚的。我还是画图吧...

第一句:String s = new String("1");

img

 

第二句:s.intern();发现字符串常量池中已经存在"1"字符串对象,直接返回字符串常量池中对堆的引用(但没有接收)-->此时s引用还是指向着堆中的对象

 

img

 

第三句:String s2 = "1";发现字符串常量池已经保存了该对象的引用了,直接返回字符串常量池对堆中字符串的引用

 

img

 

很容易看到,两条引用是不一样的!所以返回false


第一句:String s3 = new String("1") + new String("1");注意:此时"11"对象并没有在字符串常量池中保存引用

 

img

 

第二句:s3.intern();发现"11"对象并没有在字符串常量池中,于是将"11"对象在字符串常量池中保存当前字符串的引用,并返回当前字符串的引用(但没有接收)

 

img

 

第三句:String s4 = "11";发现字符串常量池已经存在引用了,直接返回(拿到的也是与s3相同指向的引用)

 

img

根据上述所说的:最后会返回true~~~

 

 

 

JVM垃圾回收(GC)

一:GC的分类:

1、年轻代(1/3堆空间):

-XX:SurvivorRatio:Eden和Survivor的比值,默认8:1

-XX:NewRatio:老年代和年轻代内存大小的比例

分为一个Eden区(1/8)和两个Survivor区(叫from和to,各占1/8),每次回收完的存活对象放在Survivor区中,当放不下时则存在老年代中;

2、对象如何晋升到老年代
3、老年代

Full GC和Major GC (差不多)

Full GC比Major GC慢,但执行频率低

4、触发Full GC的条件

对于 Minor GC,其触发条件非常简单,当 Eden 空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:

只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。

老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。

为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。

使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC。具体内容请参考上面的第 5 小节。

在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。

当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么虚拟机会抛出 java.lang.OutOfMemoryError。

为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。

执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。

 

二、判断一个对象是否可被回收

1、引用计数算法

用过判断对象的引用数量来决定对象是否可以被回收

每个对象实例都有一个引用计数器,被引用则+1,完成引用则-1

任何引用计数为0的对象实例可以被当做垃圾回收

优点:执行效率高,程序执行受影响较小

缺点:无法检测出循环引用的情况,导致内存泄露(如两个对象实例之间相互引用,则引用不可能为0)

 

2、可达性分析算法

通过判断对象的引用链是否可以达到来决定对象是否可以被回收

从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。

img

finalize()方法

类似 C++ 的析构函数,用于关闭外部资源。但是 try-finally 等方式可以做得更好,并且该方法运行代价很高,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。

当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会再调用该方法。

 

三、引用类型

无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。

Java 提供了四种强度不同的引用类型。

1. 强引用

被强引用关联的对象不会被回收。

使用 new 一个新对象的方式来创建强引用。

2. 软引用

被软引用关联的对象只有在内存不够的情况下才会被回收。

使用 SoftReference 类来创建软引用。

3. 弱引用

被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。

使用 WeakReference 类来创建弱引用。

4. 虚引用

又称为幽灵引用或者幻影引用,一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用得到一个对象。

为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到一个系统通知。

使用 PhantomReference 来创建虚引用。

 

四、垃圾回收(GC)算法

1、标记-清除算法

标记:从根集合进行扫描,对存活的对象进行标记

清除:对堆内存从头到尾进行线性遍历,回收不可达对象内存

不足:

 

2、复制算法

“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

 

3、标记-整理算法

标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。适合用于存活率高的场景(如老年代)

优点:不会产生内存碎片

不足:需要移动大量对象,处理效率比较低。

 

4、分代收集算法

现在的商业虚拟机采用分代收集算法(GC组合拳),它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。

一般将堆分为新生代和老年代。

 

无论是可达性分析算法,还是垃圾回收算法,JVM使用的都是准确式GC。JVM是使用一组称为OopMap的数据结构,来存储所有的对象引用(这样就不用遍历整个内存去查找了,空间换时间)。 并且不会将所有的指令都生成OopMap,只会在安全点上生成OopMap,在安全区域上开始GC。

 

五、垃圾收集器

上面所讲的垃圾收集算法只能算是方法论,落地实现的是垃圾收集器

单词前析:

Stop-the-World:JVM由于GC而暂停应用程序执行;任何一种GC算法都会发生;多数GC优化通过减少Stop-the-World发生的时间来提高程序性能

Safepoint:安全点,即可以暂停的点,分析过程中对象引用关系不会发生变化 的点;产生Safepoint的地方:方法调用;循环跳转;异常跳转等;安全点数量得适中

 

【1-3,年轻代收集器】

1、Serial收集器

通过(-XX:UserSerialGC)设置,复制算法

2、ParNew收集器

(-XX:+UseParNewGC)复制算法

3、Parallel Scavenge收集器

(-XX:+UseParallelGC)复制算法

吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间)

 

【4-6,老年代收集器】

4、Serial Old收集器

(-XX:+UseSerialOldGC)标记-整理算法

5、Parallel Old收集器

(-XX:+UseParallelOldGC)标记整理算法,jdk6后才出现

6、CMS收集器

(-XX:+UseConcMarkSweepGC)标记-清除算法

过程:(2次短暂停顿)

初始标记:stop-the-world 并发标记:并发追溯标记,程序不会停顿 并发预清理:查找执行并发标记阶段从年轻代晋升到老年代的对象 重新标记:暂停虚拟机,扫描CMS堆中的剩余对象 并发清理:清理垃圾对象,程序不会停顿 并发重置:重置CMS收集器的数据结构

7、G1收集器

【既用于年轻代又用于老年代,是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器.】

(-XX:+UseG1GC)复制+标记-整理算法

Garbage First收集器的特点: 并行和并发、分代收集、空间整合、课预测的停顿

 

img

 

JVM参数与调优

https://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html

https://zhuanlan.zhihu.com/p/25511795

 

 

作者:Java3y

链接:https://www.zhihu.com/question/36204510/answer/667969441

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

JVM面试题

拿些常见的JVM面试题来做做,加深一下理解和查缺补漏

题目来源:

1详细jvm内存结构

根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

 

imgimg

 

具体可能会聊聊jdk1.7以前的PermGen(永久代),替换成Metaspace(元空间)

 

imgimg

 

图片来源:https://blog.csdn.net/tophawk/article/details/78704074

参考资料:

2讲讲什么情况下回出现内存溢出,内存泄漏?

内存泄漏的原因很简单:

常见的内存泄漏例子:

解决这个内存泄漏问题也很简单,将set设置为null,那就可以避免上诉内存泄漏问题了。其他内存泄漏得一步一步分析了。

内存泄漏参考资料:

内存溢出的原因:

解决:

参考资料:

3说说线程栈

这里的线程栈应该指的是虚拟机栈吧...

JVM规范让每个Java线程拥有自己的独立的JVM栈,也就是Java方法的调用栈。

当方法调用的时候,会生成一个栈帧。栈帧是保存在虚拟机栈中的,栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息

线程运行过程中,只有一个栈帧是处于活跃状态,称为“当前活跃栈帧”,当前活动栈帧始终是虚拟机栈的栈顶元素

通过jstack工具查看线程状态

参考资料:

4JVM 年轻代到年老代的晋升过程的判断条件是什么呢?

  1. 部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。
  2. 如果对象的大小大于Eden的二分之一会直接分配在old,如果old也分配不下,会做一次majorGC,如果小于eden的一半但是没有足够的空间,就进行minorgc也就是新生代GC。
  3. minor gc后,survivor仍然放不下,则放到老年代
  4. 动态年龄判断 ,大于等于某个年龄的对象超过了survivor空间一半 ,大于等于某个年龄的对象直接进入老年代

5JVM 出现 fullGC 很频繁,怎么去线上排查问题

这题就依据full GC的触发条件来做:

- 所以看看是不是perm gen区的值设置得太小了。

- 这个一般没人去调用吧~~~

- 是不是频繁创建了大对象(也有可能eden区设置过小)(大对象直接分配在老年代中,导致老年代空间不足--->从而频繁gc) - 是不是老年代的空间设置过小了(Minor GC几个对象就大于老年代的剩余空间了)

 

imgimg

 

6类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?

双亲委托模型的重要用途是为了解决类载入过程中的安全性问题

Java的类加载是否一定遵循双亲委托模型?

- https://zhuanlan.zhihu.com/p/28909673 - https://www.cnblogs.com/huzi007/p/6679215.html - https://blog.csdn.net/sigangjun/article/details/79071850

参考资料:

7类的实例化顺序

检验一下是不是真懂了:

输出数据:

第一次做错的同学点个赞,加个关注不过分吧(hahaha

8JVM垃圾回收机制,何时触发MinorGC等操作

当young gen中的eden区分配满的时候触发MinorGC(新生代的空间不够放的时候).

9JVM 中一次完整的 GC 流程(从 ygc 到 fgc)是怎样的

YGC和FGC是什么

什么时候执行YGC和FGC

10各种回收算法

GC最基础的算法有三种:

具体:

11各种回收器,各自优缺点,重点CMS、G1

图来源于《深入理解Java虚拟机:JVM高级特效与最佳实现》,图中两个收集器之间有连线,说明它们可以配合使用.

 

imgimg

 

12stackoverflow错误,permgen space错误

stackoverflow错误主要出现:

permgen space错误(针对jdk之前1.7版本):

##