1、执行过程
Java 源文件(.java)—->编译器(javac)—->字节码文件(.class)—->JVM—->机器码
.class文件由类加载器加载,将类信息加载到方法区;执行main,遇见new S对象,但这个时候方法区里没有S类信息,所以加载S类信息到方法区,再给S实例分配堆内存,调用构造函数初始化此实例,其实存的是指向方法区S类的类型信息的引用;继续执行,遇到s.sayhello(),就根据s的引用找到S对象,再根据S对象的引用找到方法区中S类的类信息的方法表,获得sayhello()的字节码地址
2、结构
(1)类加载器
双亲委派机制: 想加载一个类时,先委派给父类加载器,若父类加载器发现没有这个类,子类加载器才会加载。比如我们写了个String的类,但源码里已经有了,所以这个类加载不了,这样就避免了我们的代码影响JDK代码
(2)内存
- 本地方法栈:native方法(C语言)
- 程序计数器:唯一不会发生OOM
- 栈:运行。栈深度大于虚拟机最大深度,就会报StackOverflowError (经常出现在递归中)8种基本类型变量、对象引用变量都存在栈里
- 方法区:存类的元数据信息、常量、静态变量。
- 堆:存储。
-
- 年轻代(复制算法)
-
-
- Eden 8
-
-
-
- FromPlace 1
-
-
-
- ToPlace 1
-
-
- 老年代(标记整理算法)
注:前三属私有,随线程结束而结束;后三属公有,所以线程不安全
永生代在JDK1.8后弃用,改存本地内存的元空间,不存在JVM中了
- 老年代(标记整理算法)
注:前三属私有,随线程结束而结束;后三属公有,所以线程不安全
3、垃圾回收****
3.1 堆内存的垃圾回收:
new一个对象,就在Eden中创建。若Eden满了,轻GC,存活对象放FromPlace中;若FromPlace也满了,里面存活对象放ToPlace,清空FromPlace,再两者调换位置;15此轻GC后还活着,就转入到老年代 老年代满了就会触发FullGC,期间会停止所有线程等待GC的完成,而且当老年区执行了full gc之后仍然无法进行对象保存的操作,就会产生OOM
3.2 GC时如何判断某对象需要被干掉: (不止在堆,还有方法区!共享的都会被垃圾收集)
- 引用计数器计算:引用一次,计数器+1,为0清除,但互相引用时无解,所以不用
- 可达性分析计算:类似于二叉树,以将一系列的GC ROOTS集合作为起始的存活对象集,从这个节点往下搜索,把能被该集合引用到的对象加入到集合中。搜索当一个对象到GC Roots没有使用任何引用链时,则说明该对象是不可用的,所以主流用这个
3.3 垃圾回收算法:
- 标记清除:碎片
- 复制:内存分半
- 标记整理:标记,存活对象向一端移动,再清理掉边界外的
- 分代收集算法:新生代每次都大批对象死亡,复制算法只用付出少量存活对象的复制成本;老年代存活率高,复制成本高,所以用标记整理算法
4、调优
4.1 调整最大堆内存和最小堆内存
-Xmx 堆最大值(默认物理内存1/4);-Xms 初始堆大小(默认物理内存1/64)
默认当堆空余小于40%,堆就扩容,扩到-Xmx为止;堆空余大于70%,堆就缩,缩到-Xms为止。
调优: 通常会将 -Xms 与 -Xmx两个参数的配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。
4.2 设置新生代和老年代大小、比值,新生代Eden与Survivor比值
官方推荐: 新生代占堆3/8,Eden占新生代8/10
4.3 调整每个线程栈大小
默认1M,推荐一个进程最多3000~5000个线程