【2.java面试-JVM篇】

JVM
  • Q1:讲一下JVM内存模型,并描述每一个模块的定义?

    Q2:什么情况下会发生栈内存溢出?
    
    Q3:讲一下垃圾回收算法?
    
    Q4:讲一下垃圾回收器?
    
    Q5:你怎么理解JMM内存模型?
    
    Q6:说下java类是怎么加载的?(什么是双亲委派机制)
    
    Q7:几个重要的JVM参数?
    
    Q8:强引用和弱引用,虚引用区别?
    

java虚拟机的内存大概图
在这里插入图片描述

程序计数器:

每个线程都有独立的程序计数器,线程私有的内存。它的生命周期随着线程的创建而创建,随着线程的结束而死亡。

java栈:先进后出

区域很小,特点是存取速度快,存放基础数据类型和堆中对象的引用

线程私有,OutOfMemoryError,栈溢出这里报错的

本地方法栈:

如果一个方法被声明native,它就是调用了本地方法栈,–》调用本地接口—》本地方法库。

线程私有

int a=0或Object o=null,在栈上分配空间

堆:

线程共享,垃圾回收主要的地方99%

new Object() 在堆上开辟空间

存的内容:对象实例,数组

  • 新生代:

    • 伊甸园Eden
    • 幸存0区(From),幸存1区(to),幸存区是动态的,谁空谁是to
    • 这里是轻GC
  • 老年代:

  • 永久代(元空间):

本地方法区

线程共享,存储已经被jvm加载的类信息,常量,静态变量,运行时常量池,即时编译后的代码

存放static,所有的class,java1.8后这里被称为元空间(永久代)

总结:线程共享区域(方法区,堆)线程私有(程序计数器,栈,本地方法栈)

Q:-Xms -Xmx -Xss含义:-Xms堆的初始值,-Xmx堆的最大值,-Xss每个线程栈的大小

  1. 一个类执行main方法,在内存中的过程:例子
public class AObject {
	public static void main(String[] args) {
		Cat cat = new Cat();
		cat.name = "招财";
		cat.age = 2 ;
		System.out.println(cat.name+":猫的名字"+"猫的名字:"+cat.age);
		//调用方法
		cat.say();
	}
}
class Cat{
	String name;
	int age;
	public void say(){
		System.out.println("喵~~");
	}
}
  1. 首先类加载,将AObject和Cat的类加载信息放入方法区。
  2. 执行main方法,main方法会进入栈中
  3. 执行到new cat(),会将cat的属性和方法放入方法区中,此时开辟一块1x00的内存地址,
  4. class被加载会在堆中开辟一块1x00内存空间,用于存放类的实例,并给成员变量和方法分配相应的地址内存。
  5. cat.name = “招财”;会在方法区开辟一块常量池地址2x00001用于存放’招财’这个值,age是int基础数据类型,cat.age = 2 ;在栈中
  6. main方法内执行到cat.say(),栈弹出,出栈。
引用
  • 强引用:

    Object obj = new Object(),类似这种引用关系,如果引用关系存在,垃圾回收永远不会回收调被引用的对象

  • 软引用:

    描述一些还有用,但非必须的对象,在系统发生内存溢出异常前,会把对象进行二次回收

  • 虚引用

    最弱的一种引用关系,无法通过虚引用获取对象的实例。如果它是虚引用,可以把它当成一个可有可无的引用关系,随时可以回收

垃圾回收算法
  • 引用记数法(java没有采用):在一个对象内部加一个计数器,当对象被引用了,计数器+1,当引用失效了,计数器-1,当计数器=0,类失效,被回收
  • 可达性分析算法:

在这里插入图片描述
GcRoot有哪些:1.局部变量表,2.静态变量,3.字符串常量池的引用,4.synchronized关键字,5.常驻异常对象(Null空指针异常类。)

​ GCROOT作为跟节点对象,从跟节点向下搜索,搜索过程所走过的路径(引用链),O3跟GCROOT有关联,能搜到,对象存活,O5跟O6跟对象没有任何引用链,判定O5和O6死亡,清除

  • 标记清除算法:标记+清除

    首先要标记出所有要回收的对象,标记完成后,统一回收调所有被标记的对象。

  • 标记压缩算法:标记+压缩+清理

    将存活对象压缩到内存的一端,清理边界。

  • 标记复制算法:Eden和Survivor的大小比例是8∶1

    复制算法是发生在新生代,新建的对象一般分配在新生代的Eden区,当Eden满了会进行一次GC(轻),存活对象会移动到S0(幸存from区),再次进行GC,S0存活对象复制到空的S1(幸存To区),谁空谁是to,这时候s0是空了,变为to,每次发生GC,s0和s1来回复制,默认15次GC后会转入老年区,老年区GC(重GC)。

在这里插入图片描述

元空间是永久存储对象的,也就是方法区,不进行回收

  1. 栈溢出代码:
public class AObject {

   public static void main(String[] args) {
    AObject aObject = new AObject();
    aObject.a();
   }
   private void a(){
      b();
   }
   private void b(){
      a();
   }
}
Exception in thread "main" java.lang.StackOverflowError
  1. 堆内存溢出代码:
/**
 * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 *
 * -Xms20m:堆的最小值为20MB
 * -Xmx20m:堆的最大值为20MB
 * -XX:+HeapDumpOnOutOfMemoryError 让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照
 *  
 */
public class AObject {

   public static void main(String[] args) {
      List<Cat> list = new ArrayList<>();
      while(true) {
         list.add(new Cat());
      }
   }
}
class Cat{
}

Java Object finalize() 方法
Object finalize() 方法用于实例被垃圾回收器回收的时触发的操作。
当 GC (垃圾回收器) 确定不存在对该对象的有更多引用时,对象的垃圾回收器就会调用这个方法。
一句话总结:就是System.gc(),就会调用这个finalize方法

经典垃圾收集器
  • Serial:单线程,必须先停止所有工作线程,新生代采取复制算法,老年代采取整理算法。
    • Serial Old是Serial收集器的老年代版本
  • ParNew: Serial收集器的多线程版本。
  • Parallel Scavenge:标记复制算法,吞吐量,jvm总运行时间100分钟,垃圾处理花1分钟,吞吐量99%
    • Parallel Old是Parallel Scavenge收集器的老年代版本
  • CMS(Concurrent Mark Sweep)收集器:最短停顿时间为目标的收集器,标记清除算法,支持并发(会产生内存碎片)
  • Garbage First(简称G1)收集器:标记整理算法的实现,支持并发(压缩空间,降低内存空间碎片)
虚拟机类的加载机制

类加载机制:java虚拟机把class文件加载到内存中,并对数据进行检验,转换解析,和初始化,最终形成可以被虚拟机直接使用的java类型,这个过程就叫做类记载机制。

在这里插入图片描述

其中的初始化:

  • 遇到new,static,(被final除外),被反射调用,如果类没有被初始化,会先进行初始化,
  • 如果父类没有被初始化,会先初始化父类,
  • 遇到有main()方法的类,会先初始化这个类。

双亲委派机制:

当一个类收到加载请求,首先会向上(父级)委派给父类加载器去完成,父类也会向上委托,当父类无法加载时子类才会自己加载。(向上委托,向下加载)

在这里插入图片描述

JMM(java内存模型)

Java内存模型的主要目的是定义程序中各种变量的访问规则(注意,只是规则,也就是一种规范)

规则如下:

  1. volatile变量类型:可见性(一个变量被定义为volatile后,当一个线程改变了这个值,其他线程立刻看到了),不保证原子性,禁止指令重排序
  2. synchronized:可见性,原子性,禁止指令重排序
  3. hapens-before原则:
    1. A在B之前,线程A操作在B之前执行
    2. 加锁:同一把锁的解锁操作必须在加锁之前执行
    3. 对volatile变量的写入操作必须在对改变量的读操作之前。(写在读之前)
    4. 线程启动原则:线程的所有操作必须在Thread.start()之后。(线程未开始,其他的都给我等着)
    5. 结束原则:线程所有操作必须在线程结束之前执行。(就是不能让线程结束了,你还没干完)
    6. 中断原则:对线程interrupt()方法的调用happens-before被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。
    7. 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。
    8. 传递性:A在B之前,B在C之前,那么A一定在C之前执行
调优:

问题:1.死锁,2.内存泄露,3.程序响应过高,4.cpu过高,5.GC频繁

案例:

新生代和老年代比例默认是1:2

1.提高堆空间大小,提升吞吐量(减少fullGC-重GC有延迟)

2.合理分配堆内存(官方建议):比如老年代后的内存是20M

  • -Xmx 和 -Xms设置为老年代存活对象的3-4倍(FullGC之后老年代占用的内存),那么这里就是设置成60M-80M。
  • 方法区(元空间)设置为老年代存活对象的1.2-1.5倍(24M-30M)
  • 年轻代-Xmn设置为老年代存活对象的1-1.5倍(20-30M)
  • 老年代为它的2-3倍(40-60M)。

3.如何强制触发FullGC?

  • jmap -dump:live ,format=b,file=heap.bin 将当前存活对象dump到文件中,会触发FullGc。
  • jmap -histo:live 打印每个class对象实例数目,内存占用,类全信息,此时会触发FullGc、
  • 通过java监控工具触发
  • 如果是springboot项目,可以采取以下命令设置-Xmx -Xms的值。
    • 启动sb项目,jps查询pid。21168 DemoApplication
    • jstat -gc 21168 1000 5 【查询FGC的次数】
    • 多次执行jmap -histo:live 21168 【触发fullGc】
    • jmap -heap 21168 【查询最后Old Generation】use的内存大小,就可以将-Xmx -Xms的值为use的4倍左右。

4.CPU很高的优化。

  • 死锁:解决方案-1.调整锁的顺序,2.采取定时锁,一段时间后,如果还获取不到锁,就释放自身的锁。

  • 排查过程:

    • ps aux|grep java 查看当前java进程使用cpu,内存磁盘使用量异常情况
    • top -Hp pid 查看当前使用异常线程pid
    • jstack 进程pid |grep -A20 -十六进制 得到相关进程的代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值