JVM常见问题(一)

(一)java类加载过程?

Java类加载需要经过七个过程。

  • 加载

  加载是类加载的第一个过程,在这个阶段,需要完成三件事情:

    • 通过一个类的全限定名获取该类的二进制流
    • 将该二进制流中的静态存储结构转化为方法去运行时数据结构
    • 在内存中生成该Class的对象,作为该类的数据访问入口
  • 验证

  验证的目的是为了确保Class文件流的信息不会危害到虚拟机,在这个阶段又需要完成四件事。

    • 文件格式验证:验证字节流是否符合Class文件的规范,如,主次版号是否在虚拟机范围内,常量池中的常量是否有不被支持的类型
    • 元数据验证:对字节码描述的信息进行语义分析,如是否有父类,是否继承了不被继承的类,是否继承了不被继承的类
    • 字节码验证:通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对的是方法体的验证,如:方法中的类型转换是否正确,跳转指令是否正确
    • 符号引用的验证:符号引用的验证的这个动作是在后面的解析过程的发生的,主要是为了确保解析的过程动作的正确执行
  • 准备

  此阶段是为类的静态变量分配内存并将其初始化为默认值,而这些内存都将在方法区中进行分配。

  准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化的时候随着对象分配到堆中。

  例如:

  public static int number=666;
  在准备阶段number的初始值是为0的,直到在初始化阶段才会变为666
  • 解析

  这个过程就是在上面验证过程中的符号引用的验证过程。解析的动作并不一定是在初始化的动作完成之前,也有可能是在完成之后

  • 初始化

  初始化是类加载的最后一个步骤,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,

  其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中的定义的Java程序代码

  • 使用
  • 卸载

(二)、JVM加载class文件的原理机制又是什么呢?

  在Java中只有将类(class)文件加载到JVM中才能  运行。

  当运行指定程序时,JVM会将编译生成的.class文件按照需求和一定的规则加载到内存中,并组织成为一个完整的Java应用程序,这个过程是由类加载器完成的,具体来说是由classloader和它的子类来实现的。

  类加载器本身也是一个类,其实质就是把类文件从硬盘读取到内存中。

 

  

  类加载方式为隐式加载和显示加载。

  隐式加载指的是程序在使用new等方式创建对象时,会隐式的调用类的加载器把对应的类加载到JVM中。

  显示加载指的是通过直接调用class.forName()方法来把所需的类加载到JVM中。

  

  任何一个工程都是由许多的类组成的,在程序启动时只需要把用到的类加载到JVM中,其他的类只是在需要用到的时候才会被加载。

  这一方式的优点是加快加载速度,节约程序运行时对内存的开销。

 

  每个类或接口都对应一个 .class 文件,这些文件可以被看成是一个个可以被动态加载的单元,因此当只有部分类被修改时,只需要重新编译变化的类即可,而不需要重新编译所有文件,因此加快了编译速度。

 

  类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(例如基类)完全加载到 JVM 中,至于其他类,则在需要的时候才加载。

  类加载的主要步骤是什么呢?

  • 装载:根据查找路径找到相对应的class文件,然后导入
  • 链接:
    • 检查:检查待加载的class文件的正确性  
    • 准备:给类中的静态变量分配存储空间  
    • 解析:将符号引用转换为直接饮用  
  • 初始化:对静态变量和静态代码块初始化

Java内存的分布

  • 寄存器:无法控制
  • 静态域:static的静态成员变量
  • 常量池:编译时被确定并保存在.class文件中的(final)常量值和一些文本修饰的符号引用(类和接口的全限定名,字段的名称和描述符,方法和名称和描述符)
  • 非RAM存储:硬盘等永久存储空间
  • 堆内存:new 创建的对象和数组,有Java虚拟机自动垃圾回收器管理,存取速度慢,
  • 栈内存:基本类型的变量和对象的引用变量(堆内存空间的访问地址),速度快,可以共享,但是大小与生存期必须确定,缺乏灵活性

Java堆的结构是什么样子的?什么是堆中的永久代?

  JVM中的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在JVM启动的时候被加载。对象所占的堆内存是由自动内存管理系统也就是垃圾回收器。

 

  堆内存是由存活和死亡的对象组成的。

  存活的对象是应用可以访问的,不会被垃圾回收。

  死亡的对象是应用不可访问尚且还没有被垃圾回收器回收掉的对象。一直到垃圾回收器将这些对象回收掉之前,他们会一直占有堆的内存空间。

GC是什么?为什么会有GC?

  GC(Garbage Collection)垃圾回收器,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,

  Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。

简述Java垃圾回收机制。

  在 Java 中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在 JVM 中,有一个垃圾回收线程,它是低优先级的,

  在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

怎么判断一个对象是否被引用?

两种方式:1、引用计数法    2、可达性算法

引用计数法: 引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。

      当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收.

      但是引用计数法的一个弊端就是:也就是说当对象 A 引用对象 B,对象 B 又引用者对象 A,那么此时 A、B 对象的引用计数器都不为零,也就造成无法完成垃圾回收,这样就会造成一个循环引用的问题。

      所以主流的虚拟机都没有采用这种算法。

可达性算法:

      该算法的思想是:(上帝视角)从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到 GC Roots 没有任何引用链相连时,则说明此对象不可用。

  在 Java 中可以作为 GC Roots 的对象有以下几种:

  • 虚拟机占中引用的对象
  • 方法区类静态属性引用的对象
  • 方法区常量池引用的对象
  • 本地方法栈JNI引用的对象

虽然这些算法可以判定一个对象是否能被回收,但是当满足上述条件时,一个对象比不一定会被回收。

当一个对象不可达 GC Root 时,这个对象并不会立马被回收,而是出于一个死缓的阶段,若要被真正的回收需要经历两次标记.

 

如果对象在可达性分析中没有与 GC Root 的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行finalize() 方法。

当对象没有覆盖 finalize() 方法或者已被虚拟机调用过,那么就认为是没必要的。

 

如果该对象有必要执行finalize() 方法,那么这个对象将会放在一个称为 F-Queue 的对队列中,

虚拟机会触发一个 Finalize() 线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这是因为如果finalize() 执行缓慢或者发生了死锁,

那么就会造成 F-Queue 队列一直等待,造成了内存回收系统的崩溃。GC 对处于 F-Queue 中的对象进行第二次被标记,这时,该对象将被移除” 即将回收”集合,等待回收。

垃圾回收的原理和优点。

Java 语言中一个显著的特点就是引入了垃圾回收机制,它使得 Java 程序员在编写程序的时候不再需要考虑内存管理。

由于有个垃圾回收机制,Java 中的对象不再有“作用域”的概念,只有对象的引用才有"作用域"。垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。

垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。

回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。 

什么是内存溢出和内存泄漏。怎么处理?

内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

memory leak会最终会导致out of memory!

内存泄漏可以分为4类:
1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。 
2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。 
3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。 
4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。 

内存溢出的原因以及解决方法

引起内存溢出的原因有很多种,小编列举一下常见的有以下几种:

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的BUG;
5.启动参数内存值设定的过小

内存溢出的解决方案:

第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)

第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。

第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

重点排查以下几点:
1.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。

2.检查代码中是否有死循环或递归调用。

3.检查是否有大循环重复产生新对象实体。

4.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。

5.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

第四步,使用内存查看工具动态查看内存使用情况

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值