1.Java类装载过程和其中细节?
答:java类装载分为三个阶段:
- loading(加载) :. java文件通过编译器编译后得到 .class文件,.class文件通过加载器加载到内存中。
- linking(连接)
2.1:验证 :校验加载到内存中的class是否符合jvm规范。
2.2:准备 :为静态变量分配内存,给类中的静态变量赋初始值
2.3:解析 : 虚拟机将常量池中的符号引用替换成直接引用。符号引用可以理解为一个标示,而直接引用是指
向内存中的地址。
- initialing(初始化):到了初始化阶段,才开始执行类中定义的java代码;初始化阶段是调用类构造器过程;
2.常见的类加载器,及其机制
BootstrapClassLoader 加载 sun.boot.class.path 路径下的类。
ExtClassLoader 加载 java.ext.dirs 下的类
ApplicationClassLoader 加载java.class.path下的类
自定义classloader ——> 继承 ClassLoader 重写 findClass方法 (模板方法设计模式)
机制:双亲委派机制。加载一个class的时候先去查找是否已经有这个类了,没有通知父加载器去查找是否已经加载这个类,没有继续通知父加载器查找是否加载这个类直到 BootstrapClassLoader查找还是没有的话,BootstrapClassLoader尝试在自己的加载的目录下加载这个类,不是自己加载,通知子加载器去加载。
为什么要使用双亲委派机制来加载一个类:
主要是出于安全的考虑,防止内存中出现多个相同的类,如:自定义一个java.lang.String 类,就无法保证类的唯一性。
怎么打破双亲委派机制:
自定义一个类加载器,重写 findClass 和 loadClass 方法。
自定义类加载器的使用场景:1.热部署 2. tomcat、Jboss等服务器都有自己自定义的类加载器 3. springAop 增强后
的类被实际的调用了。
一个简单类的大小:
一个简单类如: Object o = new Object();
1.对象头:markword 8个字节
2.ClassPointer指针:是否压缩:压缩为4个字节 不压缩为8个字节
3.实例数据 :i:引用类型:压缩为4个字节,不压缩为8个字节
4.Padding对齐:8的倍数
数组对象
- 对象头:markword 8
- ClassPointer指针同上
- 数组长度:4字节
- 数组数据
- 对齐 8的倍数
2. jvm的内存模型和GC
jvm运行时的内存被分为5个区域:程序计数器 虚拟机栈 本地方法栈 方法区 堆
- 程序计数器:是线程私有的,因为CPU的调度是分片的,就需要一个东西来记录当前线程执行的位置。程序计数器用于记录当前虚拟机正在执行的线程指令地址。
- 虚拟机栈:线程私有,每个方法执行的时候都会创建一个栈帧,由于存储局部变量表、操作数、动态链接和方法返回等信息,线程请求的栈深度超过了虚拟机允许的最大深度时,就会抛出StackOverFlowError。
- 本地方法栈:线程私有,保存的是native方法的信息。
- 方法区:存放的是已经被加载的类信息、常量、静态变量、即时编译器编译后的代码数据。即永久代,在jdk1.8中不存在永久代了,而是被元数据区代替了。jdk1.8中字符串常量池被移到了堆,运行时常量池依然还在方法区。
- 堆:线程共享,几乎所有的对象实例都在堆中,垃圾回收的主要区域。
关于垃圾回收:
判断垃圾回收的方式:引用计数 根可达算法 (roots:虚拟机栈对象、静态变量、常量池中对象、本地方法栈JNI引用对象)
垃圾回收算法:标记清除算法、标记整理、复制算法
堆的分代:年轻代(Eden s0 s1)、老年代
垃圾回收过程:当Eden区满了触发minor GC(YGC),存活对象进入s0.
Eden区再次满了触发minor GC ,回收Eden区和s0,存活对象进入s1.对象年龄到15 进入老年代
Eden区再次满了触发minor GC ,回收Eden区和s1,存活对象进入s0.对象年龄到15 进入老年代
常见的垃圾回收器:
Serial Serial Old Parallel Scavenge Parallel Old ParNew CMS G1 ZGC
CMS:初始标记 并发标记 重新标记 并发回收 (回收会存在空间碎片问题)