本篇为JVM重点知识介绍,JVM相关常见面试问题
复习之前学的内容,同时补充以下知识点:JVM的双亲委派机制、伊甸区与老年代相关知识;

JVM包含两个子系统和两个组件:
a) 子系统:ClassLoader,类加载器,和ExecutionEngine,执行引擎
b) 组件:Run-Time Data Area,运行时数据区,和Native Interface,本地接口
①ClassLoader,类加载:根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area;
②Execution engine,执行引擎:执行classes中的命令;
③Native Interface,本地接口:与Native Library交互,是其他编程语言交互的接口;
④Runtime Data Area,运行时数据区:就是我们常说的JVM内存;
流程:首先通过编译器,将Java代码编译为字节码,类加载器ClassLoader再把字节码加载到内存,放到Run-Time Data Area的方法区内,而字节码文件只是JVM的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎Execution Engine,将字节码翻译为底层系统指令,再交由CPU去执行,而整个过程中需要调用其他语言的本地库接口,Native Interface。
Java程序运行机制详细说明
- 首先利用IDE集成开发工具编写.java文件;
- 利用编译器(javac命令)将源代码编译为字节码文件,后缀为.class;
- 运行字节码的工作是由解释器(java命令)来完成的;
类的加载指的是将类的.class文件中的二进制数据读入内存,将其放在运行时数据区的方法区内,然后再堆区创建的java.lang.Class对象,用来封装类在方法区内的数据结构。
PS:
静态变量放在方法区
静态对象放在堆区
程序的可见度:堆对于整个程序都是共享、可见的。
栈只对线程可见。所以是线程私有。其声明周期和线程相同。
双亲的含义应该就是AppClassLoader有:ExtClassLoader和BootstrapClassLoader“两个”父加载器。
首先介绍Java中的类加载器
Bootstrap ClassLoader(启动类加载器),默认加载jdk\lib目录下jar中的诸多类。可以使用-Xbootclasspath指定。
Extension ClassLoader(扩展类加载器),默认加载jdk\lib\ext目录下的jar中的诸多类。可以使用java.ext.dirs系统变量更改。
Application ClassLoader(应用程序加载器),应用程序加载器,负责加载开发人员所编写的诸多类。
User ClassLoader(自定义加载器),自定义类加载器,当存在上述加载器解决不了的特殊情况时,或者存在特殊要求时,可以自行实现类加载逻辑。
关系图:

通俗故事:
- 假设用户刚刚摸鱼时写了个Test类想进行加载,此时会发送给应用程序类加载器AppClassLoader;
- 然后AppClassLoader并不会直接去加载Test类,而是会委派于父类ExtClassLoader,来完成此操作;
- ExtClassLoader同样不会直接加载Test类,而是会继续委派父类BootstrapClassLoader;
- BootstrapClassLoader已经是顶层了,没有更高的父类加载器了,因此BootstrapClassLoader就从jdk\lib中搜索是否存在,因为这里是用户自己写的Test类,因此不会存在于jdk下,所以此时会给子类一个反馈;
- ExtClassLoader收到父类传回的反馈,知道父类加载器没有找到对应的类,爸爸靠不住,就只能自己来加载了,结果显而易见,自己也不行,只能给下面的子类加载器,AppClassLoader;
- AppClassLoader收到父类加载器的反馈,顿时明白,原来爸爸虽然是爸爸,但是终究不能管儿子的私事,所以此时,AppClassLoader就自己尝试去加载。
- 结果,就这样成功了,走了一大圈,兜兜转转还是自己干。
专业性解释:①避免类的重复加载;②防止核心API被篡改;
为了避免原始类被覆盖的问题。
老子走过的路,小子不用走
比如,用户编写了一个Object类,放入程序中加载。
当没有双亲委派机制时,就会出现重复的Object类,给开发人员造成很大的困扰,本来就只需要基于JDK开发就好了,现在还得把JDK中的类全记住,避免编写重复的类。
当存在双亲委派机制时,整个事情就不一样了,每次加载类时,都会遵循双亲委派机制,去问父类是否可以加载,如果可以呢,那就不需要再次加载了,这样事情就简单了。

为什么要考虑这个问题?

蓝色部分是多个线程共享部分;
绿色部分为单个线程独享部分;
;
垃圾是指JVM中没有任何引用指向的对象,如果不清理这些垃圾对象,那么他们就一直占用内存,而不能给其他对象使用,最终垃圾对象越来越多,就会出现OOM。
先找到垃圾对象。
每个对象保存一个引用计数器属性,用户记录对象被引用的次数。
a)优点:实现简单,计数器为0就是垃圾对象;
b)缺点:
①无法解决循环引用问题;
②需要额外的空间记录;
③需要额外的时间维护应用计数。

以GCRoots作为起始点,然后一层一层找到对应的对象,被找到的对象就是存活对象,那么其他对象就是不可达对象,即垃圾对象。
GCRoots包括:
STW,Stop The World;
a) 缺点:效率不高;产生内存碎片;
b) 优点:逻辑简单;
Copying
将内存分为两块,每次只使用其中一块,进行GC时,将可达对象赋值到另外没有被使用的内存块中,然后再清楚当前内存块中的所有对象,内存块交替使用。
a) 缺点:耗费空间较大;可达对象多时,效率很低,因此适用于新生代,垃圾对象多的空间;对象内存之地变化之后,需要额外的时间修改对象的引用地址。
b) 优点:没有内存碎片;没有标记和清除阶段,直接复制操作,不需要修改对象头;
Mark-Compact算法
第一阶段,从GCRoots找到并标记可达对象;
第二阶段,将所有存活对象移动到内存的一端;
最后清理边界外所有的空间;
a) 缺点:需要修改对象引用地址;适用于垃圾对象少、可达对象多;效率低 ,三种当中最低的;
b) 优点:没有内存碎片;不需要额外的内存空间;
分代收集的理念
不同对象的存活时间不一样,因此可以针对不同的对象采取不同的垃圾回收算法。

整个垃圾收集过程变长了,但是STW时间变短了;

Garbage First
将整个内存分为一个个的方块,均分为2048块。


所有的对象和数组都应该存放在堆区,在执行字节码指令时,会把创建的对象存入堆中,对象对应的引用地址存入虚拟机栈中的栈帧中,不过当方法执行完之后,刚刚被创建的对象并不会被回收,而是要等JVM后台执行GC之后,对象才会被回收。
可以通过-XX:NewRatio参数来配置新生代和老年代的比例,默认是2,新生代占1,老年代占2,也就是新生代占堆区的1/3;
一般不需要调整,只有明确知道存活时间比较长的对象偏多,那么就需要调大NewRatio,从而调整老年代的占比;
YGC,Young Garbage Collection,新生代垃圾回收,将Eden区对象放入S0区;S0区和S1区交替使用;
当达到某个条件之后,剩余对象就会被保存到老年代中。
遇到第二个非常大的对象,Eden区原有一个大对象,内存不够用时,Eden区的大对象会被直接放到老年代(S0、S1区放不下)
或者来了一个超大对象,可以直接放进老年代;
记录下一条待执行指令的地址;

虚拟机栈是线程私有的,每个线程创建时都会创建一个虚拟机栈,栈内保存一个栈帧,一个栈帧就对应一个方法。


操作数栈:也可以叫做操作栈,是栈帧的一部分,操作数栈是用来执行字节码指令过程中用来进行计算的。
比如加法过程,运算操作数在计算过程中的进栈出栈;

保存局部变量,比如加法过程中,其实每个变量的值都是保存在局部变量表中的。
C/C++语言写的一些代码
存的是本地方法的栈帧,也是线程私有的,也会OOM和SOF。
堆是 JVM 内存管理的最大的一块区域,此内存区域的唯一目的就是存放对象的实例,所有对象实例与数组都要在堆上分配内存。它也是垃圾收集器的主要管理区域。java 堆可以处于物理上不连续的空间,只要逻辑上是连续的即可。线程共享的区域。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将抛出 OutOfMemoryError 异常。
为了支持垃圾收集,堆被分为三部分:

在Java中可以使用new关键字和反射机制创建对象:
User user = new User();
User user = User.class.newInstance();
或者使用Constructor类的newInstance():
Constructor<User> constructor = User.getConstructor();
User user = constructor.newInstance();

内存的分配过程的?
Integer对象占用空间的有三部分: