JVM
一、概述
- JVM和操作系统的关系:
- JVM体系结构:
程序计数器:选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
二、JVM类加载器&双亲委派机制
- 作用:加载class文件
public class Student {
public static void main(String[] args) {
// 实例化
Student student1 = new Student();
Student student2 = new Student();
Student student3 = new Student();
// 三个不同的对象
System.out.println(student1.hashCode());
System.out.println(student2.hashCode());
System.out.println(student3.hashCode());
// 获取类加载器
Class<? extends Student> aClass = student1.getClass();
System.out.println(aClass.getClassLoader()); // AppClassLoader(应用类加载器)
// 获取父类加载器
System.out.println(aClass.getClassLoader().getParent()); // ExtClassLoader(扩展类加载器):位置jre/lib/ext
// 获取父类的父类加载器
System.out.println(aClass.getClassLoader().getParent().getParent()); // null:java获取不到
}
}
2. 双亲委派机制
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
1)BootStrapClassLoader:启动类加载器,该ClassLoader是jvm在启动时创建的,用于加载 JAVA_HOME/jre/lib下面的类库;
2)ExtClassLoader:扩展类加载器,JAVA_HOME/jre/lib/ext下的类库;
3)AppClassLoader:应用程序类加载器,会加载java环境变量CLASSPATH所指定的路径下的类库;
4)CustomClassLoader:自定义类加载器,该ClassLoader是指我们自定义的ClassLoader。
比如定义一个Student类:
public class Student{
@Override
public String toString(){
return "Hello!"
}
public static void main(String[] args) {
Student s = new Student();
System.out.println(s.toString()); // Hello!
System.out.println(s.getClass().getClassLoader());// sun.misc.Launcher$AppClassLoader@
}
}
// 先去启动类加载器找,发现没有;再去扩展加载器还是没有;最终在应用加载器获取到了toString,成功加载
双亲委派机制怎么保证安全的?
我们自己定义一个lang包:java.lang
package java.lang
public class String{
public String toString(){
return "Helllo";
}
public static void main(String[] args) {
String s = new String();
System.out.println(s.toString());
}
}
// 结果:错误: 在类 java.lang.String 中找不到main方法
原因:由于双亲委派机制,会去 rt.jar
找 java.lang.String,当然直接找到了,所以自己定义的该 String 类永远不会被调用!
- 总结:
- 类加载器收到类加载的请求;
- 一直向父类加载器委派,直到启动类加载器;
- 如果父级加载器可以加载,就使用当前加载器,加载结束;若不能加载抛出异常,会交由子级加载器完成;
- 重复上一步
三、沙箱安全机制
访问本地代码:
当远程代码需要使用操作系统和本地资源时,在jdk1.1,加入安全策略保证访问远程代码的安全性;jdk1.2加入代码签名保证安全;jdk1.6引入域的概念。
- 沙箱组件:
- 字节码校验器:确保类文件遵循java编写规范;
- 类装载器:双亲委派机制保证恶意代码干涉;
四、native关键字&方法区内存
- Java平台有个用户和本地C代码进行互操作的API,称为Java Native Interface (Java本地接口即JNI)。
使用native
关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用;这些函数的实现体在DLL中,JDK的源代码中并不包含。 - 例如:创建一个Teacher teacher = new Teacher();
(1):Class Teacher类通过类加载器,成为Teacher.class文件进内存;
(2):在栈内存为Teacher开辟空间,并分配地址;
(3):在堆内存为teacher对象开辟空间;
(4):在堆内存对teacher对象的成员变量进行默认初始化;
(5):对老师对象的成员变量进行显示初始化;
(6):通过构造方法对老师对象的成员变量赋值;
(7):老师对象初始化完毕,把对象地址赋值给teacher变量
五、栈
-
“喝多了吐就是栈,吃多了拉就是队列”
-
为什么main()先执行,最后结束?
栈:栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题;
一旦线程结束,栈就Over! -
栈运行原理:栈帧
-
栈满了:StackOverflowError
六、堆
- 一个jvm只有一个堆,堆的大小可以手动调节;
堆存储的类型:类、方法、常量、变量、所有引用类型的真实对象; - 堆的分类:
- 伊甸区:所有对象的出生的地方,也有可能是对象“死亡”的地方;
- 永久区:常驻内存,用来存放 jdk 自己携带的class,或类信息,当VM关闭时,会释放此处的内存;不存在垃圾回收;当一个启动类加载了太多的第三方jar包,或Tomcat部署了太多应用,或大量动态生成的反射类,或导致OOM;
元空间也称为非堆:因为它逻辑上存在,物理上不存在;新生区(YoungGen) + 老年代(OldGen) = 堆内存 - 堆内存溢出:java.lang.OutOfMemoryError:java heap space(OOM)
- VM堆内存
- 修改堆内存大小:将初始内存和最大内存都改为 1024MB,并打印信息
VM options : -Xms1024m -Xmx1024m -XX:+PrintGCDetails
- 分析oom原因:使用JProfiler工具
JProfiler:
是一款检查和跟踪系统Java程序性能的工具,JProfiler可以通过时时的监控系统的内存使用情况,随时监视垃圾回收,线程运行状况等手段,从而很好的监视JVM运行情况及其性能。
IDEA下载插件:
客户端:https://www.ej-technologies.com/download/jprofiler/files
IDEA配置:
设置一个较小的堆内存:
VM options : -Xms1024m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError
参数:-Xms
设置初始化内存分配大小(默认为:1/64);-Xmx
设置最大分配内存(默认为1/4)
当程序出现OutOfMemoryError:就会在JProfiler安装目录下 生成一个文件:.hprof文件,双击打开即可,可以查看到所占内存较大的对象、cpu使用率、线程等相关信息;
排查过程:1. 查看较大内存的对象;2. 查看线程信息,精准定位到oom之处
七、GC(垃圾回收机制<堆内发生>)
- 分类:轻GC、重GC
- 引用计数法:将对象的使用次数进行统计,然后会将引用次数少的进行回收
- 复制算法:幸存区分为(from、to),从伊甸园活下来的对象会进入幸存区,两个幸村区是动态的,会交换对象,即从from–>to;
- GC一次的新生区变化:(深绿色表示有对象在内存中)
将伊甸区GC到幸存to(因为to空),from的也会转移到to
此时from变to,to变from:
15次GC都没死亡的对象,进入养老区:
- 标记清除算法(老年代主要的算法):
缺点:两次扫描浪费时间,存在内存碎片,
优点:不用额外的空间 - 标记压缩算法:(老年代主要的算法,对标记清除算法的优化)
总结: