JVM面试相关

    科技2022-08-18  97

    JVM面试相关

    1. JVM内存配置参数背-Xss1024k-Xms512m-Xmx1024m-Xmns512m-XX:NewSize=512m-XX:MaxNewSize=512m-XX:NewRatio=8-XX:SurvivorRatio=32-XX:MaxPermSize=256m 2. OOM2.1 常见的OOM2.2 OOM常见的原因及解决办法 3. Java创建对象的过程1. 类加载检查2. 分配内存2.1 内存分配的方式2.2 内存分配并发问题 3. 初始化零值4.设置对象头5.执行init方法 4.对象的访问定位有哪两种方式4.1 句柄4.2 直接指针句柄和直接指针的优劣分析 虚引用类加载垃圾回收器空间分配担保:Java运行的机制可达性分析算法

    1. JVM内存配置参数背

    -Xss1024k

    意义:设置线程栈占用内存的大小默认值:不同的操作系统,默认值是不同的。

    -Xms512m

    意义:设置堆内存初始值的大小

    -Xmx1024m

    意义:设置堆内存的最大值

    -Xmns512m

    意义:设置新生代的初始值和最大值

    -XX:NewSize=512m

    意义:设置新生代的初始值

    -XX:MaxNewSize=512m

    意义:设置新生代的最大值

    -XX:NewRatio=8

    意义:设置年老代和年轻代的比例。比如:-XX:NewRatio=8表示老年代的内存:年轻代的内存=8:1。老年代占堆内存的8/9;年轻代占堆内存的1/9。

    -XX:SurvivorRatio=32

    意义:设置新生代中Eden区和一个Survivor区的大小比例。如:-XX:SurvivorRatio=8表示存活区Eden区占据年轻代的8/10,一个Survivor区占据年轻代的1/10.

    -XX:MaxPermSize=256m

    意义:更改方法区的大小

    2. OOM

    2.1 常见的OOM

    2.1.1 java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出 此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。 2.1.2 java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出 即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。

    2.2 OOM常见的原因及解决办法

    内存分配过小,正常的业务使用了大量的内存 方法 :jamp-heap 10765。 可以查看新生代,年老代内存分配的大小和使用情况,看看是不是堆分配的太少了。某一个对象被频繁申请,但是没有释放,内存不断泄露,导致内存耗尽。 方法: jmap -histo:live 10765 | more 输入命令后,会以表格的形式显示存活对象的信息,这些信息包括这个对象的实例数,所占内存的大小,类名,并且按照所占内存的大小排序。这时候就可以找到实例数较多,占用内存较多的实例,然后对相关的代码进行检查。某一个资源被频繁申请,系统资源耗尽。例如频繁创建线程,不断发起网络连接。 使用ps命令或者netstat命令来查看进程创建的线程数,网络连接数。如果资源耗尽,也有可能出现OOM

    3. Java创建对象的过程

    1. 类加载检查

    虚拟机遇到一条new指令的时候,首先检查这个指令的参数能否在常量池中找到这个类的引用。并检查这个符号引用代表的类是否已经被加载过,如果没有,那就必须先执行相应的类加载过程。

    2. 分配内存

    新生对象需要分配内存,对象所需内存大小在类加载完成后便可以确定。内存分配的方式有两种,一种是“指针碰撞”,一种是“空闲列表”。选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

    2.1 内存分配的方式

    指针碰撞

    使用适用场合:堆内存规整(没有内存碎片)的情况下。原理:用过的内存全部整合到一边,没有用过的内存放到另一边。中间有一个分界指针,只需要想着没用过的内存方向将该指针移动对象内存大小的位置即可。GC收集器:Serial,ParNew 空闲列表适用场合:堆内存不规整的情况原理:虚拟机维护一个列表,该列表会记录哪些内存块是可用的,在分配的时候,找一个足够大的内存块划分给对象实例,最后更新列表记录。GC收集器:CMS

    2.2 内存分配并发问题

    可能有多个线程同时在堆上创建对象,这时候创建对象要考虑线程安全的问题。解决办法如下:

    CAS+失败重试获取空间TLAB:为每一个线程预先在Eden区分配一块内存,JVM在给对象分配内存的时候,首先在TLAB上分配,当对象大于TLAB中剩余内存或者TLAB内存已经使用完毕,再使用上述的CAS进行内存的分配。

    3. 初始化零值

    将分配给对象的,除了对象头之外的所有内存空间都初始化为零值。保证对象的实例字段没有初始化也可以使用。

    4.设置对象头

    5.执行init方法

    这个init方法的内容有 父类变量初始化 父类语句块 父类构造函数 子类变量初始化 子类语句块 子类构造函数

    4.对象的访问定位有哪两种方式

    4.1 句柄

    在堆上有一个句柄池用来存放诸多句柄。每一个句柄有两个指针,一个指针指向堆中的对象,另一个指针指向这个对象的类信息,这个类信息是存放在方法区的。

    4.2 直接指针

    局部变量表的数据直接通过指针指向堆中的对象,这个对象里面存放着一个指针,这个指针指向这个对象的类信息,同样,类信息存放在方法区当中。

    句柄和直接指针的优劣分析

    这两种对象访问⽅式各有优势。使⽤句柄来访问的最⼤好处是 reference 中存储的是稳定的句柄地 址,在对象被移动时只会改变句柄中的实例数据指针,⽽ reference 本身不需要修改。使⽤直接指针 访问⽅式最⼤的好处就是速度快,它节省了⼀次指针定位的时间开销

    虚引用

    一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用得到一个对象。为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到一个系统通知

    类加载

    类的生命周期有 加载,验证,准备,解析,初始化,使用,卸载。其中加载,验证,准备,初始化,卸载这个五个阶段是严格按照这个顺序开始的,但是解析则不确定。

    垃圾回收器

    JVM相关,崔腾翔的博客,除了G1垃圾收集器之外,都讲得挺仔细的。 https://blog.csdn.net/weixin_42045759/article/details/105580874

    G1收集器看这篇文章就够了 https://blog.csdn.net/mlplds/article/details/108695611 https://www.cnblogs.com/wjh123/p/11146195.html G1收集器的运作⼤致分为以下⼏个步骤: 初始标记 并发标记 最终标记 筛选回收 G1收集器在后台维护了⼀个优先列表,每次根据允许的收集时间,优先选择回收价值最⼤的Region(这

    空间分配担保:

    1. 虚拟机在进行minorGC之前会判断老年代最大的可用连续空间是否大于新生代的所有对象总空间 2. 如果大于的话,直接执行minorGC 2、如果小于,判断是否开启HandlerPromotionFailure,没有开启直接FullGC 3、如果开启了HanlerPromotionFailure, JVM会判断老年代的最大连续内存空间是否大于历次晋升的大小,如果小于直接执行FullGC 4、如果大于的话,执行minorGC 触发Minor GC的条件 Eden区域满了,或者新创建的对象大小 > Eden所剩空间触发Major GC/Full GC的条件 如果老年代空间也不足,就会触发Full GC调用 System.gc() 只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。

    Java运行的机制

    Java源代码编译后形成字节码文件,通过jvm的类加载机制加载到内存,然后使用,最后卸载。 类加载机制包括加载,验证,准备,解析,初始化

    可达性分析算法

    以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。 Java 虚拟机使用该算法来判断对象是否可被回收,GC Roots 一般包含以下内容:

    虚拟机栈中局部变量表中引用的对象本地方法栈中 JNI 中引用的对象方法区中类静态属性引用的对象方法区中的常量引用的对象
    Processed: 0.016, SQL: 9