目录索引
1.类加载器
1.1类生命周期
1.2三种类加载器
如图所示,有三个类加载器。加载顺序从上至下,顺序寻找。也就是说当所有的父级加载器中找不到的时候,才会去子级中寻找
- BootstrapClassLoader:用于加载jre/lib中的jar包,这里都是java的核心类,且最核心包为 rt.jar
- ExtClassLoader:加载的是jir/lib/ext目录下的jar包,用于加载java的扩展类
- AppClassLoader:加载我们自己开发的类或项目开发用到的第三方jar包,位于我们项目的目录下,比如WEB-INF/lib目录。同时也会加载我们所写的程序。一般的,可以通过指令去查看对应的系统配置。
- 使用jps查看所有正在运行的java进程,使用jcmd可以查看对应进程的详细信息,最终可以找到一个VM.system_propertites的属性,里面会显示我们自己的类的具体位置。
1.3类加载的特性
1.3.1类不会重复加载
同一个类加载器的实例不会重复加载同一个类,可以通过代码验证(再循环外面定义一个类加载器,然后在循环里面不停地去尝试加载同一个类,会发现最终拿到的都是相同的结果,即使运行期间将原代码修改了),这里不具体展示了。
1.3.2双亲委派机制
注意这里的双亲不是指的父亲母亲,可以简单认为指的是儿子,父亲,爷爷这样一个关系。双亲委派模型又可以理解为败家子模型。
也就是说:当要去加载一个类的时候,会从下级不断的将加载的任务往上一级抛,看是否有加载器可以加载,直到最顶级的时候,如果没有一个表示能够加载,那么,就一级一级向下传导,让最低一级的加载器加载。
只有上级有任意一个加载器能够加载这个类,那么就会直接处理掉,不会让低级加载器去加载。
1.3.3沙箱安全机制
沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问
,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
所有的Java程序运行都可以指定沙箱,可以定制安全策略。
组成沙箱的组件:
字节码校验器
:确保java类文件遵循java语言规范类装载器
- 它防止恶意代码去干涉善意代码(双亲委派机制)
- 它守护了不信任的类库边界
- 它将代码归入保护域,确定了代码可以进行的操作
2.垃圾回收机制
2.1概念
垃圾回收粗的来看有下面两种,但是我们一般说的都是对象的回收,方法的回收一般不会有什么问题。
- 对象回收
发生在堆内存空间中,也是jvm中最大的一块区域。
- 方法回收
方法的回收其实就是类卸载。类卸载的条件:
所有引用,以及对应的类加载器都为null
。要想观察类卸载的过程,可以再启动参数中加入-verbose.class
在对象的回收中主要有两个概念,那就是什么是垃圾。主要有两种方式去判断,在jvm中实现的是第二种(因为第一种可能存在循环依赖导致垃圾无法回收)
- 方式一:引用计数算法
就是对每一个创建的对象在其对象头都会有一片空间保存着一个计数器,用于记录被引用的次数,当计数器的值为 0 的时候就表示该对象可以被回收了
- 方式二:可达性分析
但是对于那些类似于连个对象相互引用的情况来说,这种引用计数的方式就没法回收他了。因此需要通过可达性分析的方式判断是否为垃圾。通过一些被称为引用链的方式向下搜索,搜索走过的路径当一个对象到 GC Roots 没有任何引用链相连时,则证明该对象是不可用的。
那些是GC Roots
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
引用类型
- 强引用:最常见,比如new一个对象,不会回收。
- 软引用:jvm认为内存不足时会试图去回收它。(缓存场景)
- 弱引用:随时可能被回收
- 虚引用:不能通过它访问对象,供对象被finalize以后,执行指定逻辑的机制(Cleaner)
可达性级别
- 强可达:一个对象可以通过一个或多个线程可以通过各种引用访问到的情况
- 软可达:只能通过软引用才能访问
- 弱可达:只能通过弱引用才能访问
- 幻想可达:只能通过虚引用才能访问
- 不可达: 没有任何引用
2.2jvm运行时区
简图如下:
官方详细图:
方法区
JVM用来存储加载的类信息(类的全限定名,访问修饰符等),常量,静态变量,编译后的代码(class)等数据。虚拟机规范中这是一个逻辑区划。具体事项根据不同的虚拟机来实现。
如:oracle的HotSpot在java7中方法区放在永久代,java8放在元数据空间,并且通过GC机制对这个区域进行管理
堆内存
它还可以细分为 老年代,新生代,JVM启动时创建,存放对象的实例。垃圾回收器主要是管理堆内存。如果满了,就会出现OutOfMemoryError。
虚拟机栈
每个线程都在这个空间有一个私有空间。线程栈由多个栈帧组成。一个线程会执行一个或多个方法,一个方法对应一个栈帧。
栈帧内容包含:局部变量表,操作数栈,动态链接,方法返回地址,附加信息。
栈内存默认最大是1M,超出则抛出StackOverflowError
本地方法栈
和虚拟机栈功能类似,虚拟机栈是为虚拟机执行JAVA方法而准备的,本地方法栈是为虚拟机使用Native本地方法而准备的。
虚拟机规范没有规定具体的实现,由不同的虚拟机厂商去实现,HotSpot虚拟机中虚拟机栈和本地方法栈的实现方式一样,同样的,超出大小后也会抛出StackOverflowError。
程序计数器
记录当前线程执行字节码的位置,存储的是字节码指令地址,如果执行Native方法,则计数器值为空。
每个线程都在这个空间有一个私有空间,占用内存空间很少。CPU同一时间,只会执行一条线程中的指令,JVM多线程会轮流切换并分配CPU执行时间的方式。为了线程切换后,需要ton过程序计数器,来回复正确的执行位置。
2.3堆内存空间
堆内存分为新生代,老年代和永久代
jdk1.6之前:永久代,常量池在方法区
jdk1.7 :永久代,但是慢慢的退化了,去永久代,常量池在堆中
jdk1.8之后:无永久代,常量池存在元空间
内存空间大小占比
新生代:老年代 = 2:8
Eden:S0:S1 = 8:1:1
2.4垃圾收集算法
- 轻GC(普通的GC)Minor GC
一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
- 重GC(全局GC)Full GC
**对整个堆进行整理,**包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:
- 1.年老代(Tenured)被写满
- 2.持久代(Perm)被写满
- 3.System.gc()被显示调用
- 4.上一次GC之后Heap的各域分配策略动态变化
垃圾清除主要有以下三种算法
标记清除法
将垃圾进行标记,然后将所有标记内容清除。两次扫描,会浪费时间,产生内存碎片;但是它不需要额外的空间
标记压缩法
在标记清除的基础上再加了一层扫描,将清楚后的内存空间进行整理,解决的就是内存碎片的问题
复制算法
主要用于年轻代,比如将S0的内容全部复制到S1,然后全部清除S0的内容
2.5垃圾收集器
- 1.Serial 串行收集器(单核,new+old)
用于单核处理器stop-the-world,单线程,停止等待垃圾回收的时间。client模式下jvm默认选项
分为新生代(复制算法)和老年代(标记整理算法)
响应时间优先
- 2.Parallel 并行收集器(new+old)
stop-the-world的停止时间依旧存在,只不过很短。server模式下jvm默认选项
分为新生代(复制算法)和老年代(标记-整理算法)
吞吐量优先,jvm默认
- 3.CMS 并发收集器(old)
用户线程一起运行去做gc,减少了停顿时间。
专用与老年代 (标记清除) 算法
响应时间优先,(新版本中被废弃,建议使用G1)
- 4.ParNew 并行收集器(new)
实际上是Serial GC的多线程版本,可控制线程数量
新生代GC实现
响应时间优先
- 5.G1 并发收集器(new/old)
g1将堆分为固定大小的区域,region之间是复制算法,但整体上实际可看做 标记-整理算法
兼顾吞吐量和响应时间