JVM架构
双亲委派机制
类装载器采用的机制是双亲委派模式:当某个类加载器需要加载某个.class
文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
BootstrapClassLoader启动类加载器:
c++
编写,加载java
核心库 java.*
,构造ExtClassLoader
和AppClassLoader
。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
ExtClassLoader标准扩展类加载器
java
编写,加载扩展库,如classpath
中的jre
,javax.*
或者java.ext.dir
指定位置中的类,开发者可以直接使用标准扩展类加载器。
AppClassLoader系统类加载器
java
编写,加载程序所在的目录,如user.dir
所在的位置的class
CustomClassLoader用户自定义类加载器
java
编写,用户自定义的类加载器,可加载指定路径的class
文件
类加载器测试
public class Car {
public int age;
public static void main(String[] args) {
Car car = new Car();
System.out.println(car);
Class<? extends Car> aClass = car.getClass();
System.out.println(aClass);
ClassLoader classLoader = aClass.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader
System.out.println(classLoader.getParent());//sun.misc.Launcher$ExtClassLoader jre8\lib\ext
System.out.println(classLoader.getParent().getParent());//null rt.jar即root.jar
}
}
双亲委派机制源码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
作用
1、防止重复加载同一个.class
。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class
不能被篡改。通过委托方式,不会去篡改核心.clas
,即使篡改也不会去加载,即使加载也不会是同一个.class
对象了。不同的加载器加载同一个.class
也不是同一个Class
对象。这样保证了Class
执行安全。
沙箱安全机制
Java安全模型的核心就是Java沙箱(sandbox),沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问:CPU、内存、文件系统、网络
。
Native(JNI)
进入本地方法栈Native Method Area/Stack
public class Thread implements Runnable {
public synchronized void start() {
try {
start0();
}
}
private native void start0();
}
方法区
静态变量static、常量final、类信息Class(构造方法、接口定义)、运行时的常量池,都保存在方法区中,实例变量保存在堆内存中
栈
一种数据结构,先进后出。(main方法先执行,后结束)(区别于队列的先进先出FIFO)
存放的内容:基本数据类型、对象引用、实例方法
运行原理:栈帧,程序正在执行的方法一定在栈的顶部,栈溢出会报错StackOverFlowError
三种JVM版本
- Sun公司的HotSpot
- Oracle的JRockit
- IBM的j9 VM
堆
- Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的
- 存放对象的实例
- 堆里边还分三个区域:新生区(伊甸园区+幸存0区+幸存1区)、养老区、永久区(jdk8以后叫元空间)
- 轻GC、重GC(full GC)
- 垃圾回收主要在 伊甸园区 和 养老区
- OOM:java.lang.OutOfMemoryError: Java heap space
永久区
这个区是常驻内存的,用来存放Jdk自身携带的Class对象、Interface元数据,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收,关闭虚拟机释放。
- jdk1.6:永久代,常量池是在方法区
- jdk1.7:永久代,但是慢慢退化了,
去永久代
,常量池在堆中(方法区) - jdk1.8:无永久代,常量池在元空间(方法区),逻辑上存在
JVM调优
public class Test {
public static void main(String[] args) {
long max = Runtime.getRuntime().maxMemory();//虚拟机试图使用的最大内存 默认电脑内存的1/4
long total = Runtime.getRuntime().totalMemory();//jvm初始化总内存 默认电脑内存的1/64
System.out.println("max:"+max+"byte,"+max/(double)1024/1024+"MB");
System.out.println("total:"+total+"byte,"+total/(double)1024/1024+"MB");
//OOM调试jvm调优:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
}
}
项目中定位OOM的工具:Jprofiler(idea+windows)、MAT(eclipse)
- 能够看到代码第几行出错,内存快照分析
- 分析Dump内存文件,快速定位内存泄漏
- 获得堆中的数据
- 获得大的对象
import java.util.ArrayList;
//-Xms设置初始化内存分配大小 1/64
//-Xmx设置最大内存分配 1/4
//-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
public class Demo03 {
byte[] bytes = new byte[1024 * 1024];
public static void main(String[] args) {
ArrayList<Demo03> list = new ArrayList<>();
int count = 0;
try {
while (true) {
list.add(new Demo03());
count++;
}
} catch (Exception e) {
System.out.println("count:"+count);
e.printStackTrace();
}
}
}
垃圾回收
GC的作用区域:方法区和堆
主要是回收新生代
- 新生代
- 幸存区(from,to)
- 老年区
GC有两种:轻GC,重GC(全局GC)
GC算法
引用计数法
计数为0的淘汰
缺点计数器成本
复制算法
GC执行时,将非垃圾对象复制到另一块内存空间中,并且保证内存上的连续性,然后直接清空之前使用的内存空间(from复制到to,谁空谁是新的to。)
好处无内存碎片,坏处也是显而易见的,直接损失了一半的可用内存。最佳实践:存货对象少,垃圾对象多的情况下,非常高效
标记清除法
扫描标记不可达对象,清除不可达对象
缺点两次扫面浪费时间+内存碎片,优点是不需要额外的空间
标记压缩
标记清除的方式之上,整理碎片
总结
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记清除算法>标记压缩算法
内存利用效率:标记压缩算法=标记清除算法>复制算法
没有最好的算法,只有最合适的算法:分代收集
年轻代,存货率低:复制算法
老年代,存货率高:标记清除(碎片不是太多)+标记压缩
JMM
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。