澳门新葡萄京娱乐场 1

Java内存问题的一些见解

在Java中,内存泄露和其他内存相关问题在性能和可扩展性方面表现的最为突出。我们有充分的理由去详细地讨论他们。

电子书下载地址:
http://wiki.jikexueyuan.com/project/java-interview-bible/

Java内存分配有那些?

基本来说分为:

  • 寄存器:JVM内部虚拟寄存器,存取速度非常快,程序不可控制.
  • 栈区:保存局部变量的值,包括:1.用来保存基本数据类型的值2.保存类的实例,即堆区对象的引用(指针),也可以用来保存加载方法时的帧.
  • 堆:用来存放动态产生的数据,比如new出来的对象.注意创建出来的对象只包含属于各自的成员变量,并不包括成员方法.因为同一个类的对拥有各自的成员变量,存储在各自的堆中,但是他们共享该类的方法,并不是每创建一个对象把成员复制一次.
  • 常量池:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合.包括直接常量(基本类型
    String)和其他类型 方法 字段的符号引用
    池中的数据和数组一样通过索引访问
    由于常量池包含了一个类型所有的其他类型.
  • 澳门新葡萄京娱乐场 ,代码段:用来存放从硬盘上读取的源程序代码.
  • 数据段:用来存放static 定义的静态成员.

    澳门新葡萄京娱乐场 1

    1.png

[http://blog.csdn.net/OyangYujun/article/details/41173747](https://link.jianshu.com?t=http%3A%2F%2Fblog.csdn.net%2FOyangYujun%2Farticle%2Fdetails%2F41173747)

Java内存模型——或者更确切的说垃圾回收器——已经解决了许多内存问题。然而同时,也带来了新的问题。特别是在有着大量并行用户的J2EE运行环境下,内存越来越成为一种至关重要的资源。乍看之下,这似乎有些奇怪,因为当前内存已经足够廉价,并且我们也有了64位的JVM和更先进的垃圾回收算法。

1. GC线程是否为守护线程?()

答案:是

解析:线程分为守护线程和非守护线程(即用户线程)。

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着
JVM 一同结束工作。
守护线程最典型的应用就是 GC (垃圾回收器)

java中垃圾回收机制:

  1. 垃圾回收的意义
      在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象;而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾。JVM的一个系统级线程会自动释放该内存块。垃圾回收意味着程序不再需要的对象是”无用信息”,这些信息将被丢弃。当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。事实上,除了释放没用的对象,垃圾回收也可以清除内存记录碎片。由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。
      垃圾回收能自动释放内存空间,减轻编程的负担。这使Java
    虚拟机具有一些优点。首先,它能使编程效率提高。在没有垃圾回收机制的时候,可能要花许多时间来解决一个难懂的存储器问题。在用Java语言编程的时候,靠垃圾回收机制可大大缩短时间。其次是它保护程序的完整性,
    垃圾回收是Java语言安全性策略的一个重要部份。
      垃圾回收的一个潜在的缺点是它的开销影响程序性能。Java虚拟机必须追踪运行程序中有用的对象,而且最终释放没用的对象。这一个过程需要花费处理器的时间。其次垃圾回收算法的不完备性,早先采用的某些垃圾回收算法就不能保证100%收集到所有的废弃内存。当然随着垃圾回收算法的不断改进以及软硬件运行效率的不断提升,这些问题都可以迎刃而解。
    垃圾回收有以下的几个特点:
    (1)垃圾收集发生的不可预知性:由于实现了不同的垃圾回收算法和采用了不同的收集机制,所以它有可能是定时发生,有可能是当出现系统空闲CPU资源时发生,也有可能是和原始的垃圾收集一样,等到内存消耗出现极限时发生,这与垃圾收集器的选择和具体的设置都有关系。
      (2)垃圾收集的精确性:主要包括2
    个方面:(a)垃圾收集器能够精确标记活着的对象;(b)垃圾收集器能够精确地定位对象之间的引用关系。前者是完全地回收所有废弃对象的前提,否则就可能造成内存泄漏。而后者则是实现归并和复制等算法的必要条件。所有不可达对象都能够可靠地得到回收,所有对象都能够重新分配,允许对象的复制和对象内存的缩并,这样就有效地防止内存的支离破碎。
      (3)现在有许多种不同的垃圾收集器,每种有其算法且其表现各异,既有当垃圾收集开始时就停止应用程序的运行,又有当垃圾收集开始时也允许应用程序的线程运行,还有在同一时间垃圾收集多线程运行。
      (4)垃圾收集的实现和具体的JVM
    以及JVM的内存模型有非常紧密的关系。不同的JVM
    可能采用不同的垃圾收集,而JVM
    的内存模型决定着该JVM可以采用哪些类型垃圾收集。现在,HotSpot
    系列JVM中的内存系统都采用先进的面向对象的框架设计,这使得该系列JVM都可以采用最先进的垃圾收集。
      (5)随着技术的发展,现代垃圾收集技术提供许多可选的垃圾收集器,而且在配置每种收集器的时候又可以设置不同的参数,这就使得根据不同的应用环境获得最优的应用性能成为可能。

接下来,我们将会仔细的讨论一下关于Java内存的问题。这些问题可以分为四组:

2. 解释内存中的栈(stack)、堆(heap)和静态存储区的用法。

答:通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用内存中的栈空间;而通过new关键字和构造器创建的对象放在堆空间;程序中的字面量(literal)如直接书写的100、“hello”和常量都是放在静态存储区中。栈空间操作最快但是也很小,通常大量的对象都是放在堆空间,整个内存包括硬盘上的虚拟内存都可以被当成堆空间来使用。

String str = new String(“hello”);

上面的语句中 str 放在栈上,用 new
创建出来的字符串对象放在堆上,而“hello”这个字面量放在静态存储区。

java中error和exception的区别:

Error类和exception类的父类都是throwable类,他们的区别是:
error类一般是指与虚拟机相关的问题,如系统崩溃
,虚拟机错误,内存空间不足,方法调用栈溢等.对于这类错误的导致的应用程序中断,仅靠程序无法恢复和预防,遇到这样的错误,建议让程序终止.
Exception类表示程序可以处理的异常,可以捕获这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常.
Exception类又分为运行时异常和受检查的异常,运行时异常,编译能通过但运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止.而受检查的异常,要么用try…catch捕获,要么用throws子句声明抛出,交给他的父类处理,否则编译不会通过.

  • 在Java中,内存泄露一般都是由于引用对象不再被使用而造成的。当有多个引用的对象,同时这些对象又不再需要,然而开发者又忘记清理它们,这时极容易导致内存泄露的发生。
  • 执行消耗太多的内存而导致不必要的高内存占用。这在为了用户体验而管理大量状态信息的 Web 应用中很常见。随着活跃用户数量的增加,内存很快到达了上限。未绑定或低效缓存配置是持续高内存占用的另一来源。
  • 当用户负载增加时,低效的对象创建容易导致性能问题。从而垃圾回收器必须不断地清理堆内存。而这导致了垃圾回收器对CPU产生了不必要的高占用。随着CPU因垃圾回收而被阻塞,应用程序响应时间频繁的增加,导致其一直处于中等负载之下。这种行为也成为“GC trashing”。
  • 低效的垃圾回收行为往往是由于垃圾回收器的缺失或者错误的配置。这些垃圾回收器将会时刻追踪对象是否被清理。然而这种行为如何以及何时发生必须由配置或者程序员,或者系统架构师决定的。通常,人们只是简单地“忘记”了正确的配置和优化垃圾回收器。我曾参加过一些关于“性能”的专题讨论会,发现一个简单的参数变化将会导致高达25%的性能提升。

3. Java 中会存在内存泄漏吗,请简单描述。

答:理论上 Java 因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是
Java
被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被
GC 回收也会发生内存泄露。一个例子就是 Hibernate
的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象。下面的例子也展示了
Java 中发生内存泄露的情况:

package com.lovo;  

import java.util.Arrays;  
import java.util.EmptyStackException;  

public class MyStack<T> {  
    private T[] elements;  
    private int size = 0;  

    private static final int INIT_CAPACITY = 16;  

    public MyStack() {  
        elements = (T[]) new Object[INIT_CAPACITY];  
    }  

    public void push(T elem) {  
        ensureCapacity();  
        elements[size++] = elem;  
    }  

    public T pop() {  
        if(size == 0)   
            throw new EmptyStackException();  
        return elements[--size];  
    }  

    private void ensureCapacity() {  
        if(elements.length == size) {  
            elements = Arrays.copyOf(elements, 2 * size + 1);  
        }  
    }  
}  

上面的代码实现了一个栈(先进后出(FILO))结构,乍看之下似乎没有什么明显的问题,它甚至可以通过你编写的各种单元测试。然而其中的
pop 却存在内存泄露的问题,当我们用 pop
方法弹出栈中的对象时,该对象不会被当作垃圾回收,即使使用栈的程序不再引用这些对象,因为栈内部维护着对这些对象的过期引用(obsolete
reference)。在支持垃圾回收的语言中,内存泄露是很隐蔽的,这种内存泄露其实就是无意识的对象保持。如果一个对象引用被无意识的保留起来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其他对象,即使这样的对象只有少数几个,也可能会导致很多的对象被排除在垃圾回收之外,从而对性能造成重大影响,极端情况下会引发
Disk Paging(物理内存与硬盘的虚拟内存交换数据),甚至造成
OutOfMemoryError。

字节流和字符流有什么区别?

程序中的输入输出都是以流的形式保存的,流中保存的实际上全都是字节文件。
字符流处理的单元为2个字节的Unicode字符
字符流(一个字符占两个字节)
字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使用到缓冲区的
字节流在操作文件时,即使不关闭资源(close方法),文件也能输出,但是如果字符流不使用close方法的话,则不会输出任何内容,说明字符流用的是缓冲区,并且可以使用flush方法强制进行刷新缓冲区,这时才能在不close的情况下输出内容.

字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,再写入底层设备,这为我们向IO设别写入或读取字符串提供了一点点方便。
字符向字节转换时,要注意编码的问题,因为字符串转成字节数组,

在大多数情况下,内存问题不仅影响性能,还会影响可扩展性。每次请求消耗的内存数量越高,用户或Session可以执行的并行事务就越少。在某些情况下内存问题也影响可用性。当JVM耗尽了内存或者即将接近内存极限,这个时候它将退出并报OutOfMemory错误。这时经理会来到你的办公室,你就知道自己摊上大事了。

4. GC 是什么?为什么要有 GC?

答:GC
是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java
提供的 GC
功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java
语言没有提供释放已分配内存的显示操作方法。Java
程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc()
Runtime.getRuntime().gc() ,但 JVM 可以屏蔽掉显示的垃圾回收调用。

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在
Java 诞生初期,垃圾回收是 Java
最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今
Java 的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得 iOS
的系统比 Android 系统有更好的用户体验,其中一个深层次的原因就在于
Android 系统中垃圾回收的不可预知性。

==与equlas有什么区别?

==可以判断基本数据类型值是否相等,也可以判断两个对象指向的内存地址是否相同,也就是说判断两个对象是否是同一个对象,Equlas通常用来做字符串比较

内存问题很难被解决通常有两个原因: 第一,某些情况下分析很复杂,也很困难,特别是如果你缺少正确的方法来解决他们;其次,他们通常是应用程序的架构基础。简单的代码更改不会帮助解决他们。

5. 第 3 行中生成的 object在第几行执行后成为 garbage collection 的对象?

1.public class MyClass {

2. public StringBuffer aMethod() {

3. StringBuffer sf = new StringBuffer(“Hello”);

4. StringBuffer[] sf_arr = new StringBuffer[1];

5. sf_arr[0] = sf;

6. sf = null;

7. sf_arr[0] = null;

8. return sf;

9. }

10.}

答:第 7 行

拦截器与过滤器的区别?

  • 拦截器是基于java的反射机制的,而过滤器是基于函数回调。
  • 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
  • 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
  • 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
  • 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被.
  • 执行顺序 :过滤前 – 拦截前 – Action处理 – 拦截后 – 过滤后
    http://964874044.iteye.com/blog/1941069

为了使开发过程更容易,我会展示一些实际应用中常被使用的反模式。这些模式已经能够在开发过程中避免内存问题。

6. 描述一下 JVM 加载 class 文件的原理机制?

答:JVM 中类的装载是由类加载器(ClassLoader) 和它的子类来实现的,Java
中的类加载器是一个重要的 Java
运行时系统组件,它负责在运行时查找和装入类文件中的类。

  1. 由于 Java 的跨平台性,经过编译的 Java
    源程序并不是一个可执行程序,而是一个或多个类文件。当 Java
    程序需要使用某个类时,JVM
    会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的
    .class 文件中的数据读入到内存中,通常是创建一个字节数组读入 .class
    文件,然后产生与所加载类对应的 Class 对象。加载完成后,Class
    对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后
    JVM 对类进行初始化,包括:1.
    如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2.
    如果类中存在初始化语句,就依次执行这些初始化语句。

  2. 类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从JDK
    1.2开始,类加载过程采取了父亲委托机制(PDM)。PDM 更好的保证了 Java
    平台的安全性,在该机制中,JVM 自带的 Bootstrap
    是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM
    不会向 Java 程序提供对 Bootstrap
    的引用。下面是关于几个类加载器的说明:

a)Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);

b)Extension:从 java.ext.dirs
系统属性所指定的目录中加载类库,它的父加载器是 Bootstrap;

c)System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量
classpath 或者系统属性 java.class.path
所指定的目录中记载类,是用户自定义加载器的默认父加载器。

SESSION, COOKIE区别

session数据放在服务器上,cookie则放在客户浏览器上。cookie不太安全,因为可以分析出本地cookie,并进行cookie欺骗,考虑安全应使用session。session会在一定时间内保存在服务器上,当访问增多时,会比较占用服务器的性能,考虑减轻服务器压力则应该使用cookie。单个cookie保持的数据不超过4k,很多浏览器都限制要给站点最多保存20个cookie。
http://blog.csdn.net/duan1078774504/article/details/51912868

HTTPSession作为缓存

此反模式是指滥用HTTPSession对象作为数据缓存。session对象的存在是为了存储信息,这个信息里面存在着一个HTTP请求。这也称为一个Session状态。这意味着,数据将被保存直至它们被处理。这些方法通常存在于一些重要的web应用程序中。web应用程序除了在服务器上存储这些信息外,没有别的方法。然而,一些信息是能够存储在cookie中,但是这将会带来一些其他的影响。

在cookie中,尽可能地保持少而短的数据,这是非常重要的。有时候很容易发生这种现象,session里存储着成兆字节的数据对象。这将会立即导致堆栈高占用和内存短缺。同时并行用户的数量非常有限,JVM将应对越来越多出现OutOfMemoryError错误的用户。多数用户Session也有其他性能损失。集群场景的session复制中,这将会增加序列化和沟通工作将导致额外的性能和可伸缩性问题。

在某些项目中这些问题的解决方案是增加数量的内存和切换到64位jvm。他们无法抵抗住仅仅增加几个G大小的堆栈内存的诱惑。然而,与其提供一个对真正问题的解决方案,不如隐藏这个现象。这个“解决方案”只是暂时的,同时还会引入了一个新的问题。越来越大的堆内存使它更难以找到“真正的”内存问题。对这种非常大的堆(大约6G)来说,大部分可用的分析工具是无法处理这些内存垃圾。我们在dynaTrace投入了大量的研发工作希望能够有效地分析大量的内存垃圾。随着这个问题变得越来越重要,一种新的JSR规范也提到了它。

由于应用程序架构尚未明确,导致Session缓存问题经常出现,。在开发过程中,数据被轻松而又简单的放入session当中。这是经常发生的,类似于一种“add and forget”方式,即没有人能够确保当这种数据不再需要时是被移除的。通常,当session超时时不需要的session数据应该被处理。在企业中,一些应用程序常常大量使用Session超时,这将会导致无法正常工作。此外经常使用非常高的Session超时- 24小时为用户提供额外的“体验”,使他们不必再次登录。

举一个实际的例子,从session里的数据库列表中选择所需要的数据。其目的是为了避免不必要的数据库查询。(是不是觉得有点过早优化呢?)。这将导致在session对象中为每个单独的用户放入几千个字节。虽然,缓存这些信息它是合理的,但用户session可以肯定是一个错误的地方。

另外一个例子是,为了管理Session状态而滥用Hibernate session。Hibernatesession对象只是为了快速访问数据库而放入HTTPsession对象中。然而,这将导致更多必要的数据被存储。同时每个用户的内存占用也将显著提高。

现如今,AJAX应用程序Session状态也可以在客户端进行管理。这使服务端程序变成无状态的,或接近无状态的,同时也显然有着更好的可扩展性。

简述Servlet中请求的转发与重定向的区别.

线程本地变量内存泄露

在Java中使用ThreadLocal变量是为了在一个特定的线程中绑定变量。这意味着每个线程都有它自己的单独实例。这种方法一般在一个线程中用于处理状态信息,例如用户授权。然而,一个ThreadLocal变量的生命周期与另外一个线程的生命周期是息息相关的。被遗忘的ThreadLocal变量很容易导致内存问题,尤其是在应用服务器中。

如果忘记了设置ThreadLocal变量,尤其是在应用服务器中,这很容易导致内存问题。应用服务器利用线程池避免常量不断创建和线程销毁。举个例子,一个HTTPServletRequest类在运行时得到一个空闲的已分配的线程,在执行完后将它回传到线程池中。如果应用程序逻辑使用ThreadLocal变量和忘记了显式地移除它们,这时,内存是不会被释放的。

根据线程池大小——在程序系统中这些线程池可以是几百个线程。同时,由ThreadLocal变量引用的对象的大小,这可能导致一些问题。例如,在最坏的情况下,一个200个线程的线程池和一个5M大小的线程池将会导致1 GB的不必要的内存占用。这将立即导致强烈的垃圾回收反应,同时导致糟糕的响应时间和潜在的OutOfMemoryError错误。

一个实际的例子就是在JBossWS 1.2.0版本中出现的一个bug(在JBossWS1.2.1版本已经被修复)——“DOMUtils doesn’t clear thread locals”。此问题就是ThreadLocal变量导致的,它引用了一个14MB的解析文档。

转发:forward ,内部请求转发,前一个程序进行请求,后一个程序用来返回响应

请 求转发发生在服务器端,由服务器(比如servlet)控制。
请求转发过程在同一个请求当中完成,只会返回一个响应。
请求转发使用RequestDispatcher对象的forward()或include()方法
请求转发相对快:因为请求转发过程在同一请求中。

大型临时对象

大型临时对象在最坏的情况下也能导致outofmemoryerror错误或者至少强烈的GC反应。例如,如果非常大的文档(XML、PDF、图片…)必须阅读和处理时。在一个特定的情况下,应用程序几分钟都没有响应或性能非常有限,几乎没有可用的。其中根本原因是垃圾回收反应过于强烈。下面对读取PDF文档的一段代码作了详细分析:

byte tmpData[] = new byte [1024];

int offs = 0;

do{

int readLen = bis.read (tmpData, offs, tmpData.length - offs);

if (readLen == -1)

break;

offs+= readLen;

if (oofs == tmpData.length){

byte newres[] = new byte[tmpData.length + 1024];

System.arraycopy(tmpData, 0, newres, 0, tmpData.length);

tmpData = newres;

}

} while (true);

这些文档采用按固定字节数的方式来读取。首先,他们被读入中字节数组中,然后发送到用户的浏览器中。然而仅仅几个并行请求将会导致堆溢出。由于读取文档采用了极其低效的算法,这将导致问题越来越糟糕。最初的想法只是创建1KB的初始字节数组。如果这个数组满了,则一个新的1KB数组将被创建,同时这个老的数组将拷贝到新的数组中。这意味着当读取文档时,一个新数组将被创建,同时将读取的每字节都复制到新数组中。这将导致大量的临时对象和两倍于实际数据大小的内存消耗——数据将永久被复制。

在处理大量数据时,优化处理逻辑性能是至关重要的。在这种情况下,一个简单的负载测试会显示这一问题。

重定向:redirect,服务端发送给客户端一个重定向的临时响应头,这个响应头包含重定向之后的url,客户端用新的URL重新向服务器发送一个请求

重定向发生在客户端,由客户(通常是浏览器)控制。
重定向过程则发生在两个不同的请求中,会返回两个不同响应。
重定向则使用HttpServletResponse对象的sendRedirect()方法。

糟糕的垃圾回收器配置

到目前为止,在所提到的情境中出现的问题基本都是由应用程序代码所导致的。然而,这些原因的根源是由于垃圾回收器配置错误,或者丢失。我常常看到用户相信他们的应用程序服务器的默认设置,同时也相信应用服务器的开发者了解哪些是自己的程序最好的。无论如何,堆的配置很大程度上取决于应用程序和实际使用场景。根据场景来调整参数,应用程序才能更好地执行。和一批执行长期任务的应用程序相比,一个执行大量短而持久的应用程序配置起来是完全不同的。此外,实际的配置还取决于JVM使用情况。对IBM来说,什么才能使Sun Jvm正常运行可能是一场噩梦(或至少是不理想的)。配置错误的垃圾收集器通常不会立即被确认为性能问题的根源(除非你监控了垃圾收集器的活动)。通常我们肉眼可见的问题都是响应过慢。同时,理解垃圾回收活动与响应时间的关系也是不明显的。如果垃圾回收的时间与响应时间没什么关联,人们通常会发现一个非常复杂的性能问题。响应时间和执行时间度量问题主要体现在应用程序——对于这种现象,在不同的地方都没有一个明显的模式。

下图显示了事务指标与垃圾收集时间在dynaTrace中的关系。我发现了一些情况,关于垃圾回收器的优化问题。人们正打算花几周的时间去解决如何在几分钟内设置解决性能问题。

Java 中 sleep 方法和 wait 方法的区别?

虽然两者都是用来暂停当前运行的线程,但是 sleep()
实际上只是短暂停顿,因为它不会释放锁,而 wait()
意味着条件等待,这就是为什么该方法要释放锁,因为只有这样,其他等待的线程才能在满足条件时获取到该锁。
https://www.cnblogs.com/bethunebtj/p/5696999.html

类加载器内存泄露

在谈到内存泄漏时,大部分人主要认为是堆中的对象。除了对象,类和常量也是托管在堆中。根据JVM,它们被放入堆中特定的区域。例如Sun JVM使用所谓的永久代或PermGen。通常情况下,类被放入堆中好几次。仅仅是因为他们已经被不同的类加载器加载。在现代化企业级应用程序中,加载类的内存占用能够达到几百MB。

关键是避免无谓地增加类的大小。一个很好的例子是大量字符串常量的定义——例如在GUI应用程序中。这里所有的文本通常存储在常量。而使用常量字符串的方法原则上是一个好的设计方法,内存消耗不应该被忽视。在真实的情况下,在一个国际化应用程序中,所有常量都会被定义为各种语言。一个很不起眼的代码错误都会影响到已经被加载的类。最终的结果是,在应用程序的永久代中,JVM将出现OutOfMemoryError 错误,同时崩溃。

应用服务器还面临着类加载器泄漏的问题。这些泄漏的原因主要是因为类加载器不能被垃圾回收,因为类加载器中的类的一个对象仍然活着。结果,这些类并不打算释放这些内存占用。而现在,这个问题已经被J2EE 应用程序服务器很好的解决了,它似乎更常出现在OSGI-based应用程序环境。

什么是java序列化,如何实现java序列化?或者请解释Serializable接口的作用。

我们有时候将一个java对象变成字节流的形式传出去或者从一个字节流中恢复成一个java对象,
例如,要将java对象存储到硬盘或者传送给网络上的其他计算机,这个过程我们可以自己写代码去把一个java对象变成某个格式的字节流再传输,
但是,jre本身就提供了这种支持,我们可以调用OutputStream的writeObject方法来做,如果要让java
帮我们做,要被传输的对象必须实现serializable接口,
这样,javac编译时就会进行特殊处理,编译的类才可以被writeObject方法操作,这就是所谓的序列化。
需要被序列化的类必须实现Serializable接口,该接口是一个mini接口,其中没有需要实现的方法,implements
Serializable只是为了标注该对象是可被序列化的。

例如,在web开发中,如果对象被保存在了Session中,tomcat在重启时要把Session对象序列化到硬盘,这个对象就必须实现Serializable接口。
如果对象要经过分布式系统进行网络传输或通过rmi等远程调用,这就需要在网络上传输对象,被传输的对象就必须实现Serializable接口。

总结

在Java应用程序中内存问题通常是多方面的,这容易导致性能和可扩展性的问题。特别是在有着大量并行用户的J2EE应用程序中,内存管理必须是应用程序体系结构的核心部分。然而垃圾回收器对于那些未使用的对象是否被清理并不关心,所以开发人员还是需要适当的内存管理。此外,应用程序内存管理设计是应用程序配置的核心部分。

你的经验

这些都是我在现实世界应用程序中发现的反模式。如果你有额外的反模式或共同的问题,我很愿意更多地了解他们。

关于作者

这篇文章基于我和codecentric的作者Mirko Novakovic共同研究的性能反模式系列。

其他感兴趣的博客

由于性能反模式是我的爱好,我将定期发布关于反模式的帖子。它们将会从这些帖子里选择你们可能感兴趣的一篇帖子。

  • 关于Hibernate缓存的一些见解
  • 远程问题

如果你想要了解更多关于如何解决像本文中提到的内存问题,和其他一些java运行坏境相关的问题,你也许对我同事最近一本关于持续应用程序性能管理的白皮书感兴趣。

发表评论

电子邮件地址不会被公开。 必填项已用*标注