继天地之灵气,借前辈之功,开道友之路伴,共勉
目录
1)栈内存溢出(StackOverflowError)常见原因
六、类找不到或无法加载
1、概述
NoClassDefFoundError是一个错误(Error),而ClassNOtFoundException是一个异常,在Java中对于错误和异常的处理是不同的,我们可以从异常中恢复程序但却不应该尝试从错误中恢复程序。
2、产生及解决
1)ClassNotFoundException
产生:Java支持使用Class.forName方法来动态地加载类,任意一个类的类名如果被作为参数传递给这个方法都将导致该类被加载到JVM内存中,如果这个类在类路径中没有被找到,那么此时就会在运行时抛出ClassNotFoundException异常。
解决:解决该问题需要确保所需的类连同它依赖的包存在于类路径中,常见问题在于类名书写错误。
另外还有一个导致ClassNotFoundException的原因就是:当一个类已经某个类加载器加载到内存中了,此时另一个类加载器又尝试着动态地从同一个包中加载这个类。通过控制动态类加载过程,可以避免上述情况发生。
2)NoClassDefFoundError
产生:如果JVM或者ClassLoader实例尝试加载(可以通过正常的方法调用,也可能是使用new来创建新的对象)类的时候却找不到类的定义。要查找的类在编译的时候是存在的,运行的时候却找不到了。这个时候就会导致NoClassDefFoundError。造成该问题的原因可能是打包过程漏掉了部分类,或者jar包出现损坏或者篡改。
解决:查找那些在开发期间存在于类路径下但在运行期间却不在类路径下的类。
3、类找不到或无法加载主类
1)概述
找不到或无法加载主类(Could not find or load main class) 是一种常见的 Java 编程错误,它通常发生在使用命令行运行 Java 程序时。该错误表明 Java 虚拟机无法找到指定的主类或无法加载主类。
错误信息:Error: Could not find or load main class <class-name>
2)分析解决
问题深究
① 类路径(Classpath)设置错误
Java的运行依赖类路径来定位字节码文件(.class),如果路径错误,JVM就无法找到目标类。
② 主类未包含main方法
主类必须定义为:public static void main(String[] args) { ... }
若没有符合标准的main方法,程序无法启动。
③ 字节码文件未正确生成
Java源文件(.java)未成功编译或输出目录设置错误。
④ 包声明与目录结构不匹配
如果类属于某个包(如com.example),则需要确保其目录结构与包声明一致。
排查处理
① 确认编译是否成功
检查编译时是否生成了对应的.class文件:
javac HelloWorld.java
成功后,应在同级目录看到HelloWorld.class文件。
② 检查类路径设置
如果使用命令行运行Java程序,确保类路径包含.class文件所在目录:
java -cp . HelloWorld
注意:如果文件在某个包中,需使用完整类名运行,例如:
java -cp . com.example.HelloWorld
③ 确认包声明与目录结构
如果源文件包含包声明(如package com.example;),需确保文件存放在对应目录结构中。
④ 使用IDE调试
使用Eclipse、IntelliJ IDEA等IDE时,确保:
项目结构清晰,src目录被正确标记为源目录。
构建配置正确(如输出路径、模块设置等)。
3)归纳
- 类路径设置是否正确。
- 包声明是否与目录匹配。
- 编译过程是否正常。
七、死锁
1、什么是死锁
案例
面试官:你给我讲清楚死锁,我就录用你
面试者:你录用我,我就给你讲清楚
死锁的规范定义:集合中的每一个进程都在等待只能由本集合中的其他进程才能引发的事件,那么该组进程是死锁的。
就是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
小结
-
死锁是指在执行过程中,两个或两个以上的进程(或线程)由于竞争资源或彼此通信而阻塞,导致无法继续执行的情况。
-
如果没有外部干预,这些进程将无法向前推进。
-
这种状态被称为系统死锁或死锁产生。
-
这些相互等待的进程被称为死锁进程。
2、产生条件
1)互斥条件
一个资源每次只能被一个进程使用
1)在并发执行的多个事务中,产生死锁的一个重要条件是互斥条件。互斥条件指的是某个资源一次只能被一个事务持有,其他事务必须等待该资源释放才能获取到。
2)互斥条件在死锁产生中起到关键作用,它确保了资源的独占性。当一个事务获得了资源A的锁,其他事务就无法同时获得资源A的访问权限,只能等待该事务释放资源A的锁。如果同时有另一个事务持有资源B的锁,并且尝试获取资源A的锁,就会发生死锁。
3)简单来说,互斥条件是指每个资源只能被一个事务持有,其他事务必须等待所需资源的释放。
2)占有且等待
一个进程因请求资源而阻塞时,对已获得的资源保持不放
1)在并发执行的多个事务中,产生死锁的另一个重要条件是"占有且等待"。"占有且等待"指的是一个事务在持有某个资源的同时,又等待其他事务所占有的资源。
2)具体来说,当一个事务获得了某个资源的锁,并且在持有该资源的同时,还需要获取其他事务所占有的资源,但该资源又被其他事务持有时,就会产生"占有且等待"的情况。当多个事务相互等待对方所占有的资源时,就可能发生死锁。
3)"占有且等待"条件是死锁产生的一个关键因素。当事务之间存在资源依赖关系,且每个事务只释放部分资源而不是一次性释放所有资源时,就容易出现"占有且等待"的情况。如果没有适当的资源管理策略,就有可能导致死锁的发生。
3)不可强行占有
进程已获得的资源,在末使用完之前,不能强行剥夺。
1)在并发执行的多个事务中,产生死锁的另一个重要条件是"不可强行占有"。不可强行占有指的是一个事务在持有某个资源的同时,又不允许其他事务强行抢占该资源。
2)具体来说,当一个事务获得了某个资源的锁,并且在持有该资源的同时,不可被其他事务强制释放该资源,就会产生"不可强行占有"的情况。当多个事务同时持有某个资源,并且互相等待对方释放资源时,就可能发生死锁。
3)"不可强行占有"条件是死锁产生的另一个关键因素。当事务之间存在资源竞争关系,且每个事务不能被强制释放所占有的资源时,就容易出现"不可强行占有"的情况。如果没有适当的资源管理策略,就有可能导致死锁的发生。
4)循环等待条件
若干进程之间形成一种头尾相接的循环等待资源关系
1)在并发执行的多个事务中,产生死锁的另一个重要条件是"循环等待"。循环等待指的是多个事务之间形成了一个闭环,每个事务都在等待下一个事务所占有的资源。
2)具体来说,当多个事务之间存在资源依赖关系,并且这些事务形成了一个环路,每个事务都在等待下一个事务所占有的资源时,就会产生"循环等待"的情况。这样,每个事务都无法继续执行,导致系统无法进一步进行,即产生死锁。
3)循环等待条件是死锁产生的另一个关键因素。当事务之间存在循环依赖关系,且每个事务都在等待下一个事务所占有的资源时,就容易出现循环等待的情况。如果没有适当的资源分配和调度策略,就有可能导致死锁的发生。
3、预防死锁
不让这四个必要条件成立,资源合理分配,避免进程永久占据系统资源
②一个不能完整重装的报文能被检测出来,并要求发送该报文的源端系统重新传送;
③为每个节点配备一个后备缓冲空间,用以暂存不完整的报文。
①、②两种方法不能很满意地解决重装死锁,因为它们使端系统中的协议复杂化了。一般的设计中,网络层应该对端系统透明,也即端系统不该考虑诸如报文拆、装之类的事。③方法虽然不涉及端系统,但使每个节点增加了开销。
4、解决方法
1)死锁预防
这是一种较简单和直观的事先预防的方法。方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法,已被广泛使用。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率和系统吞吐量降低。
2)死锁避免
系统对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源;如果分配后系统可能发生死锁,则不予分配,否则予以分配。这是一种保证系统不进入死锁状态的动态策略。
3)死锁检测和解除
先检测:这种方法并不须事先采取任何限制性措施,也不必检查系统是否已经进入不安全区,此方法允许系统在运行过程中发生死锁。但可通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源。检测方法包括定时检测、效率低时检测、进程等待时检测等。
然后解除死锁:采取适当措施,从系统中将已发生的死锁清除掉。
5、解除死锁
破坏不可抢占:通过设置优先级,使优先级高的进程能够抢占资源。
破坏循环等待:保证多个进程(线程)的执行顺序相同,从而避免循环等待。例如,如果执行顺序是A -> B -> C,那么就可以避免循环等待。
最常用的避免方法是破坏循环等待,即确保多个事务的执行顺序相同。
例如,事务1执行顺序为A -> B -> C,事务2执行顺序为C -> D -> A,这种情况可能导致死锁。因为事务1占有了A并等待C,而事务2占有了C并等待A。因此,为了避免死锁,可以将事务2的执行顺序改为A -> D -> C。
6、数据库死锁
1)死锁原因
-
在数据库中,当多个事务并发执行时,也可能发生死锁。
-
例如,事务1持有资源A的锁,并尝试获取资源B的锁,而事务2持有资源B的锁,并尝试获取资源A的锁。
-
在这种情况下,就会发生死锁。
-
当发生死锁时,系统会产生异常,指示发生了死锁情况。
2)常见数据库死锁
在数据库中,当发生死锁时,通常会报错并提供相应的错误信息
2.1. Oracle数据库:
ORA-00060: deadlock detected while waiting for resource
2.2. MySQL数据库:
Deadlock found when trying to get lock; try restarting transaction
八、并发异常
1、概述
并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
并发异常是指在多线程环境中,当一个线程在访问或修改数据时,另一个线程也在访问或修改同一数据,导致数据不一致或程序异常。常见的并发异常包括并发修改异常
2、常见类型
1)ConcurrentModificationException:当在迭代器遍历集合时,如果集合被另一个线程修改,就会抛出此异常。这是因为迭代器在遍历过程中会检查集合的修改次数(modCount)是否与预期的修改次数(expectedModCount)一致,如果不一致则抛出异常。
2)死锁:多个线程互相等待对方释放资源,导致所有线程都无法继续执行。死锁是并发编程中常见的问题之一。
3)竞态条件:多个线程同时访问和修改同一资源,导致结果不确定。例如,两个线程同时增加一个计数器的值,可能导致最终结果比预期的小。
3、产生原因
1)共享资源访问冲突:多个线程同时访问和修改同一资源,导致数据不一致。
2)同步机制不当:缺乏适当的同步机制(如锁、信号量等)来控制对共享资源的访问。
3)竞态条件:多个线程同时执行,导致不可预测的结果。
4、防止并发
1)使用同步机制:通过锁、信号量等同步机制来控制对共享资源的访问,确保同一时间只有一个线程可以修改资源。
2)使用并发集合:Java 提供了ConcurrentHashMap、CopyOnWriteArrayList等并发集合,这些集合内部已经处理了并发问题,使用这些集合可以减少并发异常的发生。
3)避免共享资源:尽可能设计无共享的数据结构或算法,减少线程间的竞争。
4)使用原子变量:对于简单的操作(如自增、自减等),可以使用AtomicInteger等原子类来保证操作的原子性。
九、栈内存溢出异常
1、概述
内存溢出错误分为StackOverflowError和OutOfMemoryError,前者是栈中出现溢出,后者一般是堆或方法区出现溢出,简称OOM
1)栈溢出 StackOverflowError
栈溢出一般都是因为没有正确的结束递归导致的,无限递归导致超出栈内存(-Xss)限制时就会抛出StackOverflowError。这种情况直接根据异常信息定位到代码位置进行修正即可。
2)方法区溢出 OOM
当方法区中加载的类过多,比如通过动态代理生成很多代理类或者热部署时热加载了过多的类,多到超出方法区内存限制时(-XX:MaxMetaspaceSize),会抛出OutOfMemoryError。但这种情况一般也比较少见,如果真出现这种情况可以考虑增加MetaspaceSize,或者拆分服务,使得一个服务使用的类不超出限制。
3)堆溢出 OOM
其实大部分OOM都是发生在堆区,当堆中存储的对象过多,GC来不及回收或者回收不掉,没有足够空间创建新对象,就会抛出OutOfMemoryError。
2、原因分析
1)栈内存溢出(StackOverflowError)常见原因
栈溢出原因就是方法执行时创建的栈帧超过了栈的深度。最有可能的就是方法递归调用产生这种结果。
2)堆内存溢出(OOM)常见原因
①、OutOfMemoryError: Java heap space。在创建新的对象时, 堆内存中的空间不足以存放新创建的对象时发生。产生原因:程序中出现了死循环,不断创建对象;程序占用内存太多,超过了JVM堆设置的最大值。
②、OutOfMemoryError: unable to create new native thread。产生原因:系统内存耗尽,无法为新线程分配内存;创建线程数超过了操作系统的限制。
③、OutOfMemoryError: PermGen space。永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。
④、OutOfMemoryError:GC overhead limit exceeded。超过98%的时间都在用来做GC并且回收了不到2%的堆内存。连续多次的GC,都回收了不到2%的极端情况下才会抛出。
3、排查
1)检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
2)检查代码中是否有死循环或递归调用。
3)检查是否有大循环重复产生新对象实体。
4)检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中 数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
5)检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
4、解决办法
说白了就是程序运行要用到的内存大于虚拟机能提供的最大内存就发生内存溢出了。
一个是优化程序代码,如果业务庞大,逻辑复杂,尽量减少全局变量的引用,让程序使用完变量的时候释放该引用能够让垃圾回收器回收,释放资源。
二就是物理解决,增大物理内存,然后通过:-Xms256m -Xmx256m -XX:MaxNewSize=256m -XX:MaxPermSize=256m的修改
1)设置虚拟内存
提示:在 JVM中如果 98%的时间是用于 GC且可用的 Heap size 不足 2%的时候将抛出此异常信息。
提示: Heap Size 最大不要超过可用物理内存的 80%,一般的要将 -Xms和 -Xmx选项设置为相同,而 -Xmn为 1/4的 -Xmx值。
提示: JVM初始分配的内存由 -Xms指定,默认是物理内存的 1/64; JVM最大分配的内存由 -Xmx指定,默认是物理内存的 1/4。
默认空余堆内存小于 40%时, JVM就会增大堆直到 -Xmx的最大限制;空余堆内存大于 70%时, JVM会减少堆直到 -Xms的最小限制。因此服务器一般设置 -Xms、 -Xmx相等以避免在每次 GC 后调整堆的大小。
提示:假设物理内存无限大的话, JVM内存的最大值跟操作系统有很大的关系。
简单的说就 32位处理器虽然可控内存空间有 4GB,但是具体的操作系统会给一个限制,
这个限制一般是 2GB-3GB(一般来说 Windows系统下为 1.5G-2G, Linux系统下为 2G-3G), 而 64bit以上的处理器就不会有限制了
提示:注意:如果 Xms超过了 Xmx值,或者堆最大值和非堆最大值的总和超过了物理内 存或者操作系统的最大限制都会引起服务器启动不起来。
提示:设置 NewSize、 MaxNewSize相等, “new”的大小最好不要大于 “old”的一半,原因是 old区如果不够大会频繁的触发 “主 ” GC ,大大降低了性能
JVM使用 -XX:PermSize设置非堆内存初始值,默认是物理内存的 1/64;
由 XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的 1/4。
解决方法:手动设置 Heap size
修改 TOMCAT_HOME/bin/catalina.bat
在“ echo “Using CATALINA_BASE: $CATALINA_BASE””上面加入以下行:
JAVA_OPTS=”-server -Xms800m -Xmx800m -XX:MaxNewSize=256m”
2)代码优化
1 、尽早释放无用对象的引用。使用临时变量的时候,让引用变量在退出活动域后,自动设置为 null ,暗示垃圾收集器来收集该对象,防止发生内存泄露。对于仍然有指针指向的实例, jvm 就不会回收该资源 , 因为垃圾回收会将值为 null 的对象作为垃圾,提高 GC 回收机制效率;
2 、我们的程序里不可避免大量使用字符串处理,避免使用 String ,应大量使用 StringBuffer ,每一个 String 对象都得独立占用内存一块区域;
建议在使用字符串时能使用 StringBuffer 就不要用 String, 这样可以省不少开销;
3 、尽量少用静态变量,因为静态变量是全局的, GC 不会回收的;
4 、避免集中创建对象尤其是大对象, JVM 会突然需要大量内存,这时必然会触发 GC 优化系统内存环境;显示的声明数组空间,而且申请数量还极大。
5 、尽量运用对象池技术以提高系统性能;生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。
6 、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用 hashtable , vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次 new 之后又丢弃
7 、一般都是发生在开启大型文件或跟数据库一次拿了太多的数据,造成 OutOfMemoryError 的状况,这时就大概要计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。
总结
借贤基础夯实,以心细如发,思逻辑之完整,索思维须严谨,
多法得以尝试,代码求精炼,debug手中剑,群魔皆退散。