Java 虚拟机 (JDK1.8及以前)
Q1:什么是Java虚拟机,怎么去理解Java虚拟机
类似中间件,帮助Java与操作系统进行沟通,那么操作系统我们也可以认作为中间件,帮助我们与硬件进行沟通(硬盘、CPU)
Q2:既然Jvm的作用是为了帮助我们的程序(类)去适配不同的操作系统,那么类是怎么加载的呢?
java类的加载分为以下7步,主要为前5步:
1.加载: 读取.class文件
2.验证: 校验字节码文件正确性
3.准备: 给静态变量/常量分配内存,校验内存是否足够,并赋予字段类型默认值。
public static int sector = 3;//此时赋予的值为0
public static final int number = 3;//此时赋予的值为3,因为被final修饰,该值不可改变
4.解析:Jvm 针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类引用进行解析。这个阶段的主要任务是将其在常量池中的符号引用替换成直接其在内存中的直接引用。
private static final String EN = "zh_CN";
calculate(CN);
>>>
直接指向“zh_CN”所存的内存地址
5.初始化:执行静态变量和静态代码块
6.使用:用户程序执行
7.销毁:销毁创建的对象
Q3:那么什么是类加载中的“双亲委派机制”呢?类加载器有哪几种,分别作用是什么?
类加载器分为4种:
-
(子类)自定义加载器:user classloader,负责加载用户自定义路径下的jar。
继承ClassLoader重写findClass -
(当前类)应用类加载器:application classloader 加载classpath下jar。
public static void getAppClassLoader(){
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
URL[] urls = urlClassLoader.getURLs();
for (URL url : urls) {
System.out.println(url);
}
}
- (父类)扩展加载器:extension classloader,负责加载jre/lib/ext下jar。jdk9中将ext classloader删除,即删除了ext目录,改为:platform classloader。
ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();
-
(祖父类)根加载器:bootstrap classloader 引导类加载。负责加载核心类库,例如jre/lib下的rt.jar。
什么是“双亲委派机制”: 可以理解为与java中重写刚好相反,即:子类要加载类时,先看父类是否已经加载过或父类是否能够加载此类,若能则由父类加载,子类不加载。
具体代码,自行查看
new ClassLoader.loadClass()
new URLClassLoader.findClass()
//调用本地方法加载类
new ClassLoader.defineClass()
重写:子类未重写则引用父类方法,子类重写则引用子类方法。
Q4:如何打破双亲委派机制?
1、自定义类加载器,例如连接数据库加载驱动时,通过应用类加载器加载Driver的实现类org.postgresql.Driver
Thread.currentThread().getContextClassLoader();
2、重写loadClass和findClass方法
Q5:那么Java中变量和对象在内存中是如何分配的呢?即内存模型
内存模型分为以下5种
-
栈:只保存基础数据类型的值、对象以及基础数据的引用。每个线程都包含一个栈,每个栈都是私有的,数据不共享。
-
本地方法栈:本地方法对应的引用信息
-
程序计数器:记录代码运行的指针
-
方法区:所有的class信息(版本,类名,字段,方法,接口等)和static变量(包含static对象的引用)
-
堆:对象值,jvm只有一个堆,数据被所有线程共享
Q6:对象创建过程
1.如何划分内存
- 指针碰撞:指针已经记录当前内存分配到得位置,只需要指针后移即可
- 空闲列表:当堆中内存分配不规整时,例如之前对象被回收导致内存空闲。jvm会维护一个列表记录空闲内存的位置
Full GC时会处理内存碎片
2.创建对象时并发问题
- CAS(CompareAndSwap)
- 本地线程分配缓存(TLAB):每个线程分配一个缓存区内存,创建对象时,在各自缓存区创建。
3.对象头信息
为什么要进行指针压缩和对象填充:减少内存消耗,加快存取效率
Q7:对象内存分配
1.什么是对象逃逸分析?
简单理解:没有被其他地方引用,无需返回该对象。
public static void main(String[] args) {
Demo demo = new Demo(11, 180);
System.out.println(demo);
}
2.如何判断大对象?
通过Jvm参数设置:-XX:PretenureSizeThreshold=10000 (字节/单位) -XX:+UseSerialGC
3.如何判断大龄?
通过Jvm参数设置:-XX:MaxTenuringThreshold=
在JVM中用4个bit存储(放在对象头中),所以其最大值是15。每经历一次Minor Gc 年龄加1。
设置打印gc信息:-XX:+PrintTenuringDistribution