类加载机制
- JVM用类存储加载的类信息、常量、静态变量、编译后的代码等数据
- 虚拟机规范中这是一个逻辑区划,具体实现根据不同虚拟机来实现
如:oracle的HotSpot在java7中方法区放在永久代,java8放在元数据空间,并且通过GC机制对这个区域进行管理
类生命周期
从加载到卸载
- 加载 读取二进制内容
- 验证 验证class文件格式规范、语义分析、引用验证、字节码验证
- 准备 分配内存、设置类static修饰的变了初始值
- 解析 类、接口、字段、类方法等解析
- 初始化 为静态变了赋值、执行静态代码块
- 使用 创建实例对象
- 卸载 从JVM方法区中卸载
类加载器
- 类加载器负责装入类,搜索网络、jar、zip、文件夹、二进制数据、内存等指定位置的类资源
一个java程序运行,最少有三个类加载器实例,负责不同的加加载
类加载器-
Bootstrap Loader 核心类库加载器
- C/C++实现,无对应java类:null
- 加载JRE_HOME/jre/lib目录,或用户配置的目录
- JDK核心类库 rt.jar ... String..
-
Extension Class Loader 拓展类库加载器
- 加载JRE_HOME/jre/lib/ext目录,JDK拓展包,或用户配置的目录
-
Application Class Loader 用户应用程序加载器
- 加载java.class.path指定的目录,用户应用程序class-path 或者java命令运行时参数 -cp..
相关问题引导
- 查看类对应的加载器
- JVM如何指定我们的类在何方
- 类不会重复加载
- 类的卸载
- 双亲委派模型
查看类对应的加载器
/**
* 查看类的加载器实例
*/
public class ClassLoaderView {
public static void main(String[] args) throws Exception {
// 加载核心类库的 BootStrap ClassLoader
System.out.println("核心类库加载器:"
+ ClassLoaderView.class.getClassLoader().loadClass("java.lang.String").getClassLoader());
// 加载拓展库的 Extension ClassLoader
System.out.println("拓展类库加载器:" + ClassLoaderView.class.getClassLoader()
.loadClass("com.sun.nio.zipfs.ZipCoder").getClassLoader());
// 加载应用程序的
System.out.println("应用程序库加载器:" + ClassLoaderView.class.getClassLoader());
// 双亲委派模型 Parents Delegation Model
System.out.println("应用程序库加载器的父类:" + ClassLoaderView.class.getClassLoader().getParent());
System.out.println(
"应用程序库加载器的父类的父类:" + ClassLoaderView.class.getClassLoader().getParent().getParent());
}
}
JVM如何指定我们的类在何方
jvm 命令 查看类加载信息由上到下
jcmd :查看有哪些进程
jcmd -help : 帮助
jcmd pid (pid 是进程ID) help : 查看找执行当前进程的命令
jcmd pid VM.system_properties :查看属性配置
类不会重复加载
- 在循环外面加载一次后,不会再加载
- 循环里面会加载-因为循里面Loader每次都是新的
import java.net.URL;
import java.net.URLClassLoader;
/**
* 指定class 进行加载e
*/
public class LoaderTest {
public static void main(String[] args) throws Exception {
URL classUrl = new URL("file:D:\\");//jvm 类放在位置
// URLClassLoader loader = new URLClassLoader(new URL[]{classUrl});
while (true) {
// 创建一个新的类加载器
URLClassLoader loader = new URLClassLoader(new URL[]{classUrl});
// 问题:静态块触发
Class clazz = loader.loadClass("HelloService");
System.out.println("HelloService所使用的类加载器:" + clazz.getClassLoader());
Object newInstance = clazz.newInstance();
Object value = clazz.getMethod("test").invoke(newInstance);
System.out.println("调用getValue获得的返回值为:" + value);
Thread.sleep(3000L); // 1秒执行一次
System.out.println();
// help gc -verbose:class
newInstance = null;
loader = null;
}
// System.gc();
}
}
类的卸载
上述代码中 System.gc(); 为卸载类操作;loader需要放置在循环内部才会触发卸载
双亲委派模型
为了避免重复加载,由下到上逐级委托,由上到下逐级查找
- 首先不会自己去尝试加载类,而是把这个请求委派给父加载器去完成;
- 每一层次的加载器都是如此,因此所有的类加载请求都会传给上层的启动类加载器
- 只有当父加载器反馈自己无法完成该加载请求时,子加载器才会尝试自己去加载
import java.net.URL;
import java.net.URLClassLoader;
/**
* 双亲委派机制
*/
public class LoaderTest1 {
public static void main(String[] args) throws Exception {
URL classUrl = new URL("file:D:\\");
// 测试双亲委派机制
// 如果使用此加载器作为父加载器,则下面的热更新会失效,因为双亲委派机制,HelloService实际上是被这个类加载器加载的;
URLClassLoader parentLoader = new URLClassLoader(new URL[]{classUrl});
while (true) {
// 创建一个新的类加载器,它的父加载器为上面的parentLoader
URLClassLoader loader = new URLClassLoader(new URL[]{classUrl}, parentLoader);
Class clazz = loader.loadClass("HelloService");
System.out.println("HelloService所使用的类加载器:" + clazz.getClassLoader());
Object newInstance = clazz.newInstance();
Object value = clazz.getMethod("test").invoke(newInstance);
System.out.println("调用getValue获得的返回值为:" + value);
// help gc
newInstance = null;
value = null;
System.gc();
loader.close();
Thread.sleep(3000L); // 1秒执行一次
System.out.println();
}
}
}
垃圾回收机制
内存回收-标记
内存回收第一步需要标记,标记哪些是需要被回收的内存
不同类型内存的判断机制
- 对象回收-引用计数
- 对象回收-可达性分析
- 方法区回收
可达性分析算法
将对象及其引用关系看做一个图,选定活动的对象作为GC Roots
每个GC Root 都会去检测内存中是否还存在引用
引用行和可达性级别
引用类型
- 强引用(StrongReference):最长久的普通对象引用,只要还有强引用指向一个对象,就不会回收
- 软引用(SoftReference):JVM认为内存不足时,才会去试图回收软引用指向的对象。(缓存场景)
- 弱引用(WeakRefrence):虽然是引用,但随时可能被回收
- 虚引用(PhantomReference):不能通过它访问对象。供了对象呗finalize以后,执行指定逻辑的机制(Clenaner)
可达性级别
- 强可达(Strongly Reachable):一个对象可以有一个或多个线程可以不通过各种引用访问到的情况
- 软可达(Softly Reachable):就是当我们只能通过软引用才能访问到的对象状态
- 弱可达(Weakly Reachable):只能通过弱引用访问时的状态。当弱引用被清除的时候,就符合销毁条件
- 幻想可达(Phantom Reachable):不存在其他引用,并且finalize过了,只有幻想引用指向这个对象
- 不可达(unreachable):意味着对象可以被清除了
垃圾收集算法
-
标记-清除(Mark-Sweep)算法
手续标识出所有要回收的对象,然后进行清除;标记清除过程效率有限,并有内存碎片化问题,不适合特别大的堆; -
复制(Copying)算法
划分两块同等大小的区域,收集时将活着的对象复制到另一块区域,拷贝过程顺序放置,可避免内存碎片化问题;复制+预留内存,有一定的消耗浪费资源 -
标记-整理(Mark-Compact)
类似于标记-清除,但为了避免内存碎片化,它会在清理过程中将对象移动,以确保移动后的对象占用连续的内存空间
分代收集
根据对象存活周期,将内存划分为几个区域,不同区域采用适合的垃圾收集算法
新的对象会分配到Eden,如果超过-XX:PretenureSizeThreshold:设置大对象直接进入老年代的阀值
-
新生代
新生代有3个区域;Eden与Form采用复制算法;To采用标记-整理算法;每次执行算法后会记录内存的等级,等级到达一定会进入老年代 -
老年代
老年代采用标记-整理算法;大对象会直接进入到老年代;
分代收集
垃圾收集器
- 串行收集器
单个线程来执行垃圾回收,新生代 与 老年代
-
并行收集器
与串行收集器的区别就是它是采用多线程执行的,并且可以设置GC时间和吞吐量等值 -
并发收集器
专用老年代,基于标记-清除算法
-
JDK9后默认为并发收集器 G1 -XX:+UseG1GC
可以有效的避免内存随便。新生代老年代找不到大内存时执行的FullGC
垃圾收集器组合
-
目前JVM默认的垃圾收集器组合为
新生代-PrallelScavenge + 老年代-Parallel Old -
通常的调优采用方式:
新生代-ParNew + 老年代-CMS
JDK内置命令工具
-
javap 主要用于根据Java字节码文件反编译Java源代码文件
javap <options><classes>
-
jstack能得到运行java程序的java stack和native stack的信息。可以轻松得知当前线程的运行情况
-
jstat
-
jcmd 可代替jps工具查看本地的jvm信息
-
jinfo 查看运行中jvm的全部参数,还可以设置部分参数
-
jmap 打印出java内存中的Object的情况;或将JVM中的堆以二进制输出成文本
Jconsole 工具
JDK自带工具,在java JDK bin 目录下
jconsoleJvisualvm 工具
JDK自带工具,在java JDK bin 目录下,相比Jconsole更加灵活,可安装插件
Jvisualvm Jvisualvm-GC
网友评论