这个文章主要是自己关于jvm内存的一点思考,范围比较杂,设计类加载器,方法区和内存泄露等

一、 内存是怎么分配的

主要是指针碰撞和空闲列表两类。新生代一般是复制算法,老年代一般是标记整理(cms用了标记清除导致内存碎片较多)。复制和标记整理采用指针碰撞,标记清除采用标记清除。如果是指针碰撞需要考虑指针的冲突,有cas和本地线程分配缓冲两种策略。

二、 方法区

方法区包括 常量池(jdk7被移入堆中),代码,类信息,类静态变量。jdk8后方法区实现为本地内存,受本机物理内存影响。

三、 java对象的生命周期

创建对象(如果对象的类型没有加载则加载),使用对象,不可达,被标记,如果实现了finalize进入终结队列,回收

四、 Class对象是在方法区还是堆中

深入理解java虚拟机p215,Class是在方法区中,作为程序访问方法区的入口。这一点其实没有太大的意义,但是知道类的静态字段存在在方法区中,在类加载的准备阶段初始化内存是很重要的。

五、java对象的大小

对象由对象头,数据和对其对齐填充字节。主要考虑的是对象头,对象头包含一个指向对应Class的指针和对象信息。32位下,两者都是4字节,共8字节。64位下两者都是8字节共16字节,如果开启了指针压缩那么Class指针为4字节共12字节。如果是数组则多4个字节表明数组的长度。

六、 类加载的初始化阶段

在java并发编程思想中有个代码例子如下:

public abstract class testAbstract {
public testAbstract() {
System.out.println("absStart");
testChildImpl();
System.out.println("absEnd");
} public abstract void testChildImpl();
}
public class testAbstractImpl extends testAbstract {
private String str = "123"; public testAbstractImpl() {
System.out.println(str);
System.out.println("real");
} public static void main(String[] args) {
new testAbstractImpl();
} @Override
public void testChildImpl() {
System.out.println(str); }
}

问输出的是什么? 答案:

absStart
null
absEnd
123
real

以前在看这个问题的时候根本不知道答案,看了也觉得输出中的null非常难以理解,在重新看书的时候想到了其实就是关于类的初始化。上面的子类作为启动类需要初始化,但是根据加载的顺序,父类要先初始化,父类初始化的时候调用了子类的函数,这时子类输出静态字段,因为子类的静态字段在类加载的准备阶段已经分配了内存并且有默认值null,“123”要到子类初始化的时候才去赋值,所以输出为null。一切都解释通了,这也是我喜欢计算机的原因,一切都有原因。 (可以参考深入理解jvm的p219)

七、Class.forName和Classloader

区别在于Class.forName加载类的时候会经过加载阶段的初始化阶段。Classloader的loadClass根据传入resolve是否为true执行连接阶段(验证,准备,解析),始终不会执行初始化阶段。测试代码如下:

  public class ClassLoadTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
Class<? extends ClassLoader> aClass = ClassLoader.getSystemClassLoader().getClass();
Method loadClass = aClass.getDeclaredMethod("loadClass", String.class, boolean.class);
loadClass.setAccessible(true);
loadClass.invoke(systemClassLoader, "jvm.classLoad.test.ClassOut", true);
System.in.read();
// Class.forName("jvm.classLoad.test.ClassOut");
}
}

另外补充下类加载器的一些重要方法:

  1. loadClass 定义了双亲委派模型的框架
  2. findClass 自己基本ClassLoader一般重写这个方法
  3. findLoadedClass 找到当前类加载器加载的类
  4. defineClass 类加载的加载阶段(注意这个时候Class对象已经在内存中生成)
  5. resolveClass 类加载的验证、准备、解析阶段

八、 类加载器的回收

很多场景都是讲的是类的回收,我在网上查的时候很少有谈到类加载的回收。类加载的回收三个条件都有讲到,其中需要类加载器被回收,那么类加载器什么时候能被回收呢?在这里的我的理解是当类加载器不可达时即可被回收(代码中就是将所有类加载器的引用消除,一般我们都在本地存有它的引用然后置为null,但是有很多意想不到的类加载器引用泄露的问题,例如序列化,log4j,ThreadLocal问题),注意类的回收是方法区,类加载器则是堆中。由此可以解释ThreadLocal导致tomcat内存泄露的问题(tomcat新版已经解决了这个问题https://wiki.apache.org/tomcat/MemoryLeakProtection)。对于这个问题有时间在Tomcat好好看下源码。网上看了很多关于类加载器泄露的坑,自己编写要十分谨慎。

九、 内存泄露

看了一篇文章讲了内存泄露很不错,自己在公司遇到过一次连接泄露的问题,所以正好整理了下相关的问题来分享下。

场景描述:我们公司的用户服务对接了第三方腾讯云通信服务,在用户注册的时候我们需要走http接口调腾讯云,问题就出在http连接那块,同事当时采用了,线上出现了cpu100%的问题,日志出现java.lang.OutOfMemoryError: GC overhead limit exceeded

排查思路:这个其实很好定位,本来还想打印线程栈看下到底是哪个导致的cpu100%,一看日志直接定位到gc出问题。GC overhead limit exceeded是指gc占用了大量的cpu时间又回收不了内存引起的,从内存泄露去考虑,重启服务 ,启动参数加上-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./user.hprof -verbose:gc -Xloggc:user%t.log。问题复现的时候获得了堆的dump文件,然后通过Jprofile分析,发现有大量的http.HttpKeepAliveCache实例,占用了80%的内存,大致定位到是由于http连接泄露。同事封装的HttpUtil中使用了HttpsURLConnection,在读取数据的时候没有关闭InputStream导致连接没有关闭。

十、String拼接导致内存溢出

公司的后台有段时间会间歇性的卡顿,严重的情况下会导致cpu100%。在cpu100%的时候,通过top定位到进程号,然后输入H切换到线程,记住具体的进程号,使用jstack打印java进程的线程栈,jstack输出为十六进制,需要将top的转换成十六进制的然后入找线程经常卡在哪个方法。定位到方法发现是查询用户关联设备号的方法出问题,方法的逻辑是从数据库查询设备号,在内存中以以逗号分隔拼接返回,如1,2,3。这个bug的原因是有如下:

  1. sql出错,导致查询返回数据量很多,正常情况最多几百个,但是异常情况有七万个设备号
  2. 字符串拼接采用str+="1234"的形式,导致大量的内存分配和回收。

运营在点击后台查询的时候发现没返回,点掉就重新点,导致服务器多个线程卡在这个方法造成cpu100%。解决完sql,改用StringBuilder问题解决。

JVM内存管理的一些思考的更多相关文章

  1. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

  2. java jvm内存管理/gc策略/参数设置

    1. JVM内存管理:深入垃圾收集器与内存分配策略 http://www.iteye.com/topic/802638 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想 ...

  3. JVM内存管理------垃圾搜集器参数精解

    本文是GC相关的最后一篇,这次LZ只是罗列一下hotspot JVM中垃圾搜集器相关的重点参数,以及各个参数的解释.废话不多说,这就开始. 垃圾搜集器文章传送门 JVM内存管理------JAVA语言 ...

  4. Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收

    很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...

  5. JVM内存管理(二)

    JVM内存管理          JVM在执行java程序的过程中,会把内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖 ...

  6. JVM内存管理及垃圾回收

    一.JVM内存的构 Java虚拟机会将内存分为几个不同的管理区,这些区域各自有各自的用途,根据不同的特点,承担不同的任务以及在垃圾回收时运用不同的算法.总体分为下面几个部分: 程序计数器(Progra ...

  7. 现代JVM内存管理方法的发展历程,GC的实现及相关设计概述(转)

    JVM区域总体分两类,heap区和非heap区.heap区又分:Eden Space(伊甸园).Survivor Space(幸存者区).Tenured Gen(老年代-养老区). 非heap区又分: ...

  8. java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3)

    概述 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又 ...

  9. Java的内存 -JVM 内存管理

    一.综述 如果你学过C或者C++,那么你应该感受过它们对内存那种强大的掌控力.但是强大的能力往往需要更强大的控制力才能保证能力不被滥用,如果滥用C/C++的内存管理那么很容易出现指针满天飞的情况,不出 ...

随机推荐

  1. ionic提示弹框

    //提示框 .factory('TipsPort', function ($ionicPopup) { var TipsPort = function (tipsText, SureFunction, ...

  2. Kotlin入门(27)文件读写操作

    Java的文件处理用到了io库java.io,该库虽然功能强大,但是与文件内容的交互还得通过输入输出流中转,致使文件读写操作颇为繁琐.因此,开发者通常得自己重新封装一个文件存取的工具类,以便在日常开发 ...

  3. Gson解析空字符串异常的处理

    面对一些不规范的json,我们的gson解析经常会抛出各种异常导致app崩溃,这里可以采取一些措施来避免. 我们期望在后台返回的json异常时,也能解析成功,空值对应的转换为默认值,如:newsId= ...

  4. 你不可不知的Java引用类型之——PhantomReference源码详解

    定义 PhantomReference是虚引用,该引用不会影响不会影响对象的生命周期,也无法从虚引用中获取对象实例. 说明 源码介绍部分其实也没多大内容,主要内容都在前面介绍中说完了.PhantomR ...

  5. Bootstrap table 分页 In asp.net MVC

    中文翻译文档: http://blog.csdn.net/rickiyeat/article/details/56483577 版本说明: Jquery v2.1.1 Bootstrap V3.3.7 ...

  6. openstack nova工作流程

    工作流程请求:nova boot --image ttylinux --flavor 1 i-01nova-api 接受请求,一个tcp REST请求.nova-api 发送一个创建虚拟机的请求到消息 ...

  7. MongoDB添加仲裁节点报错replica set IDs do not match办法

    背景:由于历史原因,某个MongoDB副本集只有一主一从双节点,无法满足自动故障转移要求,需要配置一个仲裁节点. 原有节点192.168.10.20:27017,192.168.10.21:27017 ...

  8. Microsoft.AspNet.Identity 重置密码

    重置密码:先生成重置密码的Token,然后调用ResetPassword方法重置密码,密码要符合规则.. ApplicationUserManager UserManager => _userM ...

  9. Linux CFS调度器之task_tick_fair处理周期性调度器--Linux进程的管理与调度(二十九)

    1. CFS如何处理周期性调度器 周期性调度器的工作由scheduler_tick函数完成(定义在kernel/sched/core.c, line 2910), 在scheduler_tick中周期 ...

  10. DVWA v1.9 新手指南

    DVWA简介 DVWA(Damn Vulnerable Web Application)是一个用来进行安全脆弱性鉴定的PHP/MySQL Web应用,旨在为安全专业人员测试自己的专业技能和工具提供合法 ...