jvm参数的使用方式如下 在启动类配置即可
1. java中的类什么时候加载?
用到就加载,没用到就不加载,可以使用-XX:+TraceClassLoading监控类的加载
public class SpringMain {
public static void main(String[] args) throws Exception {
System.out.println("-XX:+TraceClassLoading 打印加载的类");
}
}
打印
省略...
[Loaded com.yujie.SpringMain from file:/H:/javaDemo/spring-01/out/production/spring-01/]
[Loaded java.net.Inet6Address from C:\Program Files\Java\jdk8\jre\lib\rt.jar]
-XX:+TraceClassLoading 打印加载的类
[Loaded java.net.ProxySelector from C:\Program Files\Java\jdk8\jre\lib\rt.jar]
省略...
2.一个类的加载过程(类的生命周期)
1.加载:classpath下,以及jar包中的类并且为每个类生成一个Class对象放入元空间中
2.验证:class文件是否符合jvm规范
3.准备:为类变量(static修饰)赋默认值 int为0 boolean为false
4.初始化:触发条件访问一个类的静态属性,修改一个类的静态属性,反射对类进行调用 初始化就是赋=号右边的值
5.使用:开始使用这个类
6.卸载:垃圾回收
以下是对初始化的补充,准备阶段已经按照系统的要求赋值过一次值,现在初始化才是赋真正的值
1.父类静态变量->父类静态代码块->子类静态变量->子类静态代码块
2.父类普通变量->父类普通代码块->父类构造方法
3.子类普通变量->子类普通代码块->子类构造方法
注意静:态变量一定要放在静态代码块之前否则静态代码块使用静态变量会报错
纠正一点很多人说static静态代码块在类加载进jvm就会执行
纠正:应该是类进入初始化的阶段才会进行加载,有的人在A类中定义一个main方法这样就会执行静态代码块,这是因为A方法调用了main方法触发了初始化阶
3.什么是类加载器?
负责将.class文件加载进jvm内存
1.类加载器有三种BootstrapClassLoader他是用来加载jre/lib目录下的rt.jar文件很多c++写的代码也是由该类加载器来加载的
2.第二个类加载器就是ExtClassLoader他会加载jre/lib/ext所有jar包
3.第三个类加载器就是AppClassLoader加载的是我们classpath路径下的.class文件,也是我们写的代码都会由他进行加载
找到三个类加载器加载的目录进行加载测试
public static void main(String[] args) throws Exception {
// rt.jar包下的类 BootstrapClassLoader是由c++编写的所以打印为null
System.out.println("BootstrapClassLoader="+Constants.class.getClassLoader());
// ext/access-bridge-64.jar包下的类
System.out.println("ExtClassLoader="+ AccessBridge.class.getClassLoader());
//自己写的类
System.out.println("AppClassLoader="+ SpringMain.class.getClassLoader());
}
打印结果
BootstrapClassLoader=null
ExtClassLoader=sun.misc.Launcher$ExtClassLoader@1f32e575
AppClassLoader=sun.misc.Launcher$AppClassLoader@18b4aac2
4.双亲委派加载机制
1.说到类加载器不得不说他的双亲委派模式,加载一个类进jvm内存中他是先交给appClassLoader去缓存中查找是否之前加载过
2.没有找到就委派给上一层ExtClassLoader进行去缓存中查找
3.如果没有,继续委派给上一层的BootstrapClassLoader缓存中查找
4.如果没有找到就BootStrapClassLoader会去他的指定的目录下查找jre/lib/rt.jar查找是否有此类
5.如果没有就反馈给下级让ExtClassLoader去他指定的目录下查找也就是jre\lib\ext文件夹下查找
6.如果还是没有就交给AppClassLoader进行查找也就是去classpath目录下进行查找该文件最后如果都没有找到
就会报找不到类异常
总结就是从下到上找缓存,缓存都没有找到那么就从上到下找文件
5. 为什么要设计双亲委派?
1.保证核心类库的安全
防止你自己也写一个String类和系统自带的有冲突
2.避免类的重复加载
如果这个类加载过了就会缓存起来下次直接从缓存中获取
3.保证类的唯一性
你写一个String类系统是不会用你写的,jvm中也只会有一份String.class
6.如何打破双亲委派机制
自定义一个类继承ClassLoader并且重写loadClass方法
ClassLoader类中有两个重要方法
loadClass方法实现双亲委派,委派给不同的类加载器
findClass方法去哪里找class文件
总结:loadClass会加载不同类加载器去调用findClass方法找不同的路径
7.class.forName和ClassLoader的loadClass方法有什么区别
forName加载类会进入初始化状态执行类的静态代码块
loadClass加载类不会进入初始化状态,所以不会执行静态代码块
8.tomcat类加载器
tomcat有三个自定义类加载器
Common类加载器(CommonClassLoader)
加载tomcat lib目录下的jar文件
WebApp类加载器(WebappClassLoader)
加载tomcat 目录下的webapps文件夹下的项目,比如有webapps下有5个项目会有5个地址不同的WebappClassLoader去加载,这样保证了项目的隔离性。
Jsp类加载器(JsperLoader)
加载jsp文件
优先到自定义的类加载器去找,找不到了就去jdk自带类加载器找
9.tomcat为什么要打破双亲委派
1.项目需要实现隔离
webapps下有两个项目,一个使用spring4,一个使用spring5,如果不打破,这两个项目引用同一个版本class对象会出现不兼容问题,应该使用两个不同的类加载器创建两个不同的class对象,项目一用4版本class对象,项目二用5版本的class对象
2.实现jsp动态热部署 在你修改jsp页面的时候他会重新生成一个class文件开启一个新的类加载器去加载
以下是tomcat打破双亲委派的效果演示 注意两个项目的UserOrderController 都是同包同名
项目一Controller
@RequestMapping("/userOrder")
public class UserOrderController {
public UserOrderController(){
System.out.println("spring4");
ClassLoader classLoader = this.getClass().getClassLoader();
System.out.println("当前类加载器:"+classLoader+classLoader.hashCode());
}
项目2Controller
@RequestMapping("/userOrder")
public class UserOrderController {
public UserOrderController(){
System.out.println("spring5");
ClassLoader classLoader = this.getClass().getClassLoader();
System.out.println("当前类加载器:"+classLoader+classLoader.hashCode());
}
启动tomcat
如果还不能理解class对象的同学请下面这个例子,虽然创建了三个a对象实例但是只有一个class对象,要想创建新的class对象就要用到不同的类加载器
10.如何实现一个热加载器
1.创建一个定时器不断的轮询判断classpath路径下的class文件并且把时间保存在一个map中,k是类的全路径,v是修改时间
2.循环判断每个类的修改时间是否和上次时间一致,不一致就创建一个自定义类加载器对象对该class进行加载
11.jvm内存结构
12.jvm什么时候内存溢出
当new出来的对象没有变量引用他了就会被当成垃圾进行回收
采用的是根可达算法,通过这个list变量如果能找到C对象引用的地址那么就不算垃圾不会进行回收
以下list演示内存溢出,为了方便演示溢出jvm最大内存设置20m
public class C {
static int createNum=0;
static int createSumNum=0;
static int destroy =0;
private byte[] a= new byte[1024*1024];
public C(){
createNum++;
createSumNum++;
System.out.println("创建了"+this+"对象,当前存活对象"+createNum+"累计创建对象个数为"+createSumNum+"个");
}
@Override
protected void finalize() throws Throwable {
destroy++;
createNum--;
System.err.println("当前对象"+this+"进行回收,累计销毁"+destroy+"个对象");
super.finalize();
}
public static void main(String[] args) throws Exception {
ArrayList<Object> list = new ArrayList<>();
while (true){
C c = new C();
list.add(c);
Thread.sleep(1000);
}
}
}
打印
创建了com.yujie.C@5caf905d对象,当前存活对象1累计创建对象个数为1个
创建了com.yujie.C@27716f4对象,当前存活对象2累计创建对象个数为2个
创建了com.yujie.C@8efb846对象,当前存活对象3累计创建对象个数为3个
创建了com.yujie.C@2a84aee7对象,当前存活对象4累计创建对象个数为4个
创建了com.yujie.C@a09ee92对象,当前存活对象5累计创建对象个数为5个
创建了com.yujie.C@30f39991对象,当前存活对象6累计创建对象个数为6个
创建了com.yujie.C@452b3a41对象,当前存活对象7累计创建对象个数为7个
创建了com.yujie.C@4a574795对象,当前存活对象8累计创建对象个数为8个
创建了com.yujie.C@f6f4d33对象,当前存活对象9累计创建对象个数为9个
创建了com.yujie.C@23fc625e对象,当前存活对象10累计创建对象个数为10个
创建了com.yujie.C@3f99bd52对象,当前存活对象11累计创建对象个数为11个
创建了com.yujie.C@4f023edb对象,当前存活对象12累计创建对象个数为12个
创建了com.yujie.C@3a71f4dd对象,当前存活对象13累计创建对象个数为13个
创建了com.yujie.C@7adf9f5f对象,当前存活对象14累计创建对象个数为14个
创建了com.yujie.C@85ede7b对象,当前存活对象15累计创建对象个数为15个
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.yujie.C.<init>(C.java:7)
at com.yujie.SpringMain.main(SpringMain.java:17)
没有对象回收的原因,通过list这个根变量找到15个C对象所以不是垃圾
以下是根不可达导致对象被当成垃圾回收不会发生内存溢出
public static void main(String[] args) throws Exception {
C c =null;
while (true){
c=new C();
Thread.sleep(1000);
}
}
打印
创建了com.yujie.C@5caf905d对象,当前存活对象1累计创建对象个数为1个
创建了com.yujie.C@27716f4对象,当前存活对象2累计创建对象个数为2个
创建了com.yujie.C@8efb846对象,当前存活对象3累计创建对象个数为3个
当前对象com.yujie.C@8efb846进行回收,累计销毁1个对象
创建了com.yujie.C@2a84aee7对象,当前存活对象3累计创建对象个数为4个
创建了com.yujie.C@a09ee92对象,当前存活对象4累计创建对象个数为5个
当前对象com.yujie.C@2a84aee7进行回收,累计销毁2个对象
当前对象com.yujie.C@a09ee92进行回收,累计销毁3个对象
当前对象com.yujie.C@27716f4进行回收,累计销毁4个对象
当前对象com.yujie.C@5caf905d进行回收,累计销毁5个对象
创建了com.yujie.C@30f39991对象,当前存活对象4累计创建对象个数为6个
创建了com.yujie.C@452b3a41对象,当前存活对象2累计创建对象个数为7个
当前对象com.yujie.C@452b3a41进行回收,累计销毁6个对象
创建了com.yujie.C@4a574795对象,当前存活对象2累计创建对象个数为8个
创建了com.yujie.C@f6f4d33对象,当前存活对象3累计创建对象个数为9个
创建了com.yujie.C@23fc625e对象,当前存活对象3累计创建对象个数为10个
创建了10个对象只存活了3个对象,其他7个没有c变量引用它被当成垃圾进行回收 理论上有9个回收才对,由于jvm优化不是每次都回收的很干净,如果我们多创建几个不同的变量名作为根变量不就是可以提高对象的存活率了吗 接下来测试
测试多个变量名增加对象存活率
public static void main(String[] args) throws Exception {
C c =null;
C c2 =null;
C c3 =null;
C c4 =null;
C c5 =null;
while (true){
c=new C();
c2=new C();
c3=new C();
c4=new C();
c5=new C();
Thread.sleep(1000);
}
}
打印
创建了com.yujie.C@5caf905d对象,当前存活对象1累计创建对象个数为1个
创建了com.yujie.C@27716f4对象,当前存活对象2累计创建对象个数为2个
创建了com.yujie.C@8efb846对象,当前存活对象3累计创建对象个数为3个
创建了com.yujie.C@2a84aee7对象,当前存活对象4累计创建对象个数为4个
创建了com.yujie.C@a09ee92对象,当前存活对象5累计创建对象个数为5个
创建了com.yujie.C@30f39991对象,当前存活对象6累计创建对象个数为6个
创建了com.yujie.C@452b3a41对象,当前存活对象7累计创建对象个数为7个
创建了com.yujie.C@4a574795对象,当前存活对象8累计创建对象个数为8个
创建了com.yujie.C@f6f4d33对象,当前存活对象9累计创建对象个数为9个
创建了com.yujie.C@23fc625e对象,当前存活对象10累计创建对象个数为10个
当前对象com.yujie.C@5caf905d进行回收,累计销毁1个对象
当前对象com.yujie.C@27716f4进行回收,累计销毁2个对象
当前对象com.yujie.C@8efb846进行回收,累计销毁3个对象
当前对象com.yujie.C@a09ee92进行回收,累计销毁4个对象
当前对象com.yujie.C@2a84aee7进行回收,累计销毁5个对象
创建了com.yujie.C@3f99bd52对象,当前存活对象11累计创建对象个数为11个
创建了com.yujie.C@4f023edb对象,当前存活对象10累计创建对象个数为12个
创建了com.yujie.C@3a71f4dd对象,当前存活对象8累计创建对象个数为13个
创建了com.yujie.C@7adf9f5f对象,当前存活对象9累计创建对象个数为14个
创建了com.yujie.C@85ede7b对象,当前存活对象10累计创建对象个数为15个
很明显存活率就上来了
13.排查内存溢出的参数
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=F:/dev/aa.hprof
1.在发生内存溢出的时候生成Dump文件如果不加-XX:HeapDumpPath 默认生成在项目的根路径下
2.生成的文件可以使用jdk自带的VisualVM工具导入进行分析
3.VisualVM可以查到类的加载数量,实例对象数量,发生内存溢出的线程和代码的具体行数,以及对象的占用大小
14.java中的引用类型
//强引用 内存不足即使内存溢出也不会进行回收
C c =new C();
//软引用 内存不足进行回收不管你这个对象是否被其他对象所引用适合用在缓存
SoftReference<C> cSoftReference = new SoftReference<>(c);
//弱引用
WeakReference<C> cWeakReference = new WeakReference<>(c);
//弱引用只要触发gc就会进行回收
WeakReference<C> cWeakReference1 = new WeakReference<>(c);
15.标记垃圾两种算法
计数法 回收计数为0的对象
A a =new A();
B b= new B();
C c= new C();
a.set(b);
//a对象引用了b对象b对象计数1
c.set(b);
//c对象引用了b对象b对象计数2
//当触发gc时垃圾回收计数为0的对象
//计数器的缺点不能回收循环依赖对象
A a =new A();
B b= new B();
a.set(b);
b.set(a);
//a计数1,b计数1
根可达算法
判断哪些对象是跟对象,获取当前对象的根对象,如果能够找到就不是垃圾
一个类中哪些对象可以作为根对象
public class D {
//静态成员变量可以作为GCRoot
public static C c=new C();
//final成员变量可以作为GCRoot
public final C cfinal=new C();
//普通成员变量不能!!!
public C c1=new C();
//方法参数可以作为GCRoot
public void d(C c){
//局部变量可以作为GCRoot
C c2 = new C();
}
}
结构图和代码
public class SpringMain {
public static void main(String[] args) throws Exception {
Object1 object1 = new Object1();
object1.setObject2(new Object2());
object1.setObject3(new Object3());
object1.getObject3().setObject4(new Object4());
}
}
一个对象可以有多个根对象 下面的object3的根对象分别是object1和list
public class SpringMain {
static List<Object> list =new ArrayList();
public static void main(String[] args) throws Exception {
Object1 object1 = new Object1();
object1.setObject2(new Object2());
object1.setObject3(new Object3());
object1.getObject3().setObject4(new Object4());
list.add(object1.getObject3()) ;
}
}
16.jvm堆内存模型
堆内存可以分为两大类新生代和老年代,而新生代又分为伊甸园 s0 和 s1区
内存占比,老年代占三分之二,年轻代占三分之一 年轻具体细分是8:1:1
比如堆内存分了300兆
老年代:200m
新生代:
伊甸园:80m
s0:10m
s1:10m
17.动态年龄判断
是一种优化手段让大的对象尽早的进入老年代而不用等到年龄15岁
符合以下条件的对象都能提前进入老年代
1.判断survivor区的存活对象占用的内存是否大于等于survivor区内存的50%
2.从年龄1到年龄N开始累加计算占用内存,到达50%时取最后达到百分之50的年龄为阈值,
从这个阈值后面的对象都会进入老年代
举例子
survivor区 年龄1=20%,年龄2=18%,年龄3=10%,年龄4=2%,年龄5=10%
20+18+10+2+10=60超过了50,年龄4和年龄5的对象都放入老年代