类加载器
加载器启动类加载器 Bootstrap
拓展类加载器 Extension
应用程序类加载器 AppClassLoader
双亲委派机制
自己写了一个类 会先去最高层的类加载器加载 防止污染源代码
方法区
所有线程共享 存在垃圾回收
方法区绝对不是存方法的地方
方法区存储了每一个类的结构信息 例如运行时的方法。字段。构造方法等的字节码内容
stack栈
栈管运行 堆管存储
栈中的数据所有线程不共享 不存在垃圾回收 线程结束就over
占中存放:8中基本类型+对象的引用变量+实例方法
本地变量:输入参数 返回参数 定义参数
记录出栈入栈操作
栈内存溢出会发生 java.lang.StackOverflowError错误
堆栈方法区关系
堆
堆内存逻辑上分为三部分 物理上分为1和2俩个部分
1新生代
1.1伊甸园区
1.2 幸存者0区
1.3幸存者1区
2老年代
3元空间/永久代 1.8之后永久区变成了元空间
所有新创建出来的对象都会存在于伊甸区 当伊甸区满了之后触发YGC垃圾回收 没有被回收的对象进入幸存区 经历15次GC还没被清理的对象进入老年代区,老年代区满了之后 触发 FGC(Full GC) 清理完之后还是没有空间 报出 OOM异常
幸存区交换:
当伊甸区满了之后 会触发GC 此时GC不仅回收伊甸区 还会回收俩个幸存区,GC之后 必然清空伊甸区和幸存区 活下来的对象进入幸存1区,幸存1区和幸存0区会交换 谁空谁是幸存1区 每次没有被清理的对象的年龄都会+1 达到15次之后转移到老年代
堆参数调优
1.7之前永久代使用JVM堆内存 1.8之后元空间使用物理内存
默认JVM堆内存大小是物理内存的一办
-Xms 设置初始内存大小 默认为物理内存 1/64
-Xmx 设置对大内存 默认为物理内存的1/4
-XX:+printGCDetails 输出详细 GC处理日志
查看当前内存大小
public class Test {
public static void main(String[] args) {
long max = Runtime.getRuntime().maxMemory();
long init = Runtime.getRuntime().totalMemory();
System.out.println("最大内存"+(max/1024/1024)+"MB");
System.out.println("初始内存"+(init/1024/1024)+"MB");
//最大内存1790MB
//初始内存121MB
}
}
在生产环境 中 初始内存和最大内存必须调成一样,为什么?避免内存忽高忽低造成卡顿
日志信息解释
GC和fullGC
Minor GC只针对yong区进行垃圾回收 因为大多数对象存活率都不高 所以FC非常频繁
fullGC 针对老年区的垃圾回收 经常会伴随出现至少一次Minor GC 但不是绝对的 FullGC 比 Mainor GC 慢十倍
GC(分代收集算法)
次数上频繁收集yong区
次数上较少收集old区
基本不动元空间
GC4算法
引用计数法:
如果一个对象有n个引用 那么计数为n 当n=0也就是没人引用的时候回收。缺点:每次对对象赋值时都要引用计数器,且计数器本身也有消耗
复制算法:
年轻代gc采用复制算法 对象在幸存区熬过一次 年龄+1 当年龄达到15(默认 可以修改但不能超过15)将会被转移到老年代 ,复制算法的基本思想就是讲内存分为俩块,每次只用其中一块 ,当这一块用完,就将还活着的对象复制到另外一块上面,赋值算法不会产生内存碎片 缺点耗空间(需要另一个幸存区) ,如果对象存活率太高,那将会100%复制 并将引用重置 这将非常耗费时间,要是用复制算法必须保证对象存活率很低
标记清除法
老年代采用该GC算法
先标记要回收的对象,然后再统一回收这些对象
优点:不需要另一个幸存区空间 省空间
缺点:俩次扫描 一次标记 一次清除 清除之后 会产生内存碎片,在gc的时候需要停止应用
标记整理
老年代使用该回收算法 老年代一般使用标记清除算法或者标记清除+标记整理混合实现(解决碎片化)
好处是解决了碎片 缺点是移动对象的成本
看完博客 相信你能做出最后这四道大厂面试题了