fenyun689 发表于 2021-02-19 17:50

1. 你知道怎么样的代码可以帮助GC减负吗?

写简单直观的代码,不要玩花招。过分设计、过多的封装/抽象层,常常会让GC很难受(导致需要处理的对象增多)。

要理解:GC是伙伴,不是仆人。在保持代码结构良好、直观易懂的前提下,减少没必要的对象分配总是好的。

不要调用System.gc() <- 可能影响GC的统计数据和未来决策.

不要随意使用“对象池” <- 为了优化GC而使用对象池常常是非常有害的。为了别的有用的目的,例如说持有初始化开销高的资源而使用对象池,这才是通常可取的场景。

通常不用关心对局部变量置null

小心使用ThreadLocal,特别是当跟线程池搭配使用的时候 <- 如果用线程池来跑任务,而这些任务向ThreadLocal写入了数据,那么应该注意在任务完成时清理ThreadLocal,不然容易泄漏

如果使用堆外内存来实现Java对象的缓存,而且在堆外内存里存的是序列化后的Java对象的话,要小心使用时的反序列化开销及其伴随的频繁创建对象的开销。




2. 程序内存溢出了,应该怎么办?我们应该怎样写代码,才能更好的帮助程序在出问题时,尽快定位问题?

JVM运行时首先需要类加载器(classLoader)加载所需类的字节码文件。加载完毕交由执行引擎执行,在执行过程中需要一段空间来存储数据(类比CPU与主存)。这段内存空间的分配和释放过程正是我们需要关心的运行时数据区。内存溢出的情况就是从类加载器加载的时候开始出现的,内存溢出分为两大类:OutOfMemoryError和StackOverflowError。以下举出内存溢出的情况。
1、java堆内存溢出。2、java堆内存泄漏。3、垃圾回收超时内存溢出。4、Metaspace内存溢出。5、直接内存内存溢出。6、创建本地线程内存溢出。7、超出交换区内存溢出。8、超出交换区内存溢出。

3. 多线程编程总能提高程序性能吗?使用时需要注意什么?
不一定。主要有两个方面,一方面是线程调度,另一个方面是线程协作。

首先,我们看一下线程调度,在实际开发中,线程数往往是大于 CPU 核心数的,比如 CPU 核心数可能是 8 核、16 核,等等,但线程数可能达到成百上千个。这种情况下,操作系统就会按照一定的调度算法,给每个线程分配时间片,让每个线程都有机会得到运行。而在进行调度时就会引起上下文切换,上下文切换会挂起当前正在执行的线程并保存当前的状态,然后寻找下一处即将恢复执行的代码,**下一个线程,以此类推,反复执行。但上下文切换带来的开销是比较大的,假设我们的任务内容非常短,比如只进行简单的计算,那么就有可能发生我们上下文切换带来的性能开销比执行线程本身内容带来的开销还要大的情况。

不仅上下文切换会带来性能问题,缓存失效也有可能带来性能问题。由于程序有很大概率会再次访问刚才访问过的数据,所以为了加速整个程序的运行,会使用缓存,这样我们在使用相同数据时就可以很快地获取数据。可一旦进行了线程调度,切换到其他线程,CPU就会去执行不同的代码,原有的缓存就很可能失效了,需要重新缓存新的数据,这也会造成一定的开销,所以线程调度器为了避免频繁地发生上下文切换,通常会给被调度到的线程设置最小的执行时间,也就是只有执行完这段时间之后,才可能进行下一次的调度,由此减少上下文切换的次数。
那么什么情况会导致密集的上下文切换呢?如果程序频繁地竞争锁,或者由于 IO 读写等原因导致频繁阻塞,那么这个程序就可能需要更多的上下文切换,这也就导致了更大的开销,我们应该尽量避免这种情况的发生。

除了线程调度之外,线程协作同样也有可能带来性能问题。因为线程之间如果有共享数据,为了避免数据错乱,为了保证线程安全,就有可能禁止编译器和 CPU 对其进行重排序等优化,也可能出于同步的目的,反复把线程工作内存的数据 flush 到主存中,然后再从主内存 refresh 到其他线程的工作内存中,等等。这些问题在单线程中并不存在,但在多线程中为了确保数据的正确性,就不得不采取上述方法,因为线程安全的优先级要比性能优先级更高,这也间接降低了我们的性能。





jieforest 发表于 2021-02-24 16:03

1. 你知道怎么样的代码可以帮助GC减负吗?
这话题有点大。
总的来说,要想GC的负担最小,那么编程就应该遵循Java的最佳实践,这样写出来的代码在JVM上运行时,GC的负担最小。故问题可归纳为Java编程的最佳实践。
下面列出一些Java编程的最佳实践:
1)遵循Java的命名约定
2)类的成员变量应该使用private
3)不要省略catch语句块中的代码
4)使用StringBuilder或StringBuffer来代替String字符串的连结操作
5)使用枚举类或常量类来代替常量接口
6)避免冗余初始化(0-false-null)
7)在类中,像0、false或null这些值,原本就是String、int、boolean类型的默认初始值。故如果初始值为0、false或null,无需做初始化。
8)在使用集合的场景,最好使用对集合的接口引用
9)不要在索引(或计数器)变量中使用for循环
以上仅仅列举了一些常见的最佳实践,实际上Java编程的最佳实践非常多,比如单元测试的最佳实践,JDBC的最佳实践,Java异常处理的最佳实践,Spring框架使用的最佳实践......
一个Java项目,把所有的部分都进行调优,比如设计调优、代码调优、模块调优、JVM调优、数据库调优,最终得到的项目源码,GC的负担肯定最小。

2. 程序内存溢出了,应该怎么办?我们应该怎样写代码,才能更好的帮助程序在出问题时,尽快定位问题?
JVM管理两种类型的内存,堆和非堆。
如果是堆内存溢出,即java.lang.OutOfMemoryError: Java heap space,最简单的解决方法是调整Xmx参数,把值改大一些。如果这样还解决不了问题,使用工具去定位内存泄露。
如果是非堆内存溢出,即java.lang.OutOfMemoryError: PermGen space。在启动Java程序时调整-XX:PermSize和-XX:MaxPermSize两个参数,把值改大一些。
另外还有元空间的内存溢出,即java.lang.OutOfMemoryError: Metaspace。遇到此问题,可以在启动Java程序时调整-XX:MetaspaceSize和-XX:MaxMetaspaceSize参数来解决。
除了以上情况,或许还可能会遇到交换区内存溢出、栈内存溢出等问题。遇到这些问题具体情况具体分析。

3. 多线程编程总能提高程序性能吗?使用时需要注意什么?
Java的线程与CPU内核数紧密相关,故Java在多线程编程时要特别注意项目的最终运行环境,即生产环境的CPU内核数。多线程编程能提高性能是确定无疑的,但线程并非越多越好,线程切换带来的开销不可小视,要注意线程数跟生产环境的CPU内核数相匹配。

forgaoqiang 发表于 2021-02-28 18:35

先占个坑吧 最近也跳到Java技术栈上研究一段时间,回头发表下自己的拙见,分为两个部分吧,第一个部分关于图书的试读章节,第二部分关于提出的问题
PART1:
试读章节包含了图书的完整的第一章和第二章节的2.2小节的部分,总共44页
第一章节是一个通用性的描述,关于Java性能调优的概述:
第一章提供的基本的思路和方法,首先进行了性能概述:
1)什么是性能,举例出常见的Eclipse IDE的卡死问题,这就是性能问题。
2)评价性能的参数,比如执行时间、占用CPU时间、内存使用、磁盘I/O使用、网络使用以及响应时间等。
3)木桶原理和性能瓶颈,将上面中的各个参数单独拿出来说,表明程序会因为某一个短板导致整体性能下降。
4)Amdahl定律,阿姆达尔定律是一个计算机科学界的经验法则,因吉恩·阿姆达尔而得名。它代表了处理器并行运算之后效率提升的能力,特别强调仅仅增加核心不一定能够起到有效作用。
5)性能调优不同层次上的方法,比如在设计角度上调优、代码上的调优(好的实现)、JVM(Java虚拟机)上面的调优,JVM层次需要程序员对JVM工作原理有所了解,以便写出对对JVM友好的代码、还有用到的持久化存储数据库上的调优、甚至包括运行JVM的OS的操作系统调优等层次。
6)基本策略和手段,首先要有策略和目标,然后在对着目标进行。

第二章节开始细化讲解上面的设计角度上的调优,毕竟优秀的设计能够规避掉很多的潜在的性能上的问题,重点开始讲解 设计模式(Design Pattern)
1)单例模式,他是一种对象创建模式,只产生一个对象的具体实例,这样可以省去每次New一个新对象的开销,同时减少GC时间。
2)代理模式,代理对象完成用户的请求,屏蔽掉用户对真实对象的直接访问,很多时候通过对真实的对象进行封装,可以打到延迟加载的目的。【延迟加载可以在真正使用组件的时候才初始化它,有效的节约资源】
3)享元模式,这个模式主要的设计目的就是为了提升性能,核心思想是:系统中有多个相同的对象时,只需要对象的一份拷贝即可,不需要每次使用都创建新的对象,会使用工厂类创建。是不是听起来和单例模式很像,但是要注意主要区别 享元设计模式是一个类有很多对象,而单例是一个类仅一个对象。
4)装饰者模式,我们一直在讲代码复用,那么装饰者模式就是这么做的,继承是紧密的耦合,委托是松散的耦合,装饰者模式就是复用系统中各个组件,运行时将这些组件进行叠加,说到这里就要提下 midway这些nodejs框架,本身提供装饰器,那么下面是使用KOA还是express都可以支持。
5)观察者模式,一个对象依赖另一个对象状态时,就可以使用。在编程思想中,这种 状态机 的编程方式,在大量系统中使用。
6)值对象模式,这个是比较复杂的一种用法,之前并没有用到过,看描述是将对象的各个属性进行封装,然后传递整个对象,是系统拥有更好的交互模型,减少网络通信次数来减少网络流量,提高系统的性能。
7)业务代理模式,和值对象的方式类似,将一组远程调用的业务流程封装在一个位于展示层的代理类中,.... 说实话,作为Java的菜鸟,这个方式并没有真正理解
常用优化组件和方法:
缓冲和缓存(Buffer和Cache),试读章节就此打住,┭┮﹏┭┮


PART II:

1. 你知道怎么样的代码可以帮助GC减负吗?
Java语言的一大特性之一就是提供垃圾回收GC机制,Java程序员可以任意的创建对象和使用内存而无需对内存回收做显示的工作。如果要对GC减负,基本方法就是两个维度,1)减少不必要的对象创建 2)编写GC友好的代码
正如试读章节中讲到的,合理的设计模式就可以提高性能,比如上面提到的采用单例模式以及享元模式,都可以有效的降低内存占用,从而减少被回收对象的数量。
Java不同版本采用GC方式也不相同,包括标记删除、G1GC、ZGC。
减负的方法如下:
1)使用更多短生命周期的和体积小的对象,并且对象不改变指向。
2)避免显示的System.gc()方法调用,交给GC可以更有效的左整体规划垃圾回收。
3)采用弱引用等方法

2. 程序内存溢出了,应该怎么办?我们应该怎样写代码,才能更好的帮助程序在出问题时,尽快定位问题?
内存移除基本上是发生了内存泄露导致的,如果纯粹的物理机器无法满足内存需求那另当别论,应该尽量使用try catch的方法,在关键节点和容易出问题的代码上做异常处理,出现问题也容易定位。
对于内存泄露,也有工具比如 Memory Analyzer Tool 可以进行分析,正如C语言常用的内存泄露分析工具valgrind一样,可以给出很多关键的信息,特别容易看出来哪一段代码会有内存泄露现象。


3. 多线程编程总能提高程序性能吗?使用时需要注意什么?

多线程不一定提升性能,尤其是单核心处理器的场景,多线程可能会增加开销,NodeJS即使单线程也能做到极高的性能,主要是异步的功效。
多线程编程如果线程之间无需操作共享变量,那么多线程是安全的,如果需要共同操作公有资源,就要注意并发和锁,避免出现不一致的问题。





页: 1 [2]
查看完整版本: 【话题讨论+送书福利】Java开发者如何跳出性能陷阱?