美文网首页
4.jvm面试题

4.jvm面试题

作者: _少年不知愁 | 来源:发表于2021-03-09 20:26 被阅读0次

    [TOC]

    1.jvm分区?

    jvm分区.jpg
    1.Java 堆(Java Heap)
    Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
    
    2.Java 虚拟机栈(Java Virtual Machine Stacks)
    每个方法在执行的同时都会在Java 虚拟机栈中创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
    
    3.本地方法栈(Native Method Stack)
    与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的; 
    Native 关键字修饰的方法是看不到的,Native 方法的源码大部分都是 C和C++ 的代码
    
    4.程序计数器(Program Counter Register)
    当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成
    
    5.方法区(Methed Area)
    用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据
    

    2.常见的内存溢出异常

    1. Java堆溢出 =>java.lang.OutOfMemoryError: Java heap space
    时间久了,对象数量达到最大堆的容量限制后,堆空间无法给新的对象分配内存空间且GC一次后仍然无法分配足够的空间时会导致堆溢出。
    
    2. 栈溢出 => java.lang.StackOverflowError
    这是由于线程请求的栈深度大于虚拟机所允许的最大深度,无法压入新的栈帧,这时可以检查一下代码中是否存在死循环的递归调用;
    
    3.方法区溢出
    java.lang.OutOfMemoryError: PermGen space
    

    3.hotSpot虚拟机对象探秘

    见深入java虚拟机第二章的2.3

    对象的创建

    1.对象创建方式:
    使用new关键字,使用Class的newInstance方法,使用Constructor类的newInstance方法,使用clone方法,使用反序列化;
    2.创建过程
    虚拟机遇到new指令,首先去检查这个符号引用的的类是否被加载,解析和初始化,类加载通过后分配内存空间。
    若Java堆中内存是绝对规整的,使用“指针碰撞“方式分配内存;如果不是规整的,就从空闲列表中分配,叫做”空闲列表“方式;划分内存时还需要考虑一个问题-并发,也有两种方式: CAS同步处理,或者本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。
    然后内存空间初始化操作,接着是做一些必要的对象设置(元信息、哈希码…),最后执行<init>方法
    
    

    对象内存布局

    大致分成三块区域:对象头 实例数据 和对齐填充

    1.对象头
    第一部分,Mark word,用于存储对象自身运行时的数据,
    如哈希码,gc分代年龄,锁状态标志,线程持有锁,偏向线程id,偏向时间戳
    另一部分类型指针,即对象指向它的类元数据的指针,(用于确定对象类型)
    
    2.实例数据
    
    3.对齐填充
    对象字节是否能被8整除,否则自动填充到能被8整除
    

    对象访问定位

    指程序需要通过JVM上栈的引用访问堆中的具体对象;

    主要使用句柄池和直接指针;

    Java堆中划分出一块内存来作为句柄池,引用中存储对象的句柄地址,而句柄中包含了对象实例数据对象类型数据各自的具体地址信息,具体构造如下图所示

    句柄池&直接指针

    引用中存储的是稳定的句柄地址,如果对象被移动(垃圾收集中移动对象非常普遍,比如标记整理算法垃圾回收机制),只会改变句柄中的实例数据指针,而引用本身并不需要修改
    
    直接引用存储的是对象地址,速度更快,节省了一次指定定位的时间开销,(hotspot默认使用)
    
    [图片上传中...(访问定位-直接指针.jpg-1474b1-1615292570308-0)]
    访问定位-直接指针.jpg

    4.讲讲jvm gc?

    java中,程序员不需要显示地去释放一个对象的内存,而在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只要在虚拟机空闲的时候或者内存不足的时候,才会触发执行,扫描那些没有任何引用的对象的,并将他们加入要回收的集合中,进行回收。

    判断对象是否可以被回收?

    引用计数法:有对象引用就+1,释放就-1,为0可以被回收(已经被淘汰,无法解决循环引用问题)
    可达性分析法:Gc Roots开始向下搜索,走过的路径叫引用链,当一个对象到gc Roots没有任何引用链时相连时,则证明此对象可以被回收(目前广泛使用)
    

    minor gc&Major gc

    对象优先在堆的Eden区分配;
    大对象直接进入老年代;
    长期存活的对象将直接进入老年代;
    

    其它问题

    1.jvm永久代会发生垃圾回收?
    不会,永久代满了会触发垃圾回收Full gc
    
    2.如果对象引用被置为Null,垃圾回收是否会立即释放对象占用内存?
    不会,在下一个垃圾回收周期中,这个对象时可以被回收
    
    3.浅拷贝和深拷贝
    浅拷贝就是增加了一个指针指向已存在的内存空间,就对对象的数据成功进行简单赋值
    深拷贝是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存;
    浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变
    深复制:在计算机中开辟一块新的内存地址用于存放复制的对象
    
    

    5.垃圾收集器的种类

    垃圾收集器是垃圾回收算法(标记清除法、标记整理法、复制算法、分代算法)

    1.CMS收集器 -Concurrent Mark Sweep(老年代收集器,标记清除),具有高并发,低停顿的特点,追求最短的GC挺短
    
    2.G1收集器  -(Garbage First)(标记整理,复制算法),应为采用标记整理算法,不会产生磁盘碎片(回收范围整个java堆)
    
    3.分代垃圾收集器 (复制算法,标记整理算法)
    
    4.其它
    新生代回收:serial,parnew ,Parallel Scavenge
    老年代:serial old,Parallel Old,CMS
    
    新生代(1/3)老年代(2/3)
    新生代采用复制算法,(eden(8) s1(1) s2(1)),每次垃圾回收,s1 s2如果有引用,就复制来复制去,年龄加1,当到达一定值(默认是15),升级成老年代
    

    8.类的加载过程

    jar包的类并不是一次性加载,是使用到才能加载

    1.加载
    通过类的全限定名来获取定义此类的二进制字节流
    在内存中生成该类的class对象,作为访问入口
    
    2.验证
    检查加载class文件的正确性
    会出现.class文件被人篡改,字节码压根不符合规范,jvm根本无法执行这个字节码,所以在加载到内存之后,必须要验证
    
    3.准备
    给类的静态变量分配内存空间并将其初始化成默认值,该操作在方法区操作,
    准备阶段不分配类中实例变量的内存,实例变量在对象实例化时随对象一起分配在java堆中
    
    4.解析
    将常量池中符号引用替换成直接引用 (符号引用只是一个表示,直接引用指向内存地址)
    
    5.初始化
    对静态变量和静态代码块执行初始化工作
    
    6.使用
    
    7.卸载
    

    类加载器

    启动类加载器:负责加载支持JVM运行的位于JRE的lib目录下的核心类库,比如:jt.jar,charsets.jar
    扩展类加载器:负责加载支持JVM运行的位于JRE的lib目录下的ext扩展目录下的JAR类包
    应用程序类加载器:负责加载ClassPath路径下的类包,主要加载你自己写的类
    自定义加载器:负责加载用户自定义路径下的类包
    

    双亲委派机制

    当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载

    为啥设计双亲?

    • 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
    • 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性

    9.gc调优

    尽可能让对象所在新生代里分配和回收,尽量别让太多的对象频繁进入老年代,避免频繁对老年代进行垃圾回收;

    同时给系统充足的内存大小,避免新生代频繁地进行垃圾回收;

    jconsole监控工具

    对参数配置

    -Xms:初始堆大小,JVM 启动的时候,给定堆空间大小。 
    -Xmx:最大堆大小,JVM 运行过程中,如果初始堆空间不足的时候,最大可以扩展到多少。 
    -Xmn:设置堆中年轻代大小。整个堆大小=年轻代大小+年老代大小+持久代大小。 
    -XX:NewSize=n 设置年轻代初始化大小大小 
    -XX:MaxNewSize=n 设置年轻代最大值
    -XX:NewRatio=n 设置年轻代和年老代的比值。如: -XX:NewRatio=3,表示年轻代与年老代比值为 1:3,年轻代占整个年轻代+年老代和的 1/4 
    -XX:SurvivorRatio=n 年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。8表示两个Survivor :eden=2:8 ,即一个Survivor占年轻代的1/10,默认就为8
    -Xss:设置每个线程的堆栈大小。JDK5后每个线程 Java 栈大小为 1M,以前每个线程堆栈大小为 256K。
    -XX:ThreadStackSize=n 线程堆栈大小
    -XX:PermSize=n 设置持久代初始值 
    -XX:MaxPermSize=n 设置持久代大小
    -XX:MaxTenuringThreshold=n 设置年轻带垃圾对象最大年龄。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。
    
    #下面是一些不常用的
    
    -XX:LargePageSizeInBytes=n 设置堆内存的内存页大小
    -XX:+UseFastAccessorMethods 优化原始类型的getter方法性能
    -XX:+DisableExplicitGC 禁止在运行期显式地调用System.gc(),默认启用  
    -XX:+AggressiveOpts 是否启用JVM开发团队最新的调优成果。例如编译优化,偏向锁,并行年老代收集等,jdk6纸之后默认启动
    -XX:+UseBiasedLocking 是否启用偏向锁,JDK6默认启用  
    -Xnoclassgc 是否禁用垃圾回收
    -XX:+UseThreadPriorities 使用本地线程的优先级,默认启用    
    

    相关文章

      网友评论

          本文标题:4.jvm面试题

          本文链接:https://www.haomeiwen.com/subject/aoibqltx.html