八股文_常规_jvm

232 阅读6分钟

堆内存

运行时数据区.jpg

类加载器

运行时根据需要动态加载类,将.class文件加载到内存中并转换为Class对象,类加载的过程:

  • 加载:通过io流把字节码文件读入到jvm的元空间
  • 连接:
    • 验证:校验字节码文件头8位是否是cafebabe
    • 准备:为类中静态部分开辟空间并赋初始值
    • 解析:将符号引用转换为直接引用
  • 初始化:为类中的静态部分赋指定值并执行静态代码

jdk提供了下面的三个类加载器:

  • BootStrapClassLoader:根类加载器,加载jre/lib下面的类
  • ExtClassLoader:扩展类加载器,加载jre/lib/ext下面的类
  • AppClassLoader:应用类加载器,加载classpath下面的类

双亲委派:

  • 类加载器加载一个类时,首先请求它的父类加载器,如果父类加载过则返回,如果没有则继续向上请求,一直到根类加载器BootStrapClassLoader,如果启动类加载器也没有找到,则从上到下依次由子加载器加载
  • jvm中判断一个类是不是已经被加载过的逻辑是:类名+对应的类加载器实例

双亲委派.jpg

  • 设计优点:避免核心类库被篡改
// 核心代码
protected Class<?> loadClass(String name, boolean resolve)  // resolve默认是false
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
            }

            if (c == null) {
                c = findClass(name);
            }
        }
        return c;
    }
}

堆空间

  • 堆空间是线程共享的
  • 堆的默认最大空间是物理内存的1/4
  • 堆分为新生代和老年代,新生代:老年代=1:2
  • 新生代又分为Eden区、s0区、s1区,Eden:s0:s1=8:1:1

对象进入老年代的方式:

  • 年龄超过某个阈值,这个是动态调整的,区间在7-15
  • 大对象可直接进入老年代,jdk8没有启用该配置
  • 动态年龄判断:当Survivor区中年龄从1到n的对象大小之和累加超过Survivor区的50%时,年龄大于等于n的对象直接进入老年代

元空间

  • jdk1.8叫元空间(之前叫永久代、方法区等),元空间存储类信息、字符串常量池等
  • 元空间使用本地内存,大小受物理内存限制,不受堆内存控制

程序计数器

线程私有,每个线程都有自己的程序计数器,记录待执行的下一条指令的地址,jvm中唯一不会出现内存溢出的地方

本地方法栈

  • 线程私有,本地方法:native修饰的方法,由c++等语言实现,用来调用操作系统的方法
  • 虚拟机栈存的是java方法调用过程中的栈帧,本地方法栈存的是本地方法调用过程中的栈帧
  • 也会出现内存溢出栈溢出

虚拟机栈

  • 虚拟机栈是线程私有,每个线程对应一个栈,栈中放栈帧,通过-Xss设置栈大小

  • 一个方法开始执行栈帧入栈,方法执行完对应的栈帧出栈,所以虚拟机栈不需要进行垃圾回收

  • 线程太多,没有足够的内存创建 虚拟机栈 会出现内存溢出,方法调用层次太多可能会出现栈溢出

  • 局部变量表保存这个方法执行过程中的局部变量,操作数栈用来辅助计算

    操作数栈.jpg

垃圾回收算法

  • 复制算法:把内存分成两部分,每次只用其中一部分,需要进行回收时,将已经使用的内存中的对象移动到另一块内存中,然后将之前已经用过的那块内存回收,就是s0s1区,高效且避免内存碎片
  • 标记-清除算法:先标记垃圾对象,然后直接清除,这种方式会产生很多内存碎片
  • 标记-整理算法:先标记垃圾对象,然后将存活的对象移动到一起,最后将其余对象回收

垃圾回收器

先用“可达性分析算法”找到没有被GC Roots引用的对象,然后使用“垃圾回收算法”回收对象,如果对象在 finalize 方法中不能拯救自己就会被回收(除非有性能衡量指标,否则尽量不要修改GC的配置参数,即改也必须基于大量的测试结果)

名称适用代际线程模式备注
Parallel新/老年代并发jdk8默认
ParNew新生代并发默认与CMS是一对
CMS老年代并发通过并发标记和回收减少STW
G1全堆并发jdk9+默认,将内存分成小块,分新老年代,jdk9+默认
ZGC全堆并发jdk11推出,将内存分成小块,不分新老代

jvm调优

  • 调整新生代大小,让更多对象在Minor GC时被回收
  • 调整对象晋升老年代时的年龄阈值
  • 减少大对象的创建,可将大对象拆成小对象
  • 选择合适的垃圾回收器,比如G1ZGC

参数总结

场景排查
  • jps -l:查看java进程
  • jmap -heap pid:查看堆内存分布
  • jmap -histo:live 进程id | more:查看对象数量
  • jmap -dump:format=b,file=heapdump.hprof 进程id:手动生成堆快照
  • -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/dump.hprof:堆内存溢出时自动生成快照
  • -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log:保存gc日志
参数设置
  • -Xss:设置虚拟机栈大小
  • -Xms:设置堆的初始大小
  • -Xmx:设置堆的最大内存
  • -XX:MetaspaceSize:设置元空间大小
  • -XX:MaxMetaspaceSize:设置元空间最大大小
  • -XX:+UseAdaptiveSizePolicy:自动调整堆空间大小,默认开启
  • -XX:PretenureSizeThreshold:设置大对象进入老年代的阈值
  • -XX:InitialTenuringThreshold-XX:MaxTenuringThreshold:设置进入老年代的年龄阈值

面试题

  • 对象都是在堆中创建吗? 有些对象不会被方法外部访问到,这样的对象会被创建在栈上,在栈上创建对象的时候 会把对象拆分成基本类型(叫标量替换)

  • 逃逸分析是什么? jvm会分析 锁对象 是否只会被当前的线程访问,如果是的话 可以实现栈上分配对象,逃逸的类型包括:方法逃逸和线程逃逸

  • finalize方法的作用? 对象在被销毁前调用的方法,如果此对象可以和gc roots上的对象建立链接 则不会被销毁