类加载器类别:
- 虚拟机自带加载器
- 启动类(根)加载器
- 扩展类加载器
- 应用程序加载器
类加载器作用:
- 加载Class文件,生成类(Class)在方法区
双亲委派机制:
- 类加载器收到类加载请求(new String(); )
- 将这个请求向上委托
- 类加载器加载类过程:应用程序加载器>扩展类加载器>根加载器
- 如果根加载其中不存在该类抛出异常,就到扩展类加载器中加载,不存在该类抛出异常,最后到应用程序加载器(自己写的类)中加载。
- 目的:安全,防止篡改(重写String类)
沙箱安全机制:
- 沙箱是一个限制程序运行的环境,将java代码限定在JVM特定运行范围内,并严格控制代码堆本地资源的访问。
- 沙箱主要限制系统资源访问(cpu,内存,文件系统,网络)
组成沙箱基本组件:
- 字节码校验器:(在编译后,由类加载器加载到jvm中)检查变量有没有初始化,有没有违背私有数据,方法调用等规则。
- 类加载器:防止恶意干涉善意代码(双亲委派机制)。守护被信任的类库边界。将代码归入保护域,确定代码可以进行哪些操作(沙箱安全机制)。
关于native关键字:
- 凡是带了native关键字的,说明java范围达不到,调用了底层c语言的库
- 该方法会进入本地方法栈,调用本地方法接口(JNI)执行
程序计数器(PC寄存器):
- 每一个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中下一步要执行的字节码(在多线程情况下该线程被挂起下次继续执行代码的位置),内存空间非常小。
- 当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成
方法区(唯一):
- 静态变量(static),常量(final),类信息(Class)(构造方法,接口定义),运行时的常量池(区别字符串常量池)存在方法区中。
- jdk1.8之前逻辑上属于(物理上不属于)堆,实际上和堆其他区域隔离开,1.8后直接在jvm之外了(在 本地内存中)。
堆(唯一):
- 注意:堆包括方法区
- 物理地址不连续
- 堆大小可以调节,在运行期可以调节
- 对象实例和数组在堆内存中。
- 字符串常量池在堆中
- 堆内存分为三个区域:新生区,老年区,永久区(jdk8以后叫元空间),GC垃圾回收主要是在新生区和养老区。
- OOM:堆内存溢出,长生命周期对象持有短生命周期对象的引用,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收
字符串常量池存在堆中(*):
-
一般放字符串,String a="abc",a放在常量池,String b="abc",此时会检查常量池是否存在“abc"存在则b指向其,Srtring c=new String("abc"),c存在堆中。
java栈(不唯一):
- 物理地址连续,性能快,内存大小在编译器就确认
- 每一个线程都包含一个栈区,是该线程私有
- 每一个栈帧都有自己的局部变量表、操作数栈和指向当前方法所属的类的运行时常量池的引用
- 一个局部变量可以保存一个类型为boolean、byte、char、short、int、float、reference类型的数据。reference类型表示对一个对象实例的引用
- 操作数栈:当一个方法刚刚开始执行时,其操作数栈是空的,随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。一个完整的方法执行期间往往包含多个这样出栈/入栈的过程
- 动态链接:一个方法要调用其他方法,需要将这些方法的符号引用(比如list.add(),add()就是符号引用,真正需要的是方法区中的方法)转化为其在内存地址中的直接引用,符号引用存在于方法区中的运行时常量池,把这个符号引用转化过程叫动态链接。(每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用)
- 方法出口:这个方法执行完返回到哪(比如主函数调用一个方法,这个方法执行完返回到主函数哪行)
- 栈存放:局部变量表(索引),操作数栈(操作数栈可理解为java虚拟机栈中的一个用于计算的临时数据存储区),返回结果。
- 栈主管线程的运行(方法的调用),生命周期和线程同步,线程结束(线程每调一个方法,就有一个栈帧入栈),对应的栈内存释放。
- java栈中保存的主要内容是栈帧,栈帧是java具体方法存储数据的地方。每一次函数调用都会有对应的栈帧被压进去java栈,执行完毕的时候被弹出java栈
- 栈不存在垃圾回收问题。
- 栈中有:8大基本类型,对象引用(地址指向堆中),方法索引
GC:垃圾回收(Gabage Collection)
- 分两类:轻GC(发生在新生区),重GC(全局GC)(发生在老年区)
- 三种算法:标记清除法,标记压缩,复制算法,引用计数法(了解)
- 复制算法:每次轻GC都会将伊甸园区中活下来对象放入幸存区中(to区),将from区对象也放入to区,清空伊甸园区和to区(此时to区变成from区,from区变成to区),伊甸园区满了再清理,伊甸园区幸存对象和from区对象放入to区,清空from和伊甸园区,一个对象经过15次(默认)轻GC后放入老年区
- 复制算法好处:没有内存碎片,坏处:浪费内存空间(to区)
- 复制算法使用场景:对象存活率较低时使用,新生区使用复制算法。
- 标记清楚算法:标记阶段会通过可达性分析将不可达的对象标记出来(下图文字中应该是对标记的对象清除),清除阶段会将标记阶段标记的垃圾对象清除。
- 可达性分析:GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。引用指向对象1,对象1调用其他对象2又有指向2,当这个引用存在,对象2不能被标记,引用不存在则对象1,2都被标记。
- 标记清除算法缺点:两次扫描严重浪费时间,会产生内存碎片,优点:不需要额外的空间
- 标记清除压缩算法:对标记清除算法优化,防止内存碎片产生,多了一个移动成本
- 引用计数法:给每个对象分配一个计数器,清理最少使用的
- 时间复杂度:复制算法>标记清除算法>标记压缩算法
- 内存整齐度:复制算法=标记清除算法>标记压缩算法
- 内存利用率:标记清除算法=标记压缩算法>复制算法
现在GC用的是分代收集算法:新生区,存活率低使用复制算法,老年区存活率大,使用标记清除+标记清除压缩算法(在多少此标记清除后使用标记清除压缩提高内存利用率)
常用的 JVM 调优的参数有:
- -Xms2g:初始化推大小为 2g;
- -Xmx2g:堆最大内存为 2g;
- -XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
- -XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
- –XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
- -XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
- -XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
- -XX:+PrintGC:开启打印 gc 信息;
- -XX:+PrintGCDetails:打印 gc 详细信息。
说一下类装载的执行过程?
类装载分为以下 5 个步骤:
- 加载:根据查找路径找到相应的 class 文件然后导入;
- 验证:检查加载的 class 文件的正确性;
- 准备:给类中的静态变量分配内存空间;
- 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;
- 初始化:对静态变量和静态代码块执行初始化工作。