4.jvm面试题

[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)。
然后内存空间初始化操作,接着是做一些必要的对象设置(元信息、哈希码…),最后执行方法

对象内存布局

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

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面试题)