Java八股文——JVM

JVM

类加载器

提前加载.class类型文件到内存中,加载类时,使用双亲委派模型,初始化继承树上还没有初始化过的所有父类,并且会执行这个链路上的所有未执行过的静态代码块、静态变量赋值语句等。

全小写的class是关键字,用来定义类,首字母大写的Class,是所有class的类。

以下是一些示例,主要是反射相关的知识:

private static Class<One> one = One.class;
One oneObject = one.newInstance();
Field field = one.getDeclaredField("inner");
field.setAccessible(true);
field.set(oneObject, "world change");

newInstance()已过时,可以使用getDeclaredConstructor().newInstance()

newnewInstance()的区别:

  1. new是强类型校验,可以调用任何构造方法,在使用new的时候,这个类可以没有被加载过。
  2. newInstance()只能调用无参数构造方法。

层次结构

  • 最高层的加载器是Bootstrap,负责最核心的Java类
  • 其次是Platform ClassLoaderJDK9之前是Extension ClassLoader),记载一些扩展的系统类
  • 最底层是Application ClassLoader,加载用户定义的CLASSPATH路径下的类

双亲委派模式:从下至上依次询问是否加载过,如果未加载过,才逐层向下询问是否可加载。

自定义类加载器作用:1.隔离加载类(主要是因为中间件可能会自定义自己的jar包,从而导致包路径、类名相同的情况发生。通过类加载器实现类隔离,避免类冲突) 2.修改类加载方式 3.扩展加载源 4.防止源码泄漏

自定义类加载器:

public class CustomClassLoader extends ClassLoader{

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] result = getClassFromCustomPath(name);
            if (result == null) {
                throw new FileNotFoundException();
            } else {
                return defineClass(name, result, 0, result.length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        throw new ClassNotFoundException(name);
    }

    private byte[] getClassFromCustomPath(String name) {
        // 从自定义的路径中加载指定类
    }

    public static void main(String[] args) {
        ClassLoader classLoader = new CustomClassLoader();
        try {
            Class<?> clazz = Class.forName("One", true, classLoader);
            final Object object = clazz.newInstance();
            System.out.println(object.getClass().getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

内存布局

Heap(堆区)

堆区是OOM最主要产生的地方,存储几乎所有的实例对象,由垃圾收集器自动回收,由各子线程共享使用。

-Xms256M -Xmx1024M-X表示是JVM的运行参数,ms表示最小堆容量,mx表示最大堆容量。通常情况下,这两个数值设为一样,避免GC后动态调整堆大小的压力。

堆分成新生代和老年代,对象一开始都在新生代,存活时间过长则进入老年代,老年代也容纳新生代无法容纳的超大对象。

新生代=1个Eden区+2个Survivor区。Eden填满的时候会触发YGC,直接清除没有引用的对象,随后将依然存活的对象送至Survivor区,Survivor区分为S1S2两块内存空间,将所有存活的对象(包括Survivor区)复制到未使用的那块空间,然后清除掉当前正在使用的空间,交换两块区的使用状态。

每次YGC后对象计数器+1,超过一定的阈值后晋升至老年代。

JVM设置运行参数-XX:+HeapDumpOutOfMemoryError,遇到OOM异常时输出堆内信息。

Metaspace(元空间)

JDK8用元空间替换永久代。

字符串常量移动至堆内存,其他内容包括类元信息、字段、静态属性、方法、常量等移动至元空间内。

核心:元空间包括常量池、方法元信息和类元信息

在常量池中的String,其实际对象是被保存在堆内存中,所以为什么说相同的字符串是在常量池中先查找,但是这个String实际的对象并不是在常量池中。

JVM Stack(虚拟机栈)

java方法执行的内存区域,线程私有。

每个方法的调用到结束就是入栈到出栈的过程,在活动线程中,只有位于栈顶的帧才是有效的,称为当前栈帧,所有指令都只对当前栈帧进行操作。

每个方法对应的栈帧包括:

局部变量表

存放方法参数和局部变量的区域,局部变量没有准备阶段,必须显式初始化。

非静态方法在index[0]的位置存储调用该方法的对象的实例引用,随后是参数和局部变量。

操作栈

方法执行过程中,会有各种指令往栈中写入和读取信息,如下例子就是需要先把常量入栈,然后把常量赋值给局部变量表中对应的下标元素,随后将局部变量表中的元素压入操作栈。执行加法的时候,从操作栈弹出两个操作数,随后计算然后压入栈。

public int simpleMathod() {
    int x = 13;
    int y = 14;
    int z = x + y;
    return z;
}
动态连接

每个栈帧中包含一个在常量池中对于当前方法的引用,支持调用过程的动态连接。

方法返回地址

方法退出的过程相当于弹出当前栈帧,有三种方式退出:

  • 返回值压入上层调用栈帧
  • 异常信息抛给能够处理的栈帧
  • PC计数器指向方法调用后的下一条指令

Native Method Stacks(本地方法栈)

本地方法栈为Native方法服务。

所谓Native方法,例如System.currentTimeMills(),就是需要依赖本身的java程序以外的系统本地的方法类获取结果,这种方法一般是非Java的,与操作系统有关的。本地方法可以通过JNI获得和JVM相同的能力和权限。

Program Counter Register(程序计数寄存器)

每个线程产生后,都会有自己的程序计数器和栈帧,程序计数器用于存放执行指令的偏移量和行号指示器等,线程执行和恢复都需要依赖这个。

堆和元空间是所有线程共享的,虚拟机栈、本地方法栈、程序计数器都是线程内部私有的

垃圾回收

垃圾回收的主要目的是清除不再使用的对象,自动释放内存。

如何判断对象是否存活:JVM引入了GC Roots,如果一个对象与GC Roots没有直接或间接的引用关系,比如某个失去任何引用的对象,或者两个互相环岛循环引用的对象等。

回收算法:

  • 标记-清除算法:会带来大量的空间碎片,导致需要分配一个较大连续空间
  • 标记-整理算法:标记存活的对象,将存活对象整理到内存空间的一端,不会产生空间碎片的问题,是主流算法

垃圾回收器:

  • Serial:单线程,垃圾回收的某个阶段会暂停整个应用程序的执行,严重影响应用程序的性能,标记-整理算法
  • CMS:部分步骤依旧会暂停程序运行,但部分步骤可以并行,标记-清除算法
  • G1:JDK11中默认回收器,具备压缩功能,避免了碎片问题,预测停顿时间
  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值