JVM
1、什么是JVM
- Java虚拟机,包括类加载子系统,运行时数据区,执行引擎
- 常见的JVM虚拟机:hotstop,j9,Microsoft VM
如下是JVM的体系结构:
3. 类加载子系统会将class文件加载到jvm虚拟机中,同时将对应的数据放到运行时方法区中
4. 运行时方法区:放的是将要被运行的数据
5. 执行引擎:按照代码逻辑,去操作运行时方法区里的数据
2、 为什么Java是编译解释型语言
- Java先由编译器编译成class文件,(一次性)
- jvm加载class文件,由解释器逐行解释成OS认识的机器代码(逐行)
3、 jvm内存结构分为:堆,栈(jvm栈,本地方法栈),方法区,程序计数器
- 堆:线程共享的,存放对象的,gc主要回收堆中对象,堆分为新生代和老年代,新生代分为伊甸园区和幸存者区
- 伊甸园和survivor的比例:8.1.1
2.新创建的对象都是放在堆中的伊甸园区
- 伊甸园和survivor的比例:8.1.1
- 方法区:线程共享的,存放的有关类的一些信息,属性,方法,静态变量
- 程序计数器:线程私有的,放的是正在执行的线程指令地址,内存很小
- 栈(jvm栈):线程私有的,存的是函数运行时的临时变量
- 本地方法栈:线程私有的,保存的是native方法的信息,被native修饰的方法是操作系统的方法
4、Java内存模型JMM
- JMM其实就是一套规范,在多线程并发操作下,多核CPU处理器下,会有缓存一致性问题的出现(直接读写内存效率不高,就用到了高速缓存,运算结束后,把缓存中的数据刷新到主内存中),而且处理器为了效率就会指令重排序,(一般都是开辟空间,设置初始化值,然后指向内存空间),有可能顺序就发生变化了,这些都是问题,所以JMM就指定了一些规范来保证原子性,可见性和有序性
- 可以用sych,volatile,final来保证
5、为什么分代,为什么Eden和Surivior
- 分代的原因:如果不分代,会gc所有的对象,扫描整个堆,效率不高,如果分代,把所有新创建的朝生夕死的放到伊甸园区中,会先扫描这块内存,百分之80的对象都会被清除,把空间腾出来,gc的效率会更高
- 在新生代gc后一次后,还存活的对象会放到surivior幸存者区,(如果一次gc就将存活的对象放到老年区,因为老年代的gc是full gc会全部扫描,这样效率会降低)
6、jre和jvm的区别
- jvm运行可执行程序的
- jre运行时环境:包括jvm,还有核心类库String.class,Object.class
- jdk:Java开发工具包包括jre和编辑器,调试器,开发工具
7、 JVM的特性
- 移植性:在不同的系统上都可以运行java,因为jvm的存在
- 成熟性:发展了多年,体系很稳定,用的人也很多
8、什么是类加载?类加载的过程:(JVM类的生命周期)
- 类加载就是把类通过jvm虚拟机通过加载,验证,准备,解析,初始化的方式加载到内存
- 类加载的过程:
1)加载:分三步:把class文件以二进制流的方式加载进来,在堆中生成对应的class文件
2)验证:加载进来的class字节流是否符合标准,会不会威胁到jvm的安全
3)准备:给class对象的静态变量开辟内存空间,设置初始化值
4)解析:把赋值符号转为引用符号
5)初始化:调用构造器执行Java代码的过程
9、类加载器
- 按照类加载器的顺序是为了让系统的类被优先加载
- 类加载器:通过全限定类名以二进制流的方式拿到一个类
- 类加载器是分层的:
1)启动类加载器bootstrap:去加载核心类库,String.class,Object.class
(getClassLoader为空,说明拿到的启动器是启动类加载器,使用c++来实现的)
2)扩展类加载器Extension:去加载扩展类库,/etc/*
3)系统类加载器Application ClassLoader:去应用中根据路径加载
4)自定义类加载器customClassLoader:继承自classLoader
- 如何打破双亲委派机制:
自定义类加载器,继承loadClass类,重写finalClass和loadClass方法(从特定位置拿到class文件)
10、双亲委派机制
- 类在被加载的是时候需要双亲委派机制
- 当类加载器需要加载一个类的时候,自己不会加载,会先将请求委派给父加载器,去父加载器的cache内存中寻找,当找到启动类加载器也找不到时,会按照加载器的顺序,依次的加载
- 父加载器不是继承的关系,而是组合的关系
- 为社么要有双亲委派机制:
1)为了安全
2)比如我自定义了一个java.lang.String覆盖了原来的String
3)自定义的String被加载进来后,用户会使用自定义的,当用户使用String进行密码存储时,我就会偷偷的拿到用户的密码,这是不安全的
4)使用双亲委派机制就不会出现这样的问题,一步步向上寻找,直到找到启动类加载器加载的核心类库,更加安全 - 如何打破双亲委派机制呢?
1)自定义类加载器,继承classLoader类
2)重写finalClass方法和loadClass方法
3)findClass方法里通过全限定类名把class文件以二进制流的方式加载进来
11、为什么要有双亲委派机制
- 安全啊
- 比如我自定义了一个java.lang.String类覆盖调原来的String,会走custom自定义类加载器
- 当用户要设置密码时,就会用String来存储,用户也不知道我在底层做了什么操作
- 如果我可以通过邮件的方式拿到密码,这不就密码泄露了么
- 使用双亲委派机制就不会出现这种问题:
- 交给custom自定义类加载器时,会向上调用,调用booststrap时从扩展类里面找到了String类,然后就不会用自定义的,更加安全
12、类加载器的使用场景,你使用过么
- 自己加载一个类,要加载一个类的时候调用的是loadClass方法
自定义类.class.getClassLoader().loadClass(“要加载进内存的类”)
13、判断是否可以被回收
- 可达性分析算法:根对象作为起点,向下寻找,被引用链引用着的对象是存活的对象,飘着的对象就可以被回收
- 根对象:
1. 本地方法区中常量和静态变量引用着的对象
2. 本地方法栈中引用的对象
14、Java中的垃圾回收算法
- 标记清除:先对可以回收的对象打上标记,然后清除;会有磁盘碎片的问题
- 标记整理:和标记清除是一样的,都是打上标记然后清除,然后把活着的对象全部向前移动,放到一起;没有了磁盘碎片的问题
- 复制/拷贝:把内存分成两个相等的空间,每次只使用一个空间,当空间用完时,把或者的对象复制到另一个空间上面,然后一次性清除当前空间;没有了磁盘碎片问题
- 分代回收算法:新生代用的是复制算法,老年代用的是标记整理算法;当对象的大小大于新生代的空间时,放到老年代存储,当新生代经历了几次垃圾回收依然存活,会放到老年代中
15、Java中的垃圾回收器
- 年轻代垃圾回收器:serial回收器,parallel new回收器,parallel scavenge回收器(都是复制算法,因为是新生代,大部分都被回收,复制的成本比较小)
1.servial:是单线程的当垃圾回收器工作时,其他线程不允许工作,所以用户体验不好
2. Parallel New :和servial是一样的需要stop the world,不过是多线程的
3. Parallel Scavenge:目标是达到一个可控的吞吐量
- 老年代回收器:serial old回收器,parallel old回收器,CMS回收器
5. serial old:一般和新生代回收器一起用
6. parallel old:一般和Parallel Scavenge一起用
7. CMS:基于标记清除实现的,有空间碎片,其目标是回收停顿时间达到无限小
- 介于新生代和老年代的回收器:G1
16、CMS
- CMS并发标记清除:目的是获得最短的停顿时间,并发:用户线程和gc线程在某些阶段同时执行,是基于标记清除算法的
- cms执行的四个步骤:
- 初始标记:标记gc跟对象直接连到的对象,这个对象不会很多,因此很快
- 并发标记:根据上一步的结果,遍历执行链上的所有对象
- 重新标记:重新标记一次,因为并发标记时,没有暂停用户线程,可能产生新的垃圾
- 并发清除:清除被标记的对象
- cms出现的问题
- 并发虽然不会暂停用户线程,但是会抢占一部分线程,造成用户线程变慢,当cpu比较少时,对用户线程的性能影响大
- 无法清除浮动垃圾:在并发清理时,用户线程还在执行,还会制造新的垃圾
- 并发失败:因为gc线程和用户线程同时执行,所以要预留一部分内存空间给用户线程,回收垃圾用
- 内存碎片问题:cms使用标记清除算法,就会有空间碎片的出现
17、G1的回收过程
- cms可以并发标记和并发处理,但是他依然有stop the world这一步骤,所以就出现了G1,目的是降低stop the world的停顿时间,G1可以回收新生代和老年代垃圾,不会分代,直接将内存划分为大小相等的region区域
18、Java对象创建过程
- Java虚拟机识别到new这条指令,去常量池中看能不能找到这个类的符号引用,看这个符号引用的类是否已经被加载,检查,准备,解析,初始化,如果没有执行加载的过程
- 给对象开辟内存空间
- 指针标记法:内存中使用过的站一边,空闲的站一边,用指针标记这中间的位置,指针移动的大小就是对象的内存大小
- 空闲列表法:内存中用过的和空闲的是混杂在一起的,JVM中维护了一个空闲列表,找一个合适的空间分配给对象实例
- 给对象的内存空间设置初始化值
- JVM会把对象的哈希值,元数据信息,gc分代年龄设置到对象头中
- 最后调用init初始化方法初始化