应用程序调试技术之Java篇

(1人)

199.50 元 5 折

全场5折优惠,咨询QQ810476411

Linux下编写的Java文件放到Windows下编译不通过->不同操作系统下的编码格式/Windows和Linux换行符的差异

 

1.在源代码中直接输入Unicode字符的跨平台问题。

javac -encoding UTF-8 GetBytesDemo.java

2.正确的做法。

 

计算字符串的长度(不同情况、不同场景下使用不同API)

由于一个字符在不同编码里,占用的字节数有可能不一致,因此:

1.String.length()用来返回字符串里char的个数。(Java里一个char占用2个字节)

2.String.codePointCount(int, int)用来返回字符串里Unicode码点(code point)

3.BreakIterator.getCharacterInstance()用来计算字符串所显示的字形个数。

 

软件支持多个国家销售->不同国家的编码格式不同,字形个数不同->导致的显示问题。(e.g.中文环境没问题,阿拉伯或其他国家看起来就一团乱..)

 

 

Java中各API对编码的处理

Java使用两种机制处理字符编码问题,刚开始是使用StringID,Java1.4之后引入了一个类型安全的Charset的类来处理编码。Java 6规范只要求支持(US-ASCII;ISO-8859-1;UTF-8;UTF-16BE;UTF-16LE;UTF-16),但一般的实现都会支持更多的编码方式

 

如果一个Java API在byte和char类型的数据之间进行转换,那很有可能使用的是默认编码,可以使用Charset.defaultCharset()获取。比如:String(byte[]), String.getBytes(), InputStreamReader(InputStream);OutputStreamWriter(OutputStream);FileReader和FileWriter。

->程序是否是永远运行在跟你写程序的时候一样的操作系统上面。

->网页接收到来自世界各地的请求,用打开浏览器的操作系统编码(你的服务器的操作系统可能跟浏览器的操作系统不一样)

 

使用上面API的问题是,你不能保证在一台机器上保存的数据,可以在另外一台机器上正确打开,建议坚持使用OutputStreamWriter(OutputStream, Charset)这样显式指定编码的API编程

 

演示使用默认编码的问题

解决方案

->程序跨平台,跨操作系统运行

[展开全文]

演示getBytes和new String等API对编码的处理

 

同一个字符在不同的编码下面对应的程序数据不一样->很多国际化问题的根源。(字符显示不正常的问题演示(同一个字符在不同操作系统下显示不正确的情况))

 

String API作为作业

 

记事本->使用操作系统里的编码,默认是ANSI

Windows命令行提示符->使用操作    系统的编码

Linux终端->使用UTF-8,Ubuntu系统默认使用该编码格式

gedit->默认使用ISO-8859-15,用户可以在打开/保存对话框里选择编码格式

IOS操作系统->...

 

操作系统版本越来越多,程序如果要在很多操作系统上正确的显示,使用->要好好考虑编码符号

 

Unicode举例

Java源文件中的Unicode字符

Java编译器支持包含Unicode字符的源文件,一般有两个方案在源代码中包含Unicode字符:

1.直接将Unicode字符输入Java源程序中,编译时指定javac使用的编码(不同平台 下的编译结果?)

2.只在源代码中直接敲入各编码下值统一的字符,其他字符则使用Unicode转义序列。

[展开全文]

浮点数的特殊情况(以数学公式存储->近似数值)

->判断浮点数相等(两个数相减在一定误差范围之内)

 

编码基本知识

ISO8859-1:属于单字节编码,最多能表示的字符范围是0-255

GB2312/GBK:汉字的国际码,专门用于表示汉字,是双字节编码。

UNICODE:最统一的编码,可以用来表示所有语言的字符,而且是定长双字节(也有四字节的)编码。(只是定义了一个规范)

->定义好的数字需要转换成程序的数字(程序可以保存的数据),双字节有大头小头等问题。保存在4个字节还是保存在2个字节?

UTF:是不定长编码,兼容iso8859-1编码,同时也可以用来表示所有语言的字符。

->把unicode的数据转换成程序能保存的数据。

 

getBytes(charset)API是Java字符串处理的一个标准函数,其作用是将字符串所表示的字符按照charset编码,并以字节方式表示。

new String(charset)API是Java字符串处理的一个标准函数,和上一个函数的作用相反,将字节数组按照charset编码进行组合识别,最后转换成Unicode存储。

 

除了一些英文或者拉丁字符意外,很多字符在不同的编码下具有不同的值

 

 

以字节保存->大头和小头的问题?

保存字节的数量?(保存在4个字节里还是2个字节里?)->UTF把Unicode所表示的的数字转换成程序所保存的数据(UTF-8/UTF-16/UTF-32)

UTF-8(可变的变长编码)

UTF-16(双字节编码格式)

UTF-32(四字节编码格式)

UTF有不同的格式,每种编码有不同优缺点

字 Unicode:23383

 

网络 倾向于使用UTF-8, 为什么?(网站上很多都是英文的,甚至包括中文网页(HTML标签)...占用存储空间小)

操作系统/编程语言 倾向于使用UTF-16->定长

[展开全文]

JDK内存工具使用介绍 - HPROF Heap Profiler

HPROF是随JDK发布的性能分析工具,可以收集CPU使用率(修改字节码,监听调用情况)、内存分配情况,而且也可以用来发现死锁等线程相关的问题。

 

HPROF参数说明

 

跟踪每个对象由谁创建(修改类加载的字节码,可以保存对象创建时的堆栈信息)

java -agentlib:hprof=heap=sites <java类>

获取内存堆(heap)的完整信息

java -agentlib:hprof=heap=dump <java类>

二进制格式的hprof文件可以用jhat工具来分析

 

HPROF->动态链接库,使用JVMTI接口(外部程序/外部库监听JVM里面的事件,甚至可以修改java程序的一些行为)

->输出到本地文件(文本格式/二进制格式)

->输出到网络socket

 

参数说明/运行情况/jhat分析

GC/内存堆

java.sun.com/developer/technicalAricles/Programming/HPROF.html

http://docwiki.cisco.com/wiki/Java_HProf_Files

 

参数说明

java -agentlib:hprof=help

 

cpu=samples(基于概率论取样->哪些函数经常被调用)

cpu=times(函数调用次数,性能影响大,在函数入口和出口处都会修改字节码)

monitor=y|n(监视同步锁信息)

format=a|b(收集数据保存到文本(ANSI/Binary))

file=<file> 数据保存到文件

new=<host>:<port> 数据保存到远端端口

depth=<size> 调用最上层几层堆栈

interval=<ms> 每次取样的间隔时间点

lineno=y|n 有源文件信息->行信息

thread=y|n 是否包含线程相关信息

doe=y|n 程序退出时,是否保存程序内存堆到文件

force=y|n 是否把好几个java文件输出到一个文件

 

演示使用HPROF分析对象分配情况

java -agentlib:hprof=heap=sites <java类>

演示使用HPROF分析Java线程死锁

java -agentlib:hprof=monitor=y <java类>

ctrl+\

演示使用HPROF分析java程序CPU使用率

java -agentlib:hprof=cpu=samples <java类>

 

java -agentlib:hprof=cpu=times <java类>

java -agentlib:hprof=cpu=times,lineno=y <java类>

[展开全文]

线程饿死与公平执行机制

1.导致线程饿死的原因

1.线程总是被高优先级的线程抢占了CPU执行时间

2.线程没有机会获取锁资源从而导致一直等待

3.线程一直在等待一个对象(通过调用wait()函数)

 

2.在java里实现公平执行机制。

1,使用Locks替代Synchronized块。

 

伪共享问题

是由于CPU cache机制造成的,CPU读取Cache时是以行为单位读取的,若两个硬件线程的两块不同内存位于同一Cache行里,那么当两个硬件线程同时在对各自的内存进行写操作时,将会造成两个硬件线程写同一Cache行的问题,它会引起竞争,效率将成百倍的下降。

 

在单核系统中,伪共享问题是不存在的,因为同一时刻只有一个硬件线程在执行,不存在同时写同一Cache行的问题。

 

伪共享问题在实际情况中是经常可以碰到的,比如两个线程同时写一个数组的相邻部分,或者写两块相邻的内存,这些都有可能造成伪共享问题。

 

解决方案: -> 填充字节

e.g 1:

public class LinkedList {

LinkedList head;

LinkedList tail;

}

| head | tail |           

 

e.g 2:

| core1 | core2 | core3 | core4 | ... | <-

| core1 | 56 ... | core2 |  56 ... | core3 | 56 ... |

[展开全文]

调试嵌套锁的线程停工问题。

 

防止线程饿死 -> 公平机制(线程存在时间+ -> 优先级+)

 

java中获取锁的顺序不一致,并不是谁先到先得的。

->默认获取锁不是公平机制,谁在最合适的时机到谁得到锁

->公平锁,构建队列。(影响性能)

 

嵌套锁停工和死锁的区别

1.嵌套锁停工和死锁的现象很类似,都是引起线程无限期的等待。

2.一般来说,死锁都是因为两个线程获取锁的顺序不一引起的,如线程1获取锁A、等待锁B,而线程2获取锁B,等待锁A。因此可以通过确保两个线程获取锁的顺序一致来避免死锁的发生。

3.而嵌套锁停工却是因为两个线程获取锁的顺序一致引起的。如线程1获取锁A、B,然后释放B并等待线程2发布的一个信号(signal),而线程2又同时需要锁A和B才能发送信号给线程1

[展开全文]

生产环境下产生死锁:

在Java程序里(服务器程序里)发现和解决死锁问题的方法

1.从Java 5.0 开始,可以在代码里调用ThreadMXBean的findMonitorDeadlockedThreads函数来找到因synchronized关键字导致的死锁

2.从java 6.0 开始,在ThreadMXBean里添加了findDeadlockedThreads函数,可以查找出因ReenTrantlock或ReentrantReadWriteLock引起的死锁问题

(findDeadlockedThreads函数无法找到ReenTrantLock和Synchronized混合引起的死锁)

3.一般来说,如果发现进程内有死锁的线程,要么是粗暴地结束其中一个线程,要么就是通知管理员人工干预-例如重启进程。

4.可以通过ManagementFactory来获取ThreadMXBean的实例。(远程的调试器或远程的程序可以去看指定的Java程序内部的一些线程信息)

 

线程3(监护线程)定期去检查线程1和线程2是否死锁了->如果死锁了,线程3可以采取一些步骤。

ThreadMXBean->远程的调试器/远程程序 去看 指定的java程序内部的线程信息

 

在java程序里发现和解决死锁问题的方法

 

。调试竞争锁资源问题

。调试使用CPU资源过多的问题

[展开全文]

Encoding

Decoding

 

utf-8 --> 变长编码格式

 

编码和解码的方式

 

utf-16 -> 字节顺序标志位

[展开全文]

在Emacs里运行JDB(在Emacs里运行GDB)->方便源码的浏览(获取Eclipse源码级别体验)

 

Emacs的GUI

jdb -attach 8765

 

jdb里面怎么找到源代码,Emacs里面怎么显示源代码

 

相比Eclipse更轻量,临时解决方案

[展开全文]

Java平台调试器架构

调试器(C)--被调程序(S)

调试器(S)--被调程序(C)

 

远程调试原理

通过客户机-服务器架构,可以在本地调试Java程序,也可以通过网络进行远程调试,JPDA...

 

进程A和进程B共享内存,只能用于调试本地程序。

 

远程调试命令参数

 

 

被调程序当做调试服务器

-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8765,suspend=y

 

被调程序当做调试客户端

-Xdebug -Xrunjdwp:transport=dt_socket,address=127.0.0.1:8000

 

Q: 既然是远程调试,为什么可以添加source?这个source和远程的一致么?(被调程序当做服务器时,Debuger(Eclipse里的source代码)和Debugee端不一致时直接执行完成)/如果代码都是一致的,那在本地调试就可以了啊?还有这中间的断点截获是通过什么来完成的?

远程调试的具体应用场景和意义?

 

使用jdb调试java程序

 

作业:修复作业文件夹里的代码

[展开全文]

断点实现原理

 

特殊断点 {

条件断点

监视断点 > 对成员变量做改变

函数断点

异常断点

类型断点 - Outline->Toggle Class Load Breakpoint/Run->Add Class Load Breakpoint

}

 

Eclipse调试技巧 {

在堆栈的任意位置重新执行语句 > 上层传入参数有问题 ,到上层位置重新执行一遍(该Frame后的结果全部抛弃,但是只修改局部变量)进行排错

在程序启动时进行调试

使用变量窗口的逻辑视图 > 容器类各家实现不一样(链表、数组...不需要知道java程序内部结构)

单步过滤调试(不进入JDK函数) > Window>Preferences>Java>Debug>Step Filtering

计算表达式 (实时地操作)

}

[展开全文]

Java GC器

虚拟机提供了好几个GC收集器,Java 5在Serial GC之外,还添加了几个GC收集器:

。Serial GC:只使用一个GC线程执行垃圾回收工作,在执行GC时,其他线程都会暂停工作,手机Young generation里的垃圾对象

。Parallel(Throughput) GC:使用多个GC线程并行执行垃圾回收工作,在执行GC时,其他线程都会暂停工作,手机Young generation里的垃圾对象

。Concurrent mark Sweep(CMS)GC:用于手机Old generation里的垃圾对象,跟其他线程并行工作,单需要标注某个线程里的垃圾对象时,会暂停线程一小会,其他时候可以与线程并行执行。

 

在GC时,通常Young generation里的GC,即minor GC很快,当Old generation空间不够时,Java虚拟机首先会尝试CMS GC并行手机,如果这样空间还不能快速回收时,那Java虚拟机会暂停所有线程执行GC,这个时候称为 Full GC。一般来说, Full GC的执行效率要比minor GC慢很多,程序优化的目标也是尽量减少Full GC的次数。

 

 

Java内存堆

Java虚拟机在启动时,会从操作系统申请一大块内存,后续Java程序运行时,所有对象都在这个内存块里分配,这个内存块叫Java Heap。

Java内存堆在Java程序启动后无法更改,只能够通过修改Java程序的-Xms(初始)、-Xmx(最大-物理内存的1/10?)、-Xmn(最小)等启动参数改变设置。当Java堆内存用光,而且GC也无法收集更多的内存时,抛出OutOfMemoryError。

可以使用Jconsole、Runtime.maxMemory()、Runtime.totalMemory()以及Runtime.freeMemory()等函数查询Java程序的内存堆设置。

[展开全文]

java GC 堆结构

 

节省GC的次数(GC对程序的影响)

 

java堆分成3个部分,或称为届(generation),分别是Young(New)generation, Tenured(Old) generation, Perm Area of heap。而New generation 又细分成Eden space,Survivor 1和Survivor 2。新的对象总是先在Eden space上创建,经过minor GC后,剩余的会挪到Survivor1,接着是Survivor 2,当执行过Major GC后,对象会被挪到Old Generation中。

 

很多时候做内存优化->关注Old generation(被长时间引用的对象)。

有的时候优化:调整Young generation中Eden Space和Survivor之间的的比例(经常创建小的对象,Eden Space设置的大一点/ Java对象经常被长期引用到,换另外一种设置)

 

Perm area主要用来保存java类型以及函数的元数据信息,有的内在化(internalization)的字符串也会放在里面(使用String.intern()的字符串)

 

 

[展开全文]

Java虽然有垃圾回收机制,但是程序编写不慎,还是会发生内存泄露导致OutOfMemoryError的发生。

->GC的机制

->JDK自带的HPROF工具以分析内存问题

 

Java GC简介

。所有Java对象都是分配在Java堆上面的

。Java上使用垃圾回收机制回收没有引用到的对象

。Java虚拟机有专门的GC线程用来执行垃圾回收

。当GC线程从内存删除一个对象时,首先会调用对象的finalize函数,在这个函数里可以执行自定义的释放资源操作

。Java程序自身无法强制启动GC,即使使用System.gc()和Runtime.gc()这样的函数,也只是递交一个GC请求给GC线程

。当无法再Java内存堆(Java heap)上创建对象时,Java虚拟机会抛出OutOfMemoryError

 

对象没有被其他对象引用,是指从GC Root开始遍历,无法遍历到的对象,(循环引用看做是无效引用)GC Root包括:

。Class - 系统里加载的类,这个类不会被卸载,类里的静态变量可能会引用其他Java对象

。Thread - 正在运行的线程(变量,本地存储...)

。堆栈上的局部变量 - 堆栈上的函数还要运行,因此他们引用到的对象都是有用的

。JNI参数和局部变量(java很难控制c/c++运行环境)

。锁(Monitor) - 用于线程同步

[展开全文]

多线程变成中,编写不当,极易发生死锁,占用CPU资源过多等问题。  -->出现问题时的调试方案。

多线程编程的一些问题

1.数据竞争(Data Race),多个线程同时访问共享数据, 而数据没有使用锁保护

2.死锁

3.伪共享,一般发生在多核机器上,一个或者多个核不停地刷新其他核上的缓存

4.锁的类型使用不当,导致程序性能缓慢!

 

调试死锁问题

 

jps

jstack

 

*nix下 ->  kill -3 [processid]

[展开全文]

跟踪线程的操作记录

有的时候要追踪锁的问题(有些共享数据本来要加锁却又没有加锁的问题/共享数据共享多了,不知道哪个共享数据应该是要加锁的)-> 内存日志技术,把一些操作我们感兴趣的数据,变量的值保持在内存中,需要时打印出来

 

多线程不用System.out.println()的调试方法:

System.out.println() -> 和控制台相关,一系列过程耗费时间并且影响IO操作->有些线程本来执行的时间很短,加入System.out.println()会打乱线程的执行顺序。->可能就重现不了原来的问题(线程处理IO请求,OS会暂停线程执行,等待IO执行完毕再调回来)

 

用内存日志的方式,记录感兴趣数据的时间尽量短。

MemoryLog

 

gdb 

javac -g

 

[展开全文]

多线程编程的一些模式

 

多线程编程中,编写不当,极易发生死锁,占用CPU资源过多等问题。

 

忘记给共享资源加锁->怎样去发现?怎样去调试?

 

使用多线程编程的原因:

1.模拟多个同时并发的活动。

2.使用多核同时运行加速计算速度。

 

多线程的编程模式有:

1.使用锁,即通过锁机制在多个线程访问的时候保护共享数据

2.消息传递,线程之间没有共享数据,一个线程通过发消息给另一个线程的方式传递消息,例如MPI和CORBA

3.自动并发,即编译器可以讲一个串行的程序改成并行地,即可以通过在串行程序中添加并行提示,例如OpenMP标准。也可以使没有提示-这一块还没有例子。

4.使用事务内存(Software Transactional Memory - STM),即多个线程之间共享的数据是通过事务更新的。Intel C++编译器有一个这样的原型编译器。

 

1.调试数据竞争问题(通常是忘记给程序加锁导致的问题)

->确定哪个数据是正确的(多线程转成单进程执行->Eclipse中中断其他线程执行,屏蔽多线程带来的数据竞争等问题,看单线程是不是有逻辑问题)

->看各个线程代码,代码中是否有共享数据域的地方忘记加锁了

2.跟踪线程的操作记录

[展开全文]

多线程编程的一些建议

1.多线程程序比单线程程序复杂很多,特别是有了多核CPU之后,内存访问模式变化就更大了(4个线程速度比2个线程速度慢的情况)

2.调试器会影响触发多线程问题的时间因素

3.在写多线程之前,先确保代码在单线程环境里是可以通过的

4.尽量不要用多线程 - 特别是客户端程序

5.不要创建过多的线程

6.线程同步锁的范围越小越好 - 最好不用将线程同步原语封装成类

7.尽量在代码里添加调试支持

[展开全文]
kill -3 pid
[展开全文]

授课老师

知平软件CEO

学员动态

QQ客服: 810476411

QQ咨询: 810476411

QQ吐槽: 810476411

服务时间: 9:00 - 21:00

刘老师: 18516031455

微信公众号:开源力量