系列文章目录
文章目录
- 系列文章目录
- 一、案例
- 二、前情提要
-
- 1、字节码文件的核心组成有哪些?
- 2、类的生命周期
-
- [2.1 加载](#2.1 加载)
- [2.2 连接:](#2.2 连接:)
- [2.3 初始化](#2.3 初始化)
- 3、类加载器
- 4、为什么需要双亲委派机制?
- 5、为什么需要打破双亲委派机制?
- 三、Java中垃圾回收
-
- [1、Young GC=年轻代垃圾回收](#1、Young GC=年轻代垃圾回收)
-
- 1、Eden区空间不足---触发YGC
- 2、Eden区+S区都满了----触发YGC
- [3、部分垃圾回收器full gc之前---触发YGC](#3、部分垃圾回收器full gc之前---触发YGC)
- [2、old GC=老年代垃圾回收](#2、old GC=老年代垃圾回收)
- [3、Full GC=整堆垃圾回收](#3、Full GC=整堆垃圾回收)
-
- 1、老年代--触发FGC
- 2、永久代或者元空间不足
- [3、调用system.gc, jmap-dump等命令](#3、调用system.gc, jmap-dump等命令)
- 4、新生代晋升到老年代失败
- 四、OOM
- 五、python中垃圾回收
-
- [1、引用计数(Reference Counting)](#1、引用计数(Reference Counting))
- [2、分代收集(Generational GC)](#2、分代收集(Generational GC))
- [3、循环检测(Cycle Detection)](#3、循环检测(Cycle Detection))
- 4、触发时机
一、案例
使用阿里的arthas去确认升级完的字节码文件是不是最新的
思路:
在出问题的服务器上部署一个arthas,并启动
连接arthas的控制台,使用jad命令加上想要查看的类名,反编译出源码
确认源码是否是最新的
二、前情提要
1、字节码文件的核心组成有哪些?
基本信息:魔数,字节码文件对应的java版本号,访问标识,父类,接口
常量池:保存了字符串常量,类和接口名,字段名
字段:当前类或者接口声明的字段信息
方法:当前类或者接口的方法信息字节码指令
属性:类的属性,比如源码的文件名,内部类的列表等
2、类的生命周期
2.1 加载
类加载器根据类的全限定名通过不同的渠道 以二进制流的方式获取字节码信息
(本地文件-磁盘上的字节码文件,程序运行时使用动态代理生成的类)
类加载器在加载完类以后,java虚拟机会将字节码中的信息保存到内存的方法去中,生成一个InstanceKlass对象(字节码文件:基本信息,常量池,字段,方法xx),保存类的所有信息,里边还包含实现特定功能比如多态的信息,同时,Java虚拟机还会在堆中生成一份与方法区中数据类似的java.lang.Class对象,
2.2 连接:
验证:验证字节码信息
准备:准备阶段为静态变量分配内存并设置初始值,final修饰的基本数据类型的静态变量,准备阶段直接会将代码中的值进行赋值
解析:将常量池中的符号引用替换为直接引用。符号引用就是在字节码文件中使用编号来访问常量池中的内容,直接引用就是使用内存地址直接访问
2.3 初始化
初始化阶段会执行静态代码块中的代码,并为静态变量赋值
初始化阶段会执行字节码文件中clinit部分的字节码指令
下面几种方式都会导致类的初始化
访问一个类的静态变量或者静态方法,注意变量是final修饰的并且右边是常量不会触发初始化
调用class.forName(xxxxx)
new 一个该类的对象时
执行main方法的当前类

3、类加载器
jdk8和之前的版本:
虚拟机底层实现
启动类加载器:加载java中最核心的类,启动类加载器是由Hotspot虚拟机提供的,使用C++编写的类加载器,默认加载java安装目录下/jre/lib下的类文件,比如rt.jar
java :允许扩展java中比较通用的类, 扩展类加载器和应用程序类加载器都是jdk提供的,使用java编写的类加载器,他们的源码都位于sun.misc.Launcher中,是一个静态内部类。继承自URLClassLoader。具备通过目录或者指定jar包将字节码文件加载到内存中
扩展类加载器:默认加载java安装目录jre/lib/ext下的类文件
应用程序类加载器:加载classpath下的类文件,以及第三方依赖的类文件
4、为什么需要双亲委派机制?
双亲委派指的是:自底向上查找是否加载过, 再由顶向下进行加载(向下委派起到了一个加载优先级的作用),向上查找如果已经加载过,就直接返回class对象,加载过程结束,这样就能避免一个类重复加载
保证类加载的安全性:通过双亲委派机制避免恶意代码替换JDK中的核心类库,比如java.lang.String, 确保核心类库的完整性和安全性
避免重复加载:避免同一个类被加载多次
5、为什么需要打破双亲委派机制?
三种方式:
自定义类加载器: 通过重写loadClass方法,就可以将双亲委派机制的代码去除
线程上下文类加载器:利用上下文类加载器加载类,比如JDBC和JNDI等
osgi框架的类加载器
三、Java中垃圾回收
1、Young GC=年轻代垃圾回收
仅针对新生代(eden区和s0,s1区), 当新生代内存,尤其是eden区被填满时触发,只回收新生代里的对象,老年代不受影响,回收频率较高,回收时间较短,因为新生代对象大多数都是周明周期短,容易被回收
1、Eden区空间不足---触发YGC
新生代被划分为三个区域:Eden区, s0区, s1区, 大部分新创建的对象会先分配到eden区
当eden区的对象填满,无法再为新的对象分配空间时,young gc会被触发,回收新生代中不再使用的对象
2、Eden区+S区都满了----触发YGC
如果eden区和s区的空间都不足以存放新分配的对象时,YGC也会被触发,清理空间并将幸存的对象转移到S区域老年代
3、部分垃圾回收器full gc之前---触发YGC
Parallel Scavenge垃圾回收器在full gc 之前先执行下YGC
2、old GC=老年代垃圾回收
只针对老年代,当老年代空间不足触发,通常是从新生代晋升到老年代对象或多,或者老年代存活对象数量达到一定一定阈值。执行方式是只回收老年代的对象,新生代不受影响,执行时间比young GC长,因为老年代中的对象存活时间长,数量多
3、Full GC=整堆垃圾回收
顾名思义,对整个堆进行回收,当老年代空间不足且取法通过老年代垃圾回收释放足够空间,或其他情况导致系统内存压力较大时触发,回收时间长,会触发整个JVM的停顿,对性能有影响
1、老年代--触发FGC
当老年代空间不足,且无法通过老年代垃圾回收释放足够空间,
2、永久代或者元空间不足
java8以前,如果永久代空间不足,会触发FGC, java8以后,永久代被移除,元空间取代了永久代,如果元空间内存不足,也可能触发FGC
3、调用system.gc, jmap-dump等命令
显示调用system.gc方法。可能会触发FGC
4、新生代晋升到老年代失败
年轻代的大对象或者长期存活的对象被晋升到老年代,如果老年代空间不足,也会触发FGC
四、OOM
oom表示JVM无法为应用程序分配足够的内存,导致程序崩溃
堆内存溢出:
java堆用于存放对象实例,如果创建了过多对象,或者内存泄漏导致对象无法被回收,会导致堆内存耗尽,如果有大量创建对象或者集合类的场景,持续增加数据但是未释放就会产生堆内存溢出
栈内存溢出:
每个线程都会有独立的栈空间,栈用于存储方法调用的信息,局部变量,方法参数等,如果方法调用层次特别深或者有无限递归,栈空间耗尽
方法区或者元空间溢出
java8以后,永久代被替换成元空间,用本地内存实现,在频繁加载和卸载类的情况下,会导致溢出
直接内存溢出
java nio使用直接内存来加快IO操作,该内存不受JVM堆内存的限制,如果分配过多的直接内存,超过了设置的最大值,也会导致内存溢出
线程数过多导致内存溢出
每个线程都需要栈空间和一定的操作系统资源,如果创建过多线程而超出操作系统的资源限制,可能无法再创建新的线程,导致OOM, 应该合理设置线程池的大小,避免无限创建新的线程
GC执行时间太长导致的溢出
五、python中垃圾回收
Python 也有垃圾回收机制(Garbage Collection, GC),但它的实现方式和 Java 的不同。虽然 Java 中有 YGC(Young Generation GC)、Old GC(Old Generation GC) 和 Full GC 等概念,Python 并没有完全相同的分类,但它的垃圾回收机制同样负责自动管理内存,避免内存泄漏
Python 使用的是 引用计数(Reference Counting) 作为主要的垃圾回收机制,
同时结合了 分代收集(Generational Garbage Collection) 和 循环检测(Cycle Detection)
来处理复杂的内存管理问题
1、引用计数(Reference Counting)
每个对象都有一个引用计数器。
当一个对象被引用时,引用计数加一;当引用被移除时,引用计数减一。
当引用计数为 0 时,该对象会被立即回收。
2、分代收集(Generational GC)
Python 将对象分为三个"代":
第 0 代(Generation 0):新创建的对象,频繁回收
第 1 代(Generation 1):在第 0 代中存活下来的对象
第 2 代(Generation 2):在第 1 代中存活下来的对象
与 Java 不同的是,Python 的分代机制不是基于内存区域划分,而是基于对象的生命周期长短。
3、循环检测(Cycle Detection)
对于无法通过引用计数回收的循环引用对象(如两个互相引用的列表),Python 使用 标记-清除(Mark-Sweep) 算法来识别并回收这些对象。
4、触发时机
自动触发:
内存使用达到一定阈值时(由 gc 模块控制)
手动触发
执行 gc.collect() 手动触发