标签: jvm

Java工程师应该读的几本书

《深入理解Java虚拟机:JVM高级特性与最佳实践》

深入理解Java虚拟机:JVM高级特性与最佳实践

《Java并发编程实战》

Java并发编程实战 阅读详细 »

Java Class文件格式解析

一、Java Class文件是什么

java语言是跨平台的,所谓一次编写,到处运行。之所以是跨平台的,就是java定义了一套与操作系统,硬件无关的字节码格式,这个字节码就是用java class文件来表示的,java class文件内部定义了虚拟机可以识别的字节码格式,这个格式是平台无关性的,在linux系统或者在windows系统上都是一致的。这个就好比html文件,我们定义好规范,这个系统只要去按照规范显示出来里面的内容就好了。好比html就是class文件,浏览器就是虚拟机一样,通过浏览器去执行html的渲染过程,我们无论是用手机,Windows系统,苹果系统上网,显示出来的内容都是一样。 java虚拟机可以从class文件中加载预定义的字节码,也可以从网络,数据库,消息文件中加载字节码。

  二、Java Class文件的格式

1.多个字节的数据采用大端存储

2.class文件采用定义u1、u2、u4来表示无符号的1、2、4字节数据。

java class
阅读详细 »

Java6,7,8中的String.intern() – 字符串常量池

本文是由http://java-performance.info/string-intern-in-java-6-7-8/翻译而来,如有错误请指正

—————Ranger的分割线—————

这篇文章主要讨论的是String.intern()方法在java6中的实现以及在java7、8中做了哪些改变

字符串常量池(又名字符串标准化)是用一个共享的String对象来替代很多拥有相同值但是不同含义的字符串对象。你能通过自定义的Map<String, String>(根据需要实现软引用或弱引用)并且使用map中的值作为标准值来实现这一目标。或者你可以使用JDK提供的String.intern()方法

有时很多标准是禁止在java6中使用String.intern(),因为如果不受控制的使用池会有可能会照成OutOfMemoryException。常量池在java7中的实现有所改变。在下面的网址可以看到详细介绍:http://bugs.sun.com/view_bug.do?bug_id=6962931 and http://bugs.sun.com/view_bug.do?bug_id=6962930.

Java6中的String.intern()

在美好的过去,所有的被intern的String被存储在PermGen区——虚拟机中一块固定大小的区域,主要用来存放加载的类和字符串常量池。除了被明确intern的字符串,PermGen区的字符串常量池也包含程序中之前使用的字符串(这里要注意是使用过的,如果一个类或方法从没有被加载或调用,任何定义的常量都不会被加载)

最严重的问题是在java6中字符串常量池是放在PermGen区。PermGen区有固定的大小并且在运行时不能被扩展。你可以使用-XX:MaxPermSize=N 来设置。据我所知,不同平台中默认的PermGen区大小是在32M到96M之间。你能增加它的大小,但是它的大小仍然是固定的。这种限制要求你必须小心地使用String.intern(),你最好不用这个方法intern任何不受控的用户输入。这就是为什么在java6中通过手动管理map来实现字符串常量池。

java7中的String.intern()

在java7中Oracel的工程师们对字符串常量池逻辑做了一个非常重要的改变——字符串常量池被迁移到堆中。这意味着你不用再受固定大小的内存限制。现在所有的字符串像其他普通对象一样被放置到了堆中,这样你调优的时候只需要管理堆的大小。在技术上,仅此一点就有足够的理由让我们重新考虑在java7中使用Strng.intern()。但是还有其他的原因。
阅读详细 »

JVM的一些概念

数据类型

Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。
基本类型包括:byte,short,int,long,char,float,double,Boolean,returnAddress
引用类型包括:类类型,接口类型和数组。

堆与栈

堆和栈是程序运行的关键,很有必要把他们的关系说清楚。

heap&stack

栈是运行时的单位,而堆是存储的单位。

栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。
在Java中一个线程就会相应有一个线程栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息。

为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗?

第一,从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。
第二,堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。
第三,栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。
第四,面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。 阅读详细 »

垃圾收集器选择

JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。

吞吐量优先的并行收集器

如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。

典型配置:

java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20

-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。 -XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。 java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC

-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100

-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy

-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。

响应时间优先的并发收集器

如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。

典型配置:

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC

-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。

-XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。

java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。 -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片

使用PrintGCDateStamps打印GC时间

之前打印gc日志的时候使用是:-XX:+PrintGCTimeStamps,这个选项记录的是jvm启动时间为起点的相对时间,可读性较差,不利于定位问题,使用PrintGCDateStamps记录的是系统时间,更humanreadable

JVM内存分析命令

jinfo:

[bash]查看Java进程的栈空间大小:sudo -u tomcat /home/java/default/bin/jinfo -flag ThreadStackSize 14750
查看是否使用了压缩指针:sudo -u tomcat /home/java/default/bin/jinfo -flag UseCompressedOops 14750
查看系统属性:sudo -u tomcat /home/java/default/bin/jinfo -sysprops 14750 [/bash]

jstack:

[bash]查看一个指定的Java进程中的线程的状态:sudo -u tomcat /home/java/default/bin/jstack 14750 [/bash]

jstat:

[bash]查看gc的信息:sudo -u tomcat /home/java/default/bin/jstat -gcutil 14750[/bash]

jmap&mat

[bash]sudo -u tomcat /home/java/default/bin/jmap -histo:live 14750

堆空间中各个年龄段的空间的使用情况:sudo -u tomcat /home/java/default/bin/jmap -heap 14750
[/bash]

jmap指定的dump文件一定要是tomcat用户可写,比如可以新创建一个文件夹
sudo mkdir /home/memdump
sudo chown tomcat:tomcat /home/memdump
sudo -u tomcat /home/java/default/bin/jmap -dump:live,format=b,file=/home/memdump/memMap.20131125.hprof 14750

Tomcat内存溢出的原因

在生产环境中tomcat内存设置不好很容易出现内存溢出。造成内存原因是不一样的,当然处理方式也不一样。 这里根据平时遇到的情况和相关资料进行一个总结。常见的一般会有下面三种情况:

1.OutOfMemoryError: Java heap space

2.OutOfMemoryError: PermGen space

3.OutOfMemoryError:unable to create new native thread.

tomcat

对于前两种情况,在应用本身没有内存泄露的情况下可以用设置tomcat jvm参数来解决。(-Xms, -Xmx, -XX:PermSize, -XX:MaxPermSize),最后一种可能需要调整操作系统和tomcat jvm参数同时调整才能达到目的。

第一种:是堆溢出。

在JVM中如果98%的时间是用于GC且可用的Heap size 不足2%的时候将抛出此异常信息。没有内存泄露的情况下,调整-Xms,-Xmx参数可以解决。

-Xms:初始堆大小

-Xmx:最大堆大小

但堆的大小受下面三方面影响:

1.相关操作系统的数据模型(32-bt还是64-bit)限制;(32位系统下,一般限制在1.5G~2G;我在2003 server 系统下(物理内存:4G和6G,jdk:1.6)测试 1612M,64为操作系统对内存无限制。)

2.系统的可用虚拟内存限制;

3.系统的可用物理内存限制。

堆的大小可以使用 java -Xmx***M version 命令来测试。支持的话会出现jdk的版本号,不支持会报错。

-Xms, -Xmx一般配置成一样比较好比如set JAVA_OPTS= -Xms1024m -Xmx1024m 阅读详细 »

5个不能不知的JVM参数

1. DisableExplicitGC
我已记不清有多少次用户要求我就应用程序性能问题提供咨询了,其实只要跨代码快速运行 grep,就会发现清单 1 所示的问题 — 原始 java 性能反模式:

清单 1. System.gc();

[java]
System.gc();

[/java]

显式垃圾收集是一个非常糟糕的主意 — 就像将您和一个疯狂的斗牛犬锁在一个电话亭里。尽管调用的语法是依赖实现的,但如果您的 JVM 正在运行一个分代的垃圾回收器(大多数是)System.gc(); 强迫 VM 执行一个堆的 “全部清扫”,虽然有的没有必要。全部清扫比一个常规 GC 操作要昂贵好几个数量级,这只是个简单数学问题。
您可以不把我的话放在心上 — Sun 的工程师为这个特殊的人工错误提供一个 JVM 标志; -XX:+DisableExplicitGC 标志自动将 System.gc() 调用转换成一个空操作,为您提供运行代码的机会,您自己看看 System.gc() 对于整个 JVM 执行有害还是有利。

阅读详细 »

JVM内存分配与回收策略

对象的内存分配,就是在堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接地在栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置。

新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。MajorGC的速度一般会比Minor GC慢10倍以上。

下面是最普遍的内存分配规则,并通过代码去验证这些规则。下面的代码在测试时使用Client模式虚拟机运行,没有手工指定收集器组合,验证的是使用Serial / Serial Old收集器下(ParNew / Serial Old收集器组合的规则也基本一致)的内存分配和回收的策略。

虚拟机提供了-XX:+PrintGCDetails这个收集器日志参数,告诉虚拟机在发生垃圾收集行为时打印内存回收日志,并且在进程退出的时候输出当前内存各区域的分配情况。在实际应用中,内存回收日志一般是打印到文件后通过日志工具进行分析,不过本实验的日志并不多,直接阅读就能看得很清楚。

1. 对象优先在Eden分配

执行testAllocation()中分配allocation4对象的语句时会发生一次Minor GC,这次GC的结果是新生代6651KB变为148KB,而总内存占用量则几乎没有减少(因为allocation1、2、3三个对象都是存活的,虚拟机几乎没有找到可回收的对象)。这次GC发生的原因是给allocation4分配内存的时候,发现Eden已经被占用了6MB,剩余空间已不足以分配allocation4所需的4MB内存,因此发生Minor GC。GC期间虚拟机又发现已有的3个2MB大小的对象全部无法放入Survivor空间(Survivor空间只有1MB大小),所以只好通过分配担保机制提前转移到老年代去。这次GC结束后,4MB的allocation4对象被顺利分配在Eden中。因此程序执行完的结果是Eden占用4MB(被allocation4占用),Survivor空闲,老年代被占用6MB(被allocation1、2、3占用)。

阅读详细 »