一、JVM类加载机制——自定义类加载器
二、JVM——对象内存分配机制
三、JVM内存模型
四、JVM垃圾收集算法和垃圾收集器
五、CMS垃圾回收器——三色标记算法
六、G1垃圾收集器
七、JVM调优实战——基本命令使用
八、JVM调优实战——arthas使用
九、大流量电商系统JVM调优案例
一、java命令执行代码大致流程
1、windows调用C++程序实现java虚拟机;
2、创建一个引导类加载器(BootstrapClassloader);
3、引导类加载器创建JVM启动器launcher,由lunacher类创建其他类加载器(扩展类加载器和应用类加载器)
二、类加载的大致过程
1、加载,根据path找到class文件;
2、验证,校验class内容是否符合规范;
3、准备,静态变量赋默认值,常量直接赋初始值;
4、解析,将符号引用转换为直接引用;符号引用可以理解为“public static ”这一类的符号;直接引用就是内存地址的引用;
(1)静态链接:比如static一类的方法,在类加载期间就转换为直接引用了;
(2)动态链接:方法的调用,“dosomething()”,在调用时才转换为直接引用;
5、初始化,为静态变量赋值,执行静态方法
三、类加载器和双亲委派机制
1、引导类加载器:加载rt.jar等核心模块;
2、扩展类加载器:加载扩展包里面的类;
3、应用程序类加载器:加载用户自己变成的文件
4、自定义类加载器:加载用户自定义路径下的类
实例:
package com.company;
import java.net.URL;
import com.sun.crypto.provider.DESedeKeyFactory;
import sun.misc.Launcher;
/**
* @date 2020/7/2
*/
public class TestJdkClassLoader {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(DESedeKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(TestJdkClassLoader.class.getClassLoader().getClass().getName());
System.out.println();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassLoader = appClassLoader.getParent();
ClassLoader bootStrapLoader = extClassLoader.getParent();
System.out.println("the bootStrapLoader"+bootStrapLoader);
System.out.println("the extClassLoader"+extClassLoader);
System.out.println("the appClassLoader"+appClassLoader);
System.out.println();
System.out.println("bootStrapLoader 加载以下文件");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println();
System.out.println("extClassLoader 加载以下文件");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appclassloader 加载以下文件");
System.out.println(System.getProperty("java.class.path"));
}
}
双亲委派的过程:
简单说一下大致过程:
首先去自定义类加载中查找有没有加载过的类,没有就一直向上委派;直到引导类加载也没找到;那么引导类加载器开始尝试加载,如果成功加载则返回;否则向下尝试;一直到自定义类加载类尝试加载。
源码只要在java.lang.ClassLoader#loadClass(java.lang.String, boolean)这个方法中:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 先查找一下
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、安全
jvm的核心类库只能由引导类加载器加载;避免了安全问题;
2、效率
一些共同使用的基础jar包,只会由指定加载器加载一次,然后就都能使用了,避免了重复加载。
自定义类加载器
继承ClassLoader类,重写findclass方法;
package com.JVM性能调优;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author
* @date 2020/7/2
*/
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
return super.loadClass(name, resolve);
}
@Override protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//该方法将一个字节数组转换为Class对象,这个字节数组就是最终读取的字节数组
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
private byte[] loadByte(String name) throws IOException {
name = name.replaceAll("\\.", "/");
FileInputStream fileInputStream = new FileInputStream(classPath + "/" + name + ".class");
int len = fileInputStream.available();
byte[] bytes = new byte[len];
fileInputStream.read(bytes);
fileInputStream.close();
return bytes;
}
public static void main(String[] args)
throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException,
InvocationTargetException {
//父加载器设置为应用程序类加载器
MyClassLoader myClassLoader = new MyClassLoader("D:/test");
Class<?> aClass = myClassLoader.loadClass("com.company.Sout");
Object object = aClass.newInstance();
Method method = aClass.getDeclaredMethod("sout", null);
method.invoke(object, null);
System.out.println(aClass.getClassLoader().getClass().getName());
}
}
}
package com.company;
/**
* @author
* @date 2020/7/27
*/
public class Sout {
public void sout(){
System.out.println("使用自定义类加载器进行加载!!!");
}
public static void main(String[] args) {
Sout sout = new Sout();
sout.sout();
}
}
打破双亲委派机制
继承ClassLoader类,重写laodClass方法
package com.JVM性能调优;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author
* @date 2020/7/2
* 自定义类加载器,重写findclass方法,重写loadclass方法,打破双亲委派;
*
*/
public class MyClassLoaderTest2 {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c ==null) {
long t0 = System.nanoTime();
if (!name.startsWith("com.company")){
c = this.getParent().loadClass(name);
} else {
c = findClass(name);
}
long t1 = System.nanoTime();
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
@Override protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//该方法将一个字节数组转换为Class对象,这个字节数组就是最终读取的字节数组
return defineClass(name, data, 0, data.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
private byte[] loadByte(String name) throws IOException {
name = name.replaceAll("\\.", "/");
FileInputStream fileInputStream = new FileInputStream(classPath + "/" + name + ".class");
int len = fileInputStream.available();
byte[] bytes = new byte[len];
fileInputStream.read(bytes);
fileInputStream.close();
return bytes;
}
public static void main(String[] args)
throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException,
InvocationTargetException {
//父加载器设置为应用程序类加载器
MyClassLoader myClassLoader = new MyClassLoader("D:/test");
Class<?> aClass = myClassLoader.loadClass("com.company.Sout");
Object object = aClass.newInstance();
Method method = aClass.getDeclaredMethod("sout", null);
method.invoke(object, null);
System.out.println(aClass.getClassLoader().getClass().getName());
System.out.println("==============================================");
//父加载器设置为应用程序类加载器
MyClassLoader myClassLoader1 = new MyClassLoader("D:/test1");
Class<?> bClass = myClassLoader.loadClass("com.company.Sout");
Object objectb =bClass.newInstance();
Method bmethod = bClass.getDeclaredMethod("sout", null);
bmethod.invoke(objectb, null);
System.out.println(bClass.getClassLoader().getClass().getName());
}
}
}
模拟tomcat,打破双亲委派机制,实现隔离
通过查看tomcat类加载器模型我们可以得出如下结论:
1、不同的webapps类加载器之间相互隔离,实现了web应用的安全隔离;
2、同时可以通过SharedclassLoader实现类在不同webapp之间的共享;
3、catlinaclassloader下的类又与应用之间的类之间隔离;
4、每个JSP类加载器只加载对应的一个JSP文件;jsp文件热加载的原理,就是tomcat检测到jsp文件发生了变化,就删除旧的jsp类加载器,重新实例化一个JSP类加载器来加载文件。
注:
1、Tomcat打破双亲委派机制:当某个请求想从web应用的类加载器中加载类时,该类会查找自己的仓库,如果没有会先尝试加载,而不是直接委托给父类。
加载顺序:(1)JVM的bootstrap类加载器,扩展类加载应用类加载器
(2)WEB应用的/WEB-INF/classes目录
(3)WEB应用的/WEB-INF/lib目录
(4)tomcat-commonClassLoader
2、Tomcat8可以通过配置“” 不打破双亲委派机制