概述
一从java.exe开始类加载运行过程
1.Java com.danny.common.Test01.class
2.windows系统下java.exe会调用底层的jvm.dll文件创建java虚拟机
3.创建一个引导类加载器的实例
4.调用java代码创建JVM启动器实例sum.mic.Launcher,该类由引导类加载器负责加载并创建其他类加载器
5.sun.mic.Launcher.getLauncher() 获取运行类自己的加载器ClassLoader
6.调用loadClass 加载要运行的类Test01,classLoader.loadClass(“com.danny.common.Test01”)
7.加载完成后,会执行Test01的main方法入口
8.Test01.main()
9.java程序运行结束
10.jvm销毁
二、类加载过程
加载》》验证》》准备》》解析》》初始化
加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的 main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的 java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证:校验字节码文件的正确性
准备:给类的静态变量分配内存,并赋予默认值
解析:将符号引用替换为直接引用 ,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用
初始化:对类的静态变量初始化为指定的值,执行静态代码块
类被加载到内存方法区中时,主要包括运行时常量池,类型信息,字段信息,方法信息,类加载器的引用,对应class实例的引用等信息。
jdk7之前方法区被称为永久代,放在jvm内存中,静态变量和字符串常量放在堆中吗,运行时常量池放在永久代中
jdk8之后,方法区又称为元空间,不适用虚拟机内存,而是并直接使用物理内存,字符串常量池和静态变量仍然在堆当中;运行时常量池、类型信息、常量、字段、方法被移动都了元空间中。元空间默认大小为21M,会根据程序运行情况,动态调整,也可以在项目启动时指定元空间大小
java -Xms2048M -Xmx2048M -XX:MetaSpaceSize=256M -XX:MaxMetaSpaceSize=256M -jar source.jar
三、类加载器
1.引导类加载器,负载加载支撑JVM运行的 位于JRE的lib目录下得核心类库,比如rt.java,charsets.jar等
2.扩展类加载器ExtClassLoadder,负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
3.应用程序类加载器AppClassLoader负责加载classPath路径下的类包,主要就是加载程序员写的那些类
4.自定义加载器:负责加载用自定义路径下的类包
四、双亲委派机制
类加载器加载类会层层递交给上层加载器加载,如果上层加载器能加载就会加载,如果所有上层加载器都没加载,才会由当前类加载器加载。
两个好处:
沙箱安全机制,保证jvm核心类库能正确加载
保证类的唯一性 ,避免重复加载类
五、全盘负责委托机制
指一个ClassLoader装载一个类时,除非显示的使用另一个ClassLoader,否则该类所依赖及引用的类也由这个ClassLoader载入
六、自定义类加载器
继承ClassLoader
重写findClass和loadClass(如果想要打破双亲委派)
package com.danny.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class MyClassLoaderTest extends ClassLoader {
private String classPath;
public MyClassLoaderTest(String classPath) {
this.classPath = classPath;
}
@Override
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) {
if (name.startsWith("java")) {
c = super.loadClass(name,resolve);
} else {
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadData(name);
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException(name);
}
}
private byte[] loadData(String name) throws IOException {
String fileName = name;
fileName = fileName.replaceAll("\.", "/") + ".class";
String file = classPath + fileName;
System.out.println(file);
byte[] data = null;
try (FileInputStream fis = new FileInputStream(new File(file))
) {
int available = fis.available();
data = new byte[available];
fis.read(data);
}
return data;
}
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoaderTest myClassLoader = new MyClassLoaderTest("d:/test/");
Class<?> clzz = myClassLoader.loadClass("com.danny.common.Person");
System.out.println(clzz.getClassLoader().getClass().getName());
}
}
七、 为什么打破双亲委派机制
根据不同的场景,可能西药加载到某些不同版本的类。例如Tomcat作为web服务器,会部署很多web应用,如果不同应用需要依赖不同版本的jar包,根据双亲委派机制,只会加载一次类,导致不同web应用拿到的是同一个版本的jar包,因此需要打破双亲委派机制。
八、什么时候对象会进入到老年代
- gc分代年龄达到默认次数,一般是第15次gc回收仍存活的对象会进入老年代,不同gc回收器可能不一样。由于对象头mark word中用4bit表示分代年龄,因此最多也就15次
- 大对象直接进入老年代,可以通过-XX:PretenureSizeThreshold设置大小
- 动态年龄判断,发生minor gc时,会计算分代年龄1+年龄2+…+年龄n等对象的大小是否超过Survivor 取50%,如果超过则把年龄n及以上的对象放入老年代
九、什么是TLAB
TLAB,即栈上分配。jvm会为每个栈分配一小块内存。如果对象经逃逸分析确定不会被栈桢以外访问,则会为其在栈上分配内存(前提是这个栈上内存能放得下这个对象)。这样可以让对象随着栈桢出栈而销毁,以降低GC垃圾回收压力
最后
以上就是心灵美夕阳为你收集整理的jvm学习笔记的全部内容,希望文章能够帮你解决jvm学习笔记所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复