这里有两种算法:
1、引用计数算法
2、可达性分析算法
引用计数算法
该算法给每个对象分配一个计数器,当有引用指向这个对象时,计数器加1,当指向该对象的引用失效时,计数器减一。最后如果该对象的计数器为0时,java垃圾回收器会认为该对象是可回收的。
优点:
1、实时性高,只要对象计数器为0就进行回收,不用等到内存不足的时候。
2、在垃圾回收过程中,应用无需挂起。
3、更新对象的计数器时,只是影响到该对象,不会扫描全部对象。
缺点:
1、每次引用对象时,都会更新计数器,有时间消耗
2、不能解决循环引用问题
那什么是循环引用问题呢?我们看下面这段代码:
-
class ClassA{
-
ClassB b;
-
}
-
class ClassB{
-
ClassA a;
-
}
-
public static void main(String[] args){
-
ClassA a = new ClassA(); 1
-
ClassB b = new ClassB(); 1
-
a.b = b; 2
-
b.a = a; 2
-
a = null; 1
-
b = null; 1
-
}
上面的a、b两个对象虽然都赋值为null,但是都不能回收,因为存在循环引用,它们的计数器不为0.
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l3Q95Jh7-1624847287186)(imgs/1624329698752.png)]](https://i-blog.csdnimg.cn/blog_migrate/00435af06138bac95d7b19cff0e8b52b.png)
可达性分析算法
该算法通过一种被称作“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象时不可用的。
如下图:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wX1HPZbq-1624847287187)(jvm.assets/clip_image002-1624000270863.gif)]](https://i-blog.csdnimg.cn/blog_migrate/6855d4d2013dae86c6d30bc8a33dd1f6.gif)
在Java语言中,可作为GC Roots对象包括下面几种:
1)虚拟机栈中引用的对象
2)方法区中类静态属性引用的对象
3)方法区常量池中引用的对象
3)本地方法栈中JNI引用的对象
再回头看前面这段代码,虽然a和b对象的引用计数都不为0,但是它们作为GC Root对象,最后都赋值为null,导致引用不可达,这样两个对象都是可以被回收的。
Java的堆是JVM中最大的一块内存区域,主要保存Java中各种类的实例。为了更好的管理堆中各个对象的内存,包括分配内存和回收内存。
JVM将堆分成了几块区域:
-
新生代(Young)
-
新生代又分为:
-
Eden 伊甸区
-
From Survivor 幸存区1
-
To Survivor 幸存区2
-
老年代(Old)
其中新生代占堆的1/3空间,老年代占堆的2/3空间。
而新生代中的Eden占新生代的8/10,From Survivor和To Survivor各占新生代的1/10。
堆模型如图:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0athClT7-1624847287188)(jvm.assets/clip_image002.gif)]](https://i-blog.csdnimg.cn/blog_migrate/56b715a6b542dd685d1e67e45f767591.gif)
从上图中我们可以看出:堆是由新生代和老年代组成,默认情况下,新生代 ( Young ) 与老年代 ( Old ) 的比例为 1:2 。
其中,新生代 ( Young ) 又分为 Eden 和 From Survivor 、To Survivor区域。 默认情况下,Eden 和from、to的比例为 :8 : 1 : 1 。
JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
堆中的GC分为两种:
-
Minor GC
-
Full GC
Minor GC发生在新生代,采用的算法是复制算法。
Java中新创建的对象都在新生代中,当对象被判定为死亡时(也就是无法访问),就会被GC回收内存,发生Minor GC时,会将Eden和From Survivor区域中的存活的对象复制到To Survivor区域中,然后将Eden和From survivor区域进行清理。
当一个对象活过了一次Minor GC后,它的年龄就加1,当对象的年龄达到了15时,对象就会被放入老年代。
对象进入老年代的情况
-
年龄达到15
-
对象大小超过配置 -XX:PretenureSizeThreshold=???
-
年龄相同的一批对象,大小超过Survivor区的1/2
Full GC发生在老年代,采用的是标记-清除算法。
标记:标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。
清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。
当程序运行期间,若可以使用的内存被耗尽的时候,GC线程就会被触发并将程序暂停,随后将依旧存活的对象标记一遍,最终再将堆中所有没被标记的对象全部清除掉,接下来便让程序恢复运行。
标记-清除算法存在比较大的缺点:
1)进行GC时需要暂停应用程序,所以导致用户体验变差
2)会产生许多不连续的内存空间
所以我们一般会避免出现Full GC。
JVM的优化可以通过参数配置来实现。
堆的初始大小、新生代、老年代的大小都可以通过JVM的参数进行配置。
下面是一些常用的JVM参数:
| 参数 | 说明 |
| — | — |
| -Xms | 初始堆大小。如:-Xms256m |
| -Xmx | 最大堆大小。如:-Xmx512m |
| -Xmn | 新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor,即 90% |
| -Xss | 线程的堆栈大小 |
| -XX:NewRatio | 新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3 |
| -XX:SurvivorRatio | 新生代中 Eden 与 Survivor 的比值。默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10 |
| -XX:PermSize | 永久代(方法区)的初始大小 |
| -XX:MaxPermSize | 永久代(方法区)的最大值 |
| -XX:+PrintGCDetails | 打印 GC 信息 |
| -XX:PretenureSizeThreshold | 超过该值的对象直接进入老年代 |
JVM优化案例:某电商系统做活动期间频繁出现卡顿,每秒订单为1000左右,产生大概60M的对象
运行参数如下
java -Xms3G -Xmx3G -Xss1M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M -jar xxx.jar
分析原因:
-
堆大小固定为3G,其中新生代800M,两个survivor均为100M
-
发生MinorGC后,60M的对象进入survivor,因为超过survivor的一半,直接进入老年代
-
老年代很快被占满,频繁发生Full GC
解决方法:
-
可以将整个堆的大小设大,也就是修改-Xms -Xmx
-
如果堆的大小已经是固定的,可以把新生代设大,也就是 -Xmn2G,这样survivor达到了200M,60M的对象不会直接到老年代,而是需要反复经过15次Minor GC后,很多对象就被回收掉了,老年代就不会被频繁沾满而出现Full GC。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BuuiVBDp-1624847287189)(imgs/1624343685309.png)]](https://i-blog.csdnimg.cn/blog_migrate/324f94f1b0c0b35372a0cbaa3ec59817.png)
我们执行Java程序开发出来后,需要先编译再执行,JVM就负责加载类的过程。
类加载的过程分为:
-
加载
-
验证
-
准备
-
解析
-
初始化
类加载的具体过程
下面详细介绍下这几个过程:
- 加载
在加载类的过程要完成:
-
根据类的全名限定符,获取class二进制流,这个流可以从磁盘上的class、jar文件获得,也可以从网络中获得。
-
将类的静态存储结构转化为方法区的运行时动态存储结构
-
在内存的堆中生成对应的java.lang.Class对象,作为方法区的入口
-
验证
加载类完成后,就进入了验证过程,这个过程保证了前面生成的Class对象中的信息,不会危害JVM的安全。
需要验证的方面有:
-
文件格式验证,是要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。如验证魔数是否0xCAFEBABE;主、次版本号是否正在当前虚拟机处理范围之内;常量池的常量中是否有不被支持的常量类型等等,该验证阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区中,经过这个阶段的验证后,字节流才会进入内存的方法区中存储,所以后面的三个验证阶段都是基于方法区的存储结构进行的。
-
元数据验证,是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。可能包括的验证如:这个类是否有父类;这个类的父类是否继承了不允许被继承的类;如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法。
-
字节码验证,主要工作是进行数据流和控制流分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。如果一个类方法体的字节码没有通过字节码验证,那肯定是有问题的;但如果一个方法体通过了字节码验证,也不能说明其一定就是安全的。
-
符号引用验证,发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在“解析阶段”中发生。验证符号引用中通过字符串描述的权限定名是否能找到对应的类;在指定类中是否存在符合方法字段的描述符及简单名称所描述的方法和字段;符号引用中的类、字段和方法的访问性(private、protected、public、default)是否可被当前类访问。
-
准备
准备阶段会在方法区中为类的静态变量分配内存,并赋给默认值。
public static int count = 100;
如:上面的count变量在准备阶段会赋值为0,在初始化时再赋值为100;
- 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
Integer a = 200;
Integer b = 200;
a == b;
- 符号引用(Symbolic Reference)
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
- 直接引用(Direct Reference)
直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,如果有了直接引用,那么引用的目标必定已经在内存中存在。
- 初始化
类初始化是类加载过程的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。
初始化阶段是执行类构造器()方法的过程。()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。
那么何时执行初始化呢?
-
创建类的实例
-
访问类的静态变量(除常量外,final修饰的)
原因:常量一种特殊的变量,因为编译器把他们当作值而不是属性来对待。
总结
至此,文章终于到了尾声。总结一下,我们谈论了简历制作过程中需要注意的以下三个部分,并分别给出了一些建议:
- 技术能力:先写岗位所需能力,再写加分能力,不要写无关能力;
- 项目经历:只写明星项目,描述遵循 STAR 法则;
- 简历印象:简历遵循三大原则:清晰,简短,必要,要有的放矢,不要海投;
以及最后为大家准备的福利时间:简历模板+Java面试题+热门技术系列教程视频



器()方法的过程。()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。
那么何时执行初始化呢?
-
创建类的实例
-
访问类的静态变量(除常量外,final修饰的)
原因:常量一种特殊的变量,因为编译器把他们当作值而不是属性来对待。
总结
至此,文章终于到了尾声。总结一下,我们谈论了简历制作过程中需要注意的以下三个部分,并分别给出了一些建议:
- 技术能力:先写岗位所需能力,再写加分能力,不要写无关能力;
- 项目经历:只写明星项目,描述遵循 STAR 法则;
- 简历印象:简历遵循三大原则:清晰,简短,必要,要有的放矢,不要海投;
以及最后为大家准备的福利时间:简历模板+Java面试题+热门技术系列教程视频
[外链图片转存中…(img-MpGapAxN-1714408613573)]
[外链图片转存中…(img-PFvh1jVV-1714408613573)]
[外链图片转存中…(img-xvv6PgKo-1714408613573)]
2830

被折叠的 条评论
为什么被折叠?



