1、作用:
解析字节码文件(class文件),转化成相应操作系统执行的机器指令并提交执行
输入:class文件
输出:相应操作系统的机器指令
2、JVM内存模型(基于Jdk1.8),jdk1.7版本中元空间则是方法区
字面量
字面量就是指由字母、数字等构成的字符串或者数值常量
字面量只可以右值出现,所谓右值是指等号右边的值,如:int a=1 这里的a为左值,1为右值。在这个例子中1就是字面
量。
1 int a = 1 ;
2 int b = 2 ;
符号引用
符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量:
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
上面的a,b就是字段名称,就是一种符号引用,还有Math类常量池里的 Lcom/tuling/jvm/Math 是类的全限定名,
main和compute是方法名称,()是一种UTF8格式的描述符,这些都是符号引用。
这些常量池现在是静态信息,只有到运行时被加载到内存后,这些符号才有对应的内存地址信息,这些常量池一旦被装
入内存就变成 运行时常量池 了,对应的符号引用在程序加载或运行时会被转变为被加载到内存区域的代码的直接引用,
也就是我们说的 动态链接了。例如,compute()这个符号引用在运行时就会被转变为compute()方法具体代码在内存中
的地址,主要通过对象头里的类型指针去转换直接引用。
1、内存各区域参数配置
| 参数 | 含义 | 默认值 | 示例 | 说明 |
堆 | -Xms | 初始堆大小 | 物理内存的1/64(<1GB) | -Xms1g | 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制. |
-Xmx | 最大堆大小 | 物理内存的1/4(<1GB) | -Xmx1g | 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制 | |
-Xmn | 年轻代大小 | | -Xmn512m | 注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小. 增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8 | |
-XX:NewRatio | 年轻代与年老代的比值 | | -XX:NewRatio=1 | -XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。 | |
-XX:SurvivorRatio | Eden区与Survivor区的大小比值 | 默认8:1:1 | | 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10 | |
栈 | -Xss | 每个线程的堆栈大小 | | | JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右 一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。 |
元空间 | -XX:MetaspaceSize | 初始元数据空间大小 | | | |
-XX:MaxMetaspaceSize=128m | 最大元数据空间大小 | | | 作用:如果不限制,会导致物理内存耗尽,不会进行GC操作 |
2、内存溢出异常
(通过不断的递归方法)
OutOfMemoryError异常:当虚拟机在扩展栈时无法申请到足够的内存空间时,会抛出OutOfMemoryError异常。 (通过不断的创建线程的方式)
(通过不断生成new对象)
(通过不断加载类的字节码文件)
package com.example.stu.jvm;
import org.springframework.cglib.proxy.CallbackFilter;
import org.springframework.cglib.proxy.Dispatcher;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import java.lang.management.ClassLoadingMXBean;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
public class TestJvm1 {
public static void main(String[] args) {
ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TestJvm1.class);
enhancer.setCallbackTypes(new Class[]{Dispatcher.class, MethodInterceptor.class});
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
return 1;
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
});
Class clazz = enhancer.createClass();
System.out.println(clazz.getName());
//显示数量信息(共加载过的类型数目,当前还有效的类型数目,已经被卸载的类型数目)
System.out.println("total: " + loadingBean.getTotalLoadedClassCount());
System.out.println("active: " + loadingBean.getLoadedClassCount());
System.out.println("unloaded: " + loadingBean.getUnloadedClassCount());
}
}
}
内存溢出和内存泄漏的区别:内存溢出,申请不到足够的内存,内存泄露、无法释放已申请的内存
3、元空间的作用
1、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
4、直接内存的作用
在数据传输过程中,减少了堆内存跟直接内存之间的双向传输
非直接内存作用链:
本地IO –>直接内存–>非直接内存–>直接内存–>本地IO
直接内存作用链:
本地IO–>直接内存–>本地IO
3、判断实例是否可以回收
1、引用计数法:当对象之间相互引用时,则无法判断是否可回收
2、可达性分析:从gc roots出发,根据根搜索算法,如果到达这个对象之间没有路径可达,则判断此对象可以回收
4、回收算法及垃圾收集器
查看jvm使用的垃圾回收器:使用java -XX:+PrintCommandLineFlags -version命令:
UseSerialGC | 虚拟机运行在Client 模式下的默认值,打开此开关后,使用Serial + Serial Old 的收集器组合进行内存回收 |
UseParNewGC | 打开此开关后,使用ParNew + Serial Old 的收集器组合进行内存回收 |
UseConcMarkSweepGC | 打开此开关后,使用ParNew + CMS + Serial Old 的收集器组合进行内存 回收。Serial Old 收集器将作为CMS 收集器出现Concurrent Mode Failure失败后的后备收集器使用 |
UseParallelGC | 虚拟机运行在Server 模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old(PS MarkSweep)的收集器组合进行内存回收 |
UseParallelOldGC | 打开此开关后,使用Parallel Scavenge + Parallel Old 的收集器组合进行内存回收 |
CMS垃圾回收器:
G1垃圾回收器:
5、GC过程
mirror gc触发条件:
full gc触发条件:
类加载器
类加载器是JVM的一个子系统
类加载到使用整个过程有如下几步:
加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用 类的main()方法,new对象等等
验证:校验字节码文件的正确性
准备:给类的静态变量分配内存,并赋予默认值
解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链 接 过程(类加载期间完成), 动态链接 是在程序运行期间完成的将符号引用替换为直接 引用,下节课会讲到动态链接
初始化 :对类的静态变量初始化为指定的值,执行静态代码块
注意,jar包里的类,除了java的核心类,不是一次性全部加载的,是使用到时才加载。
启动类加载器:负责加载支撑JVM运行的位于JRE的 lib目录 下的核心类库,比如 rt.jar、charsets.jar 等
扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的 ext 扩展目录中 的 JAR 类包
应用程序类加载器:负责加载 ClassPath 路径下的类包,主要就是加载你自己写 的那些类
自定义加载器:负责加载用户 自定义路径下 的类包
打破双亲委托机制,就是重写classloader里面的loadclass方法,当然类路径如果是java.lang等,则就算打破了双亲委托,也加载不成功
打破双亲委托机制作用就是JVM存在两种版本类路径类名都一样的类,但是java非核心类,取的时候,会根据加载器去获取
以下是没有打破双亲委托的代码,如果需要重写就复制父类的loadclass方法内容,把相关父类传递的代码给去掉
package com.fen.dou.classLoadStu;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终 的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String args[]) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:/test");
Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
Object obj = clazz.newInstance();
Method method= clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
对象逃逸
在编译的时候,就是Java文件生成字节码class文件,默认开启了智能逃逸
如果jit认为 对象不会产生逃逸,则对象可能会存放在栈中,不会进入堆中去