JVM相关
jvm详解、GC、堆内存参数调优_春_的博客-CSDN博客_jvm堆内存调优
常见JVM面试题及答案整理_Java程序员-张凯的博客-CSDN博客_jvm面试题
JVM的模块
常见问题
JVM的理解?java8虚拟机和之前的变化更新?
撤销了永久带,引入了元空间
OOM?栈溢出?怎么分析?
常见的OOM情况有三种:
1)**java.lang.OutOfMemoryError: Java heap space** ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
2**)java.lang.OutOfMemoryError: PermGen space/ Metaspace------>java永久代(元数据)溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize/MetaspaceSize=64m -XX:MaxPermSize/MaxMetaspaceSize =256m的形式修改。另外,过多的常量也会导致方法区溢出。
3)**java.lang.StackOverflowError -**----->不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。
JVM性能调优
-
调整JVM参数
-
分析dump文件
-
分析内存泄露
-
监控GC状态
JVM常用调优参数有哪些?
内存快照如何抓取?怎么分析dump文件?
下载MemoryAnalyzer
导入dump文件
分析图表、对象树
dump可以是内存溢出时让其自动生成,或者手工直接导。配置jvm参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/biapp/m.hprof
手工直接导,PID为进程号
jmap -dump:live,format=b,file=m.hprof PID
jvm的位置
jvm的体系结构
运行时数据区:有亮色的有灰色的,灰色的就是占得内存非常小,几乎不存在GC垃圾回收,并且线程独占的,亮色的存在垃圾回收,并且所有线程共享。
类加载器
1)通过一个类的全限定名类获取定义此类的二进制字节流。2)将字节流的所代表的静态存储结构转化成方法区运行时的数据结构。3)在内存中生成一个代表这个类的java.lang.Class的对象,做作为方法区的这个类各种数据访问的入口。 验证就是为了确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害到虚拟机本身。 准备阶段是给类变量赋予初始值的阶段。这里的初始值是指变量默认的值,并不是用户赋予的初始值。 初始化阶段是类加载的最后一步。是给类变量赋予初始值
类加载器就是把类加载阶段中的”通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到java虚拟机外部来实现的代码模块。
双亲委派模型
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器器制定; 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终到达顶层的启动类加载器; 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式
启动类加载器、扩展类加载器、应用类加载器(系统类加载器)、用户自定义类加载器。 启动类加载器:这个类负责将存放在JAVA_HOME/lib目录或者被-Xbootclasspath参数所指定的路径中的并且是虚拟机内存中。 扩展类加载器:负责加载JAVA_HOME/lib/ext目录中或者被java.ext.dirs系统变量指定路径中的所有类库,开发者可以直接使用扩展类加载器。 应用程序类加载器:负责加载用户类路径上指定的类加载器,一般情况下就是程序中默认的类加载器。 各个类加载器之间的关系:
public static void main(String[] args) {
ClassLoader cl = new Student().getClass().getClassLoader(); //应用程序类加载器
ClassLoader parent = cl.getParent(); //扩展类加载器
ClassLoader grand = parent.getParent(); //启动类加载器,C++编写的,因此没有toString打印
System.out.println(cl);
System.out.println(parent);
System.out.println(grand);
System.out.println("String类的类加载器是:" + new String("ddf").getClass().getClassLoader());
}
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@6d6f6e28
null
String类的类加载器是:null
注
:
启动类加载器 |
这个类加载器是jvm自己实现的,加载home/lib 或者-Xbootclasspath 指定的路径下的而且文件名能被识别(rt.jar,tools.jar 名字不符合也不会加载) |
扩展类加载器 |
以代码的形式实现的 ,他负责加载home/lib/ext目录中或者被java.ext.dirs系统变量所指定的类库 |
应用程序类加载器 |
他负责加载ClassPath上所有的类库 |
自定义类加载器 |
可以选择继承java.lang.ClassLoader类的方式实现自己的类加载器来满足一些特殊的需求 JDK1.2之前 会去继承ClassLoader类,并且重写loadClass方法,1.2之后不建议覆盖loadClass方法,而是建议把逻辑写在findClass()方法里面或者可以直接继承URLClassLoader类 |
沙箱安全机制
Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络
。不同级别的沙箱对这些资源访问的限制也可以不一样。
所有的Java程序运行都可以指定沙箱,可以定制安全策略。
Native
1、一个native方法就是一个Java调用非Java代码的接口。一个native方法是指该方法的实现由非Java语言实现,比如用C或C++实现。
2、在定义一个native方法时,并不提供实现体(比较像定义一个Java Interface),因为其实现体是由非Java语言在外面实现的
主要是因为JAVA无法对操作系统底层进行操作,但是可以通过jni(java native interface)调用其他语言来实现底层的访问。
PC寄存器
这块内存区域很小,它是当前线程所执行的字节码的行号指示器,
字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。
如果执行的是一个Native方法, 那这个计数器是空的。
方法区
方法区(Method Area)同Java 堆,是各个线程共享的内存区域,用于存储虚拟机已经加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
在不同的JDK版本中,方法区中存储的数据是不一样的。
在JDK1.6及之前,运行时常量池是方法区的一个部分,同时方法区里面存储了类的元数据信息、静态变量、即时编译器编译后的代码(比如spring 使用IOC或者AOP创建bean时,或者使用cglib,反射的形式动态生成class信息等)等。
在JDK1.7及以后,JVM已经将运行时常量池从方法区中移了出来,在JVM堆开辟了一块区域存放常量池。
栈
JVM堆和JVM栈是程序运行的关键,很有必要把他们的关系说清楚。
JVM栈是运行时的单位,而JVM堆是存储的单位。
JVM栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;JVM堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
在Java中一个线程就会相应有一个线程JVM栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程JVM栈。而JVM堆则是所有线程共享的。JVM栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而JVM堆只负责存储对象信息。
堆
JVM的堆被同一个JVM实例中的所有Java线程共享
三种JVM
HotSpot,oracle JRockit,IBM J9
JRockit是oracle发明的,用于其WebLogic服务器,IBM JVM是IBM发明的用于其Websphere服务器(所以在某行开发的时候,他们用的是IBM的JDK,因为他们使用的IBM的应用程序服务器Websphere,使用其他JDK可能存在兼容性问题)。
新生区、老年区、永久区
堆内存调优
默认情况系:分配的总内存是电脑的1/4,而初始化的内存是1/64
如果出现OOM错误,代表堆内存不够,这时候我们可以通过调整堆内存大小,来进行堆内存的调优
我们也可以使用JProfile来进行,错误定位,进而对该部分的代码进行优化
GC
主要的作用区域是堆和方法区
常用算法:
-
标记/清除算法
-
复制算法
-
标记整理算法
JMM
Java内存模型(Java Memory Model,JMM)JMM主要是为了规定了线程和内存之间的一些关系。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。
内存泄漏和内存溢出
内存泄漏与溢出的区别
-
内存泄漏是指分配出去的内存无法回收了。
-
内存溢出是指程序要求的内存,超出了系统所能分配的范围,从而发生溢出。比如用byte类型的变量存储10000这个数据,就属于内存溢出。
-
内存溢出是提供的内存不够;内存泄漏是无法再提供内存资源。
何时产生内存泄漏
-
静态集合类:在使用Set、Vector、HashMap等集合类的时候需要特别注意,有可能会发生内存泄漏。当这些集合被定义成静态的时候,由于它们的生命周期跟应用程序一样长,这时候,就有可能会发生内存泄漏。
jvm中的内存溢出与内存泄露 - sunweiye - 博客园
内存泄漏是指本应该被GC回收的无用对象没有被回收,导致的内存空间的浪费,当内存泄露严重时会导致OOM。Java内存泄露根本原因是:长生命周期的对象持有短生命周期对象的引用,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被GC回收。
-
监听器:在Java中,我们经常会使用到监听器,如对某个控件添加单击监听器addOnClickListener(),但往往释放对象的时候会忘记删除监听器,这就有可能造成内存泄漏。好的方法就是,在释放对象的时候,应该记住释放所有监听器,这就能避免了因为监听器而导致的内存泄漏。
-
各种连接:Java中的连接包括数据库连接、网络连接和io连接,如果没有显式调用其close()方法,是不会自动关闭的,这些连接就不能被GC回收而导致内存泄漏。一般情况下,在try代码块里创建连接,在finally里释放连接,就能够避免此类内存泄漏。
-
外部模块的引用:调用外部模块的时候,也应该注意防止内存泄漏。如模块A调用了外部模块B的一个方法,如:public void register(Object o)。这个方法有可能就使得A模块持有传入对象的引用,这时候需要查看B模块是否提供了去除引用的方法,如unregister()。这种情况容易忽略,而且发生了内存泄漏的话,比较难察觉,应该在编写代码过程中就应该注意此类问题。
-
单例模式:使用单例模式的时候也有可能导致内存泄漏。因为单例对象初始化后将在JVM的整个生命周期内存在,如果它持有一个外部对象(生命周期比较短)的引用,那么这个外部对象就不能被回收,而导致内存泄漏。如果这个外部对象还持有其它对象的引用,那么内存泄漏会更严重,因此需要特别注意此类情况。这种情况就需要考虑下单例模式的设计会不会有问题,应该怎样保证不会产生内存泄漏问题。
常见实例问题
思考:假如我们自己写了一个java.lang.String的类,我们是否可以替换调JDK本身的类?
答案是否定的。我们不能实现。为什么呢?我看很多网上解释是说双亲委托机制解决这个问题,其实不是非常的准确。因为双亲委托机制是可以打破的,你完全可以自己写一个classLoader来加载自己写的java.lang.String类,但是你会发现也不会加载成功,具体就是因为针对java.*开头的类,jvm的实现中已经保证了必须由bootstrap来加载。