每天一道面试题-JVM相关知识
二、JVM
1. Java内存结构
- 线程私有的:
- 程序计数器(当前线程所执行的字节码的行号指示器)
- 虚拟机栈(每次方法调用的数据都是通过栈传递的。局部变量表/对象引用/实例方法)
- 本地方法栈(有一个native关键字修饰,而且不存在方法体,这种用native修饰的方法就是本地方法)
- 线程共享的
-
堆(栈管运行,堆管存储,堆是垃圾收集器管理的主要区域)
-
方法区(存放类的元数据信息,常量和静态变量)
-
直接内存(非运行数据区的一部分)
2. Java内存模型
- 概念
Java内存模型(Java Memory Model,JMM)是java虚拟机规范定义的,工作内存和主内存。Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,
但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝
-
volatie
- 保证可见性
- 不保证原子性
- 禁止指令重排(保证有序性)
-
总线嗅探机制
每个处理器通过嗅探在总线上传播的数据来检查自己的缓存值是不是过期了
3. 对象的创建过程
类加载检查 -> 分配内存(执行碰撞/空闲列表) -> 初始化零值 -> 设置对象头 -> 执行 init 方法
4. 类加载过程
类加载过程:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。
5. 类加载器&双亲委派模型介绍
- 加载器
-
BootstrapClassLoader(启动类加载器)
-
ExtensionClassLoader(扩展类加载器)
-
AppClassLoader(应用程序类加载器)
-
自定义加载器
- 双亲委派
每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 双亲委派模型 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass()
处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader
中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 BootstrapClassLoader
作为父类加载器。
- 双亲委派优点
- 可以避免类的重复加载
- 比如我们编写一个称为
java.lang.Object
类的话,那么程序运行的时候,系统就会出现多个不同的Object
类。
6. GC调优策略
-
GC 调优原则;
多数的 Java 应用不需要在服务器上进行 GC 优化; 多数导致 GC 问题的 Java 应用,都不是因为我们参数设置错误,而是代码问题; 在应用上线之前,先考虑将机器的 JVM 参数设置到最优(最适合); 减少创建对象的数量; 减少使用全局变量和大对象; GC 优化是到最后不得已才采用的手段; 在实际使用中,分析 GC 情况优化代码比优化 GC 参数要多得多。
-
GC 调优目的;
将转移到老年代的对象数量降低到最小; 减少 GC 的执行时间。
-
GC 调优策略;
- 将新对象预留在新生代
- 大对象进入老年代
- 合理设置进入老年代对象的年龄
- 设置稳定的堆大小,堆大小设置有两个参数
7. GC Roots的对象分为以下几种
- 虚拟机栈(栈帧中的本地方法表)中引用的对象(局部变量)
- 方法区中静态变量所引用的对象(静态变量)
- 方法区中常量引用的对象
- 本地方法栈(即native修饰的方法)中JNI引用的对象(JNI是Java虚拟机调用对应的C函数的方式,通过JNI函数也可以创建新的Java对象。且JNI对于对象的局部引用或者全局引用都会把它们指向的对象都标记为不可回收)
- 已启动的且未终止的Java线程
8. 垃圾回收算法
标记清除,复制,标记整理和分代收集算法
9. 引用的几种类型
强引用、软引用、弱引用、虚引用
10. 解决服务器OOM问题
- top命令
- jstack命令 jstack pid查看线程的堆栈信息
- Arthas,CPU 为什么起飞了 thread(redifine/jad)
- 结合应用报错追踪平台(cat),一些heartbeat的GC info,JVM heap info等