1.class文件在内存中是如何存储的?
磁盘中的class文件通过类加载子系统加载后生成class content存储在操作系统内存中,class content通过class文件规范解析生成instanceKlass存储在方法区中,同时生成instanceMirrorKlass镜像类存储在堆区,Class对象就是instanceMirrorKlass类的实例。
拓展:
方法区、元空间、永久代三者的关系
- 方法区是理论规范,永久代、元空间是具体实现
- 在jdk7以前永久代是方法区的具体实现,放在堆区管理
- 在jdk8开始元空间是方法区的具体实现,放在操作系统内存(直接内存)管理
2.JDK8为什么要使用元空间替换永久代
- 字符串存储在永久代中容易出现性能问题和内存溢出(字符串常量池存储在永久代中)
- 类及其方法信息的大小比较难确定,因此对于永久代大小的指定比较困难,太小容易出现永久代溢出,太大容易出现老年代溢出
- 永久代会为gc带来不必要的复杂度,而且效率偏低(永久代的回收和老年代的回收是绑定在一起的)
3.什么是类加载器,类加载器有哪些?
实现通过类的全限定名获取类的二进制字节流的代码块就是类加载器,类加载器本身就是一个类。
四种类加载器:
- 启动类加载器(Bootstrap ClassLoader) :用来加载Java核心类库,无法被Java程序直接引用。
- 扩展类加载器(Extensions ClassLoader):用来加载Java的扩展库,Java虚拟机的实现会提供一个扩展库目录,该类加载器从此目录里查找并加载相应的类。
- 系统类加载器(System ClassLoader):根据Java应用的类路径来加载Java类,一般Java应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。
- 用户自定义类加载器:通过继承ClassLoader类的方式实现。
拓展
类装载方式
- 隐式装载:程序在运行过程中遇到new等方式生成对象时,隐式调用类装载加载相应的类到jvm中。
- 显示装载:class.forName()等方法,通过类的全限定名装载相应的类到虚拟机中
4. 双亲委派机制
任意一个类的唯一性都是由该类本身和加载它的类加载器共同确立的。每一个类加载器都有独立的类名称空间,类加载器根据指定全限定名将class文件加载到jvm内存中。
双亲委派模型:一个类需要被加载时,不考虑自定义类加载器的情况下,先由系统类加载器判断是否加载过,如果有就无需加载了,如果没有,会拿到父类加载器,进行相同的判断,一直委托到Bootstrap ClassLoader。当父类加载器在自己负责加载的范围中没有找到相应的类时会下沉给子类去加载,找到的话直接加载。
5. jvm调优工具
- jconsole : 用于对jvm内存、线程和类进行监控
- jvisualvm:jdk自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、gc变化等。
6.常用虚拟机调优参数
- -Xms2g:初始化堆大小为2g;
- -Xmx2g:堆最大内存为2g;
- -XX:+PrintGC: 开启打印GC信息;
- -XX:+PrintGCDetails:打印GC详细信息;
- -XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
- -XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;